From 5b2ddc8b19415c20704bb79e01a4a25886de2341 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Thu, 16 Apr 2026 02:02:18 +0700 Subject: [PATCH 01/42] Move clustering from graspologic to graspologic_native --- ragu/graph/graph_builder_pipeline.py | 40 +++++++++++++++++++++------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/ragu/graph/graph_builder_pipeline.py b/ragu/graph/graph_builder_pipeline.py index 006309e..7c71388 100644 --- a/ragu/graph/graph_builder_pipeline.py +++ b/ragu/graph/graph_builder_pipeline.py @@ -3,7 +3,7 @@ from typing import Any, Dict, List, Tuple import networkx as nx -from graspologic.partition import HierarchicalClusters, hierarchical_leiden +from graspologic_native import hierarchical_leiden from ragu.chunker.base_chunker import BaseChunker from ragu.chunker.types import Chunk @@ -240,19 +240,41 @@ async def cluster_graph( if graph.number_of_nodes() == 0 or graph.number_of_edges() == 0: return [] - community_mapping: HierarchicalClusters = hierarchical_leiden( - graph, + edges: List[tuple[str, str, float]] = [ + (str(u), str(v), 1.0) + for u, v in graph.edges() + ] + + raw_community_mapping = hierarchical_leiden( + edges, + starting_communities=None, max_cluster_size=self.build_parameters.max_cluster_size, - random_seed=self.build_parameters.random_seed, + seed=self.build_parameters.random_seed, + resolution=1.0, + randomness=0.001, + use_modularity=True, + iterations=1 ) - clusters = defaultdict(lambda: defaultdict(lambda: {"entity_ids": set(), "relation_ids": set()})) + def _extract_mapping_item(item: Any) -> tuple[str, int, int]: + if hasattr(item, "node") and hasattr(item, "cluster") and hasattr(item, "level"): + return str(item.node), int(item.cluster), int(item.level) + + if isinstance(item, dict): + return str(item["node"]), int(item["cluster"]), int(item["level"]) + + if isinstance(item, (tuple, list)) and len(item) >= 3: + return str(item[0]), int(item[1]), int(item[2]) + + raise TypeError(f"Unsupported hierarchical_leiden output item: {item!r}") + + clusters = defaultdict( + lambda: defaultdict(lambda: {"entity_ids": set(), "relation_ids": set()}) + ) node_membership = defaultdict(set) - for part in community_mapping: - level = part.level - cluster_id = part.cluster - node_id = str(part.node) + for part in raw_community_mapping: + node_id, cluster_id, level = _extract_mapping_item(part) node = entity_by_id.get(node_id) if node is None: From cbb7cbd61f22b44a129c9c6f6681631d7a5ab6f2 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 20 Apr 2026 15:25:35 +0700 Subject: [PATCH 02/42] Add qdrant vdb --- .../vdb_storage_adapters/qdrant_vdb.py | 480 ++++++++++++++++++ 1 file changed, 480 insertions(+) create mode 100644 ragu/storage/vdb_storage_adapters/qdrant_vdb.py diff --git a/ragu/storage/vdb_storage_adapters/qdrant_vdb.py b/ragu/storage/vdb_storage_adapters/qdrant_vdb.py new file mode 100644 index 0000000..465d04d --- /dev/null +++ b/ragu/storage/vdb_storage_adapters/qdrant_vdb.py @@ -0,0 +1,480 @@ +import asyncio +import uuid +from pathlib import Path +from typing import Any, List, Dict, override, Literal, cast + +from qdrant_client.conversions.common_types import CollectionInfo, QueryResponse +from qdrant_client.http.models import FusionQuery, Fusion + +from pydantic import BaseModel +from ragu.common.global_parameters import Settings +from ragu.storage.base_storage import BaseVectorStorage +from ragu.storage.types import EmbeddingHit, Point +from ragu.common.logger import logger + +from qdrant_client import AsyncQdrantClient, models +from qdrant_client.models import ( + Distance, + Modifier, + PointIdsList, + PointStruct, + Prefetch, + SparseVector, + SparseVectorParams, + VectorParams, +) + +from ragu.utils.ragu_utils import split_on_batches_by_size + + +class QdrantVectorDBStorage(BaseVectorStorage): + """ + Qdrant-backed vector storage for dense-only and hybrid retrieval. + + This adapter creates or validates one Qdrant collection containing: + + - one dense vector field named ``"dense"`` + - optionally one sparse vector field named after ``sparse_type`` + such as ``"bm25"``, ``"bm42"``, or ``"splade"`` + + Storage modes + ------------- + The constructor supports three common Qdrant deployment styles: + + 1. Local on-disk mode + If no remote connection arguments are provided, the adapter starts + Qdrant in local mode. The directory containing ``filename`` is used + as the Qdrant storage path. This is the default mode used by RAGU. + + 2. In-memory mode + Pass ``location=":memory:"`` to use Qdrant's in-memory local backend. + This is useful for tests, examples, and short-lived indexing sessions. + + 3. Remote server mode + Pass connection parameters such as ``url``, ``host``, ``port``, + ``grpc_port``, or ``api_key`` to connect to an external Qdrant server. + In this case no local path is used. + + Sparse retrieval modes + ---------------------- + ``sparse_type`` controls whether the collection stores sparse vectors and + which Qdrant sparse vector name is used: + + - ``None``: dense-only retrieval + - ``"bm25"``: sparse vector named ``"bm25"``, with Qdrant ``IDF`` modifier + - ``"bm42"``: sparse vector named ``"bm42"``, with Qdrant ``IDF`` modifier + - ``"splade"``: sparse vector named ``"splade"`` + - ``"custom"``: sparse vector named ``"custom"`` + + For dense+sparse queries, the adapter uses Qdrant reciprocal-rank fusion + by default. + + Examples + -------- + Local on-disk dense-only collection: + + ```python + from ragu.storage.vdb_storage_adapters.qdrant_vdb import QdrantVectorDBStorage + + vdb = QdrantVectorDBStorage( + embedding_dim=768, + storage_folder="./storage", + filename="entity_vectors.json", + ) + ``` + + In-memory collection: + + ```python + vdb = QdrantVectorDBStorage( + embedding_dim=768, + location=":memory:", + collection_name="temp_vectors", + ) + ``` + + Remote Qdrant server: + + ```python + vdb = QdrantVectorDBStorage( + embedding_dim=768, + url="http://localhost:6333", + api_key="secret", + collection_name="prod_chunks", + ) + ``` + + Hybrid dense + BM25 sparse retrieval: + + ```python + vdb = QdrantVectorDBStorage( + embedding_dim=768, + filename="chunk_vectors.json", + sparse_type="bm25", + ) + ``` + + Hybrid dense + BM42 sparse retrieval: + + ```python + vdb = QdrantVectorDBStorage( + embedding_dim=768, + location=":memory:", + sparse_type="bm42", + collection_name="hybrid_demo", + ) + ``` + """ + + DENSE_VECTOR_NAME = "dense" + DEFAULT_SPARSE_VECTOR_NAME = "sparse" + + def __init__( + self, + embedding_dim: int, + storage_folder: str | None = None, + filename: str = "data.json", + collection_name: str | None = None, + path: str | None = None, + url: str | None = None, + host: str | None = None, + port: int | None = None, + grpc_port: int | None = None, + api_key: str | None = None, + location: str | None = None, + sparse_type: Literal["bm25", "bm42", "splade", "custom"] | None = None, + non_default_dense_config: VectorParams | None = None, + non_default_sparse_config: SparseVectorParams | None = None, + max_payload_size_in_mb: int = 16, + **kwargs: Any, + ): + """ + Initialize a Qdrant-backed vector store. + + Collection naming defaults to the stem of ``filename`` under + ``storage_folder``. For example, ``filename="vdb_entity.json"`` + becomes the collection name ``"..._vdb_entity"``. + + Remote connection arguments are passed directly into + :class:`qdrant_client.AsyncQdrantClient`. If any of them are provided, + the adapter does not open a local on-disk path. + + :param embedding_dim: Dense vector dimensionality. + :param storage_folder: Base folder for local Qdrant storage. + :param filename: Storage filename used to derive collection name and local path. + :param collection_name: Explicit Qdrant collection name override. + :param path: Explicit local Qdrant path override for on-disk local mode. + :param url: Remote Qdrant URL. + :param host: Remote Qdrant host. + :param port: Remote Qdrant HTTP port. + :param grpc_port: Remote Qdrant gRPC port. + :param api_key: Remote Qdrant API key. + :param location: Qdrant location identifier, including ``":memory:"`` for in-memory mode. + :param sparse_type: Optional sparse vector mode. Supported values are + ``"bm25"``, ``"bm42"``, ``"splade"``, and ``"custom"``. + :param non_default_dense_config: Optional custom dense vector config. + :param non_default_sparse_config: Optional custom sparse vector config. + :param max_payload_size_in_mb: Maximum upsert batch payload size before splitting. + :param kwargs: Additional client options forwarded to ``AsyncQdrantClient``. + """ + super().__init__() + resolved_storage_folder = storage_folder if storage_folder else Settings.storage_folder + resolved_filename = Path(resolved_storage_folder) / filename + + self.filename = str(resolved_filename) + self.embedding_dim = embedding_dim + + self._dense_config = non_default_dense_config or VectorParams( + size=self.embedding_dim, + distance=Distance.COSINE, + ) + self._sparse_config = non_default_sparse_config or SparseVectorParams( + modifier=Modifier.IDF if sparse_type in {"bm25", "bm42"} else None + ) + + self._sparse_mode = sparse_type + self._max_payload_size_in_bytes = max_payload_size_in_mb * 1024 * 1024 + + remote_kwargs = { + "url": url, + "host": host, + "port": port, + "grpc_port": grpc_port, + "api_key": api_key, + "location": location, + } + remote_kwargs = {key: value for key, value in remote_kwargs.items() if value is not None} + client_kwargs = dict(kwargs) + + filename_path = Path(self.filename) + self.collection_name = (collection_name or + str(Path(resolved_storage_folder) / filename_path.stem).replace("/", "_")) + self._client_path = None if remote_kwargs else str(Path(path) if path is not None else filename_path.parent) + self._client_kwargs = {**remote_kwargs, **client_kwargs} + + self._client: AsyncQdrantClient | None = None + self._collection_ready = False + self._collection_lock = asyncio.Lock() + + def _get_client(self) -> AsyncQdrantClient: + """ + Return the lazily initialized Qdrant client. + + :returns: Async Qdrant client instance. + """ + if self._client is None: + self._client = AsyncQdrantClient(path=self._client_path, **self._client_kwargs) + assert self._client is not None + return self._client + + def _to_qdrant_point_id(self, record_id: str) -> str: + """ + Convert arbitrary RAGU record IDs into a Qdrant-compatible point ID. + """ + return str(uuid.uuid5(uuid.NAMESPACE_URL, f"{self.collection_name}:{record_id}")) + + async def _ensure_collection(self) -> None: + """ + Create or validate the Qdrant collection schema. + + :raises ValueError: If an existing collection schema does not match the adapter configuration. + """ + async with self._collection_lock: + if self._collection_ready: + return + + client = self._get_client() + + expected_sparse_name = self._sparse_mode + expected_sparse_modifier = Modifier.IDF if self._sparse_mode in {"bm25", "bm42"} else None + + if await client.collection_exists(self.collection_name): + collection_info: CollectionInfo = await client.get_collection(self.collection_name) + params = collection_info.config.params + + vectors_config = params.vectors + if vectors_config is None: + raise ValueError( + f"Qdrant collection '{self.collection_name}' does not define any dense vectors" + ) + + dense_config: VectorParams | None + if isinstance(vectors_config, dict): + dense_config = vectors_config.get(self.DENSE_VECTOR_NAME) + else: + dense_config = vectors_config + + if dense_config is None: + raise ValueError( + f"Qdrant collection '{self.collection_name}' does not define " + f"the required dense vector '{self.DENSE_VECTOR_NAME}'" + ) + + if dense_config.size != self.embedding_dim: + raise ValueError( + f"Qdrant collection '{self.collection_name}' expects dense dimension " + f"{dense_config.size}, got {self.embedding_dim}" + ) + + if dense_config.distance != Distance.COSINE: + raise ValueError( + f"Qdrant collection '{self.collection_name}' uses dense distance " + f"{dense_config.distance}, expected {Distance.COSINE}" + ) + + sparse_vectors_config = getattr(params, "sparse_vectors", None) or {} + + if self._sparse_mode: + sparse_config = sparse_vectors_config.get(expected_sparse_name) + if sparse_config is None: + raise ValueError( + f"Qdrant collection '{self.collection_name}' does not define " + f"the required sparse vector '{expected_sparse_name}'" + ) + + existing_modifier = getattr(sparse_config, "modifier", None) + if existing_modifier != expected_sparse_modifier: + raise ValueError( + f"Qdrant collection '{self.collection_name}' has sparse modifier " + f"{existing_modifier} for '{expected_sparse_name}', " + f"expected {expected_sparse_modifier}" + ) + + else: + create_kwargs: dict[str, Any] = { + "collection_name": self.collection_name, + "vectors_config": { + self.DENSE_VECTOR_NAME: self._dense_config, + }, + } + + if self._sparse_mode: + create_kwargs["sparse_vectors_config"] = { + expected_sparse_name: self._sparse_config, + } + + await client.create_collection(**create_kwargs) + + self._collection_ready = True + + @override + async def upsert(self, data: List[Point], **kwargs: Any) -> None: + """ + Insert or update dense records with optional sparse side channels. + + :param data: Embedding records to upsert. + """ + + if not data: + return + + await self._ensure_collection() + + has_sparse = [item.sparse_embedding is not None for item in data] + if any(has_sparse) and not all(has_sparse): + raise ValueError("All points in this batch must either have sparse embeddings or not have them.") + + if not self._sparse_mode and has_sparse: + raise ValueError(f"Try to insert sparse embeddings, but `sparse_type` parameter is set to None. " + f"Please, set `sparse_type` parameter. Possible values: bm25, bm42, splade, custom") + + points: list[PointStruct] = [] + for point in data: + dense_embedding = point.dense_embedding + sparse_embedding = point.sparse_embedding + + payload = dict(point.metadata) + payload["__ragu_id__"] = point.id + + vector_payload: Dict[str, list[float] | models.SparseVector] = {} + if dense_embedding is not None: + vector_payload[self.DENSE_VECTOR_NAME] = dense_embedding.tolist() + + if sparse_embedding is not None and self._sparse_mode: + vector_payload[self._sparse_mode] = models.SparseVector( + indices=sparse_embedding.indices, + values=sparse_embedding.values + ) + + points.append( + PointStruct( + id=self._to_qdrant_point_id(point.id), + vector=vector_payload, + payload=payload, + ) + ) + + if not points: + return + + for batch in split_on_batches_by_size(points, self._max_payload_size_in_bytes): + await self._get_client().upsert( + collection_name=self.collection_name, + points=batch, + wait=True, + ) + + @override + async def query(self, point: Point, **kwargs: Any) -> List[EmbeddingHit]: + """ + Query Qdrant using dense-only or dense+sparse reciprocal-rank fusion. + :param point: Qdrant point + :param kwargs: + top_k: int Maximum number of results to return. + hybrid_search_query_type. + :returns: Ranked embedding hits. + """ + + # Parameters from kwargs + hybrid_query_type: BaseModel = kwargs.pop("hybrid_query_type", FusionQuery(fusion=Fusion.RRF)) + top_k: int = kwargs.pop("top_k", 20) + + await self._ensure_collection() + + prefetch: list[Prefetch] | None = None + query: Any + using: str | None = None + + if point.dense_embedding is not None and point.sparse_embedding is None: + query = point.dense_embedding + using = self.DENSE_VECTOR_NAME + + elif point.dense_embedding is not None and point.sparse_embedding is not None: + if not self._sparse_mode: + logger.warning(f"Try to use sparse embeddings, but `sparse_type` parameter is set to None. " + f"This can lead to unexpected results.") + prefetch = [ + Prefetch( + query=point.dense_embedding.tolist(), + using=self.DENSE_VECTOR_NAME, + limit=top_k, + ), + Prefetch( + query=SparseVector( + values=point.sparse_embedding.values, + indices=point.sparse_embedding.indices, + ), + using=self._sparse_mode, + limit=top_k, + ), + ] + query = hybrid_query_type + + else: + raise NotImplementedError("Only dense and dense+sparse queries are supported") + + query_response: QueryResponse = await self._get_client().query_points( + collection_name=self.collection_name, + query=query, + prefetch=prefetch, + using=using, + with_payload=True, + limit=top_k, + ) + + hits: list[EmbeddingHit] = [] + for retrieved_point in query_response.points: + payload = dict(retrieved_point.payload or {}) + record_id = str(payload.pop("__ragu_id__", point.id)) + hits.append( + EmbeddingHit( + id=record_id, + distance=float(retrieved_point.score), + metadata=payload, + ) + ) + return hits + + async def delete(self, ids: List[str], **kwargs: Any) -> None: + """ + Delete records from the collection by RAGU IDs. + + :param ids: Record identifiers to remove. + """ + if not ids: + return + + await self._ensure_collection() + await self._get_client().delete( + collection_name=self.collection_name, + points_selector=PointIdsList(points=[self._to_qdrant_point_id(record_id) for record_id in ids]), + wait=True, + ) + + async def index_start_callback(self): + """ + Ensure collection availability before indexing starts. + """ + await self._ensure_collection() + + async def index_done_callback(self): + """ + Finalize indexing. + """ + pass + + async def query_done_callback(self): + """ + Finalize query processing. + """ + pass From 9874943e9e137eeb848d44143f3c697315de9435 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 20 Apr 2026 15:26:30 +0700 Subject: [PATCH 03/42] Update embeddings types to support hybrid retrieval --- ragu/storage/types.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/ragu/storage/types.py b/ragu/storage/types.py index c1c3bf3..800fb39 100644 --- a/ragu/storage/types.py +++ b/ragu/storage/types.py @@ -1,6 +1,6 @@ import time from dataclasses import dataclass, field -from typing import Any, List, Dict, Optional +from typing import Any, List, Dict from ragu.utils.ragu_utils import FLOATS, compute_mdhash_id, serialize @@ -34,23 +34,32 @@ def to_dict(self) -> Dict[str, Any]: return serialize(self) +DenseEmbedding = FLOATS + + @dataclass(slots=True) -class Embedding: - """ - Representation of an embedding. +class SparseEmbedding: + indices: List[int] + values: List[float] + + def __post_init__(self): + if len(self.indices) != len(self.values): + raise ValueError("indices and values must have the same length") - :param id: Unique record identifier. - :param vector: Embedding vector. - :param metadata: Additional payload. - """ - vector: List[float] | FLOATS - metadata: Dict[str, Any] = field(default_factory=dict[str, Any]) - id: Optional[str] = None + +@dataclass(slots=True) +class Point: + id: str = "auto" + dense_embedding: DenseEmbedding | None = None + sparse_embedding: SparseEmbedding | None = None + metadata: Dict[str, Any] = field(default_factory=dict) def __post_init__(self): - # If id is not set, generate a random one - if self.id is None: - self.id = compute_mdhash_id(str(time.time_ns()), prefix="emb") + if self.id == "auto": + self.id = compute_mdhash_id(str(time.time_ns()), prefix="pnt") + + if self.dense_embedding is None and self.sparse_embedding is None: + raise ValueError("Point must contain at least one dense or sparse embedding") @dataclass(slots=True) From e2981d1e3268af8280b64c0dc0abcec21e9c2384 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 20 Apr 2026 15:27:25 +0700 Subject: [PATCH 04/42] Update vdb interface to support hybrid retrieval --- ragu/storage/base_storage.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/ragu/storage/base_storage.py b/ragu/storage/base_storage.py index 8c1c385..c9edc94 100644 --- a/ragu/storage/base_storage.py +++ b/ragu/storage/base_storage.py @@ -1,8 +1,19 @@ from dataclasses import dataclass from abc import ABC, abstractmethod -from typing import Dict, Generic, Iterable, List, Optional, Set, Tuple, TypeVar, Union - -from ragu.storage.types import Edge, Embedding, EmbeddingHit, Node +from typing import ( + Dict, + Generic, + Iterable, + List, + Optional, + Set, + Tuple, + TypeVar, + Union, + Any +) + +from ragu.storage.types import Edge, EmbeddingHit, Node, Point EdgeSpec = Tuple[str, str, Optional[str]] @@ -42,18 +53,17 @@ class BaseVectorStorage(BaseStorage, ABC): """ @abstractmethod - async def query(self, vector: Embedding, top_k: int) -> List[EmbeddingHit]: + async def query(self, point: Point, **kwargs) -> List[EmbeddingHit]: """ Retrieve top-k nearest items for a batch of embedding vectors. - :param vector: Query embedding vector. - :param top_k: Maximum number of results to return per query vector. + :param point: Query embedding. :return: A list of query hits with distance score and metadata. """ ... @abstractmethod - async def upsert(self, data: List[Embedding]) -> None: + async def upsert(self, data: List[Point], **kwargs: Any) -> None: """ Insert or update embedding records. @@ -62,7 +72,7 @@ async def upsert(self, data: List[Embedding]) -> None: ... @abstractmethod - async def delete(self, ids: List[str]) -> None: + async def delete(self, ids: List[str], **kwargs) -> None: """ Delete records by IDs. From f239ebea75c46a82fef32553e89163ca9f9228f5 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 20 Apr 2026 15:27:42 +0700 Subject: [PATCH 05/42] Update init --- ragu/storage/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ragu/storage/__init__.py b/ragu/storage/__init__.py index b5a1867..90b43b0 100644 --- a/ragu/storage/__init__.py +++ b/ragu/storage/__init__.py @@ -1,8 +1,8 @@ -from ragu.storage.types import Edge, Embedding, EmbeddingHit, Node +from ragu.storage.types import Node, Edge, Point, EmbeddingHit __all__ = [ 'Node', 'Edge', - 'Embedding', + 'Point', 'EmbeddingHit', ] \ No newline at end of file From 2e43563f036dee472e750bc38c416cce0977197d Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 20 Apr 2026 15:28:04 +0700 Subject: [PATCH 06/42] Update base nanovdb adapter --- ragu/storage/vdb_storage_adapters/nano_vdb.py | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/ragu/storage/vdb_storage_adapters/nano_vdb.py b/ragu/storage/vdb_storage_adapters/nano_vdb.py index 2ddf4d5..11cb168 100644 --- a/ragu/storage/vdb_storage_adapters/nano_vdb.py +++ b/ragu/storage/vdb_storage_adapters/nano_vdb.py @@ -6,11 +6,12 @@ import numpy as np from nano_vectordb import NanoVectorDB # pyright: ignore[reportMissingTypeStubs] +from nano_vectordb.dbs import Data from ragu.common.global_parameters import Settings from ragu.common.logger import logger from ragu.storage.base_storage import BaseVectorStorage -from ragu.storage.types import Embedding, EmbeddingHit +from ragu.storage.types import Point, EmbeddingHit class NanoVectorDBStorage(BaseVectorStorage): @@ -50,7 +51,7 @@ def __init__( ) @override - async def upsert(self, data: List[Embedding]): # should not return, see base class + async def upsert(self, data: List[Point], **kwargs) -> None: """ Insert or update a batch of embeddings in the database. @@ -61,44 +62,46 @@ async def upsert(self, data: List[Embedding]): # should not return, see base cl logger.warning("Attempted to insert empty data into vector DB.") return - valid_data: List[dict[str, Any]] = [] - skipped = 0 + if any([item.sparse_embedding is not None for item in data]): + logger.warning(f"NanoVDB does not support sparse embeddings. Ignoring.") + points: List[Data] = [] for embedding in data: - assert embedding.vector is not None # should not happen according to typing - # if embedding.vector is None: - # skipped += 1 - # continue - - item: dict[str, Any] = { + item: Data = { "__id__": embedding.id, - "__vector__": np.array(embedding.vector), + "__vector__": np.array(embedding.dense_embedding), **embedding.metadata, } - valid_data.append(item) - - if skipped: - logger.warning(f"Skipped {skipped} items with missing embeddings.") + points.append(item) - if not valid_data: + if not points: return - self._client.upsert(datas=valid_data) # type: ignore + self._client.upsert(datas=points) @override - async def query(self, vector: Embedding, top_k: int = 5) -> List[EmbeddingHit]: + async def query( + self, + point: Point, + **kwargs: Any + ) -> List[EmbeddingHit]: """ Search for the most similar documents in the vector database. Performs a cosine similarity search against all stored vectors, returning the top ``k`` results exceeding the similarity threshold. - :param vector: Query embedding payload. - :param top_k: Number of nearest neighbors to return. + :param point: Query embedding payload. + :param kwargs: + top_k: Number of nearest neighbors to return. :return: List of matched records and their distances. """ + if point.dense_embedding is None: + raise ValueError("Empty dense embedding payload.") + + top_k: int = kwargs.pop("top_k", 20) results = self._client.query( # type: ignore - query=np.array(vector.vector), + query=np.array(point.dense_embedding), top_k=top_k, better_than_threshold=self.cosine_threshold ) @@ -111,8 +114,8 @@ async def query(self, vector: Embedding, top_k: int = 5) -> List[EmbeddingHit]: } hits.append( EmbeddingHit( - id=result["__id__"], # type: ignore - distance=float(result["__metrics__"]), # type: ignore + id=result["__id__"], + distance=float(result["__metrics__"]), metadata=metadata, ) ) @@ -131,7 +134,7 @@ async def query_done_callback(self): pass @override - async def delete(self, ids: List[str]) -> None: + async def delete(self, ids: List[str], **kwargs: Any) -> None: """ Delete embeddings by their IDs from the vector database. From 4e395abbcb4cf410042061008803254e8fd41e64 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 20 Apr 2026 15:30:06 +0700 Subject: [PATCH 07/42] Add hybrid retrieval --- ragu/graph/index.py | 283 ++++++++++++++++------------------ ragu/graph/knowledge_graph.py | 98 ++++++------ 2 files changed, 184 insertions(+), 197 deletions(-) diff --git a/ragu/graph/index.py b/ragu/graph/index.py index 353b86e..08e6b93 100644 --- a/ragu/graph/index.py +++ b/ragu/graph/index.py @@ -4,18 +4,9 @@ import re from collections import defaultdict from dataclasses import asdict, dataclass, field -from typing import ( - Any, - Dict, - Iterable, - List, - Optional, - Set, - Tuple, - Type, - cast -) +from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Type, cast +import numpy as np from ragu.chunker.types import Chunk from ragu.common.global_parameters import DEFAULT_FILENAMES from ragu.common.global_parameters import Settings @@ -27,6 +18,7 @@ CommunitySummary ) from ragu.models.embedder import Embedder +from ragu.models.sparse_embedder import SparseEmbedder from ragu.storage.base_storage import ( BaseKVStorage, BaseVectorStorage, @@ -35,7 +27,7 @@ ) from ragu.storage.graph_storage_adapters.networkx_adapter import NetworkXStorage from ragu.storage.kv_storage_adapters.json_storage import JsonKVStorage -from ragu.storage.types import Embedding +from ragu.storage.types import Point from ragu.storage.vdb_storage_adapters.nano_vdb import NanoVectorDBStorage @@ -74,8 +66,9 @@ class Index: def __init__( self, - embedder: Embedder, # cannot be optional, since calls .dim in __init__ arguments: StorageArguments, + embedder: Embedder | None = None, + sparse_embedder: SparseEmbedder | None = None, ): """ Initialize storage backends and in-memory reverse indexes. @@ -87,6 +80,7 @@ def __init__( storage_folder: str = Settings.storage_folder self.embedder = embedder + self.sparse_embedder = sparse_embedder # Reverse indexes for cascade operations self._chunk_to_entities: Dict[str, Set[str]] = defaultdict(set) @@ -133,28 +127,29 @@ def __init__( self.community_summary_kv_storage = arguments.kv_storage_type(**summary_kv_kwargs) self.community_kv_storage = arguments.kv_storage_type(**community_kv_kwargs) - embedding_dim_from_embedder = embedder.dim + if self.embedder: + embedding_dim_from_embedder = embedder.dim - dimensions_from_kwargs = [ - storage_kwargs.get("embedding_dim") for storage_kwargs in [ - entity_vdb_kwargs, - relation_vdb_kwargs, - chunk_vdb_kwargs - ] if storage_kwargs.get("embedding_dim")] + dimensions_from_kwargs = [ + storage_kwargs.get("embedding_dim") for storage_kwargs in [ + entity_vdb_kwargs, + relation_vdb_kwargs, + chunk_vdb_kwargs + ] if storage_kwargs.get("embedding_dim")] - number_of_dimensions = len(dimensions_from_kwargs) - if number_of_dimensions > 1: - raise ValueError(f"Dimension mismatch in vdb kwargs: {dimensions_from_kwargs}") - if number_of_dimensions == 1: - if dimensions_from_kwargs[0] != embedding_dim_from_embedder: - raise ValueError(f"Dimension mismatch in vdb kwargs and embedder setup: " - f"{dimensions_from_kwargs[0]} and {embedding_dim_from_embedder}") + number_of_dimensions = len(dimensions_from_kwargs) + if number_of_dimensions > 1: + raise ValueError(f"Dimension mismatch in vdb kwargs: {dimensions_from_kwargs}") + if number_of_dimensions == 1: + if dimensions_from_kwargs[0] != embedding_dim_from_embedder: + raise ValueError(f"Dimension mismatch in vdb kwargs and embedder setup: " + f"{dimensions_from_kwargs[0]} and {embedding_dim_from_embedder}") - resolved_dim = embedding_dim_from_embedder + resolved_dim = embedding_dim_from_embedder - entity_vdb_kwargs["embedding_dim"] = resolved_dim - relation_vdb_kwargs["embedding_dim"] = resolved_dim - chunk_vdb_kwargs["embedding_dim"] = resolved_dim + entity_vdb_kwargs["embedding_dim"] = resolved_dim + relation_vdb_kwargs["embedding_dim"] = resolved_dim + chunk_vdb_kwargs["embedding_dim"] = resolved_dim # Vector storages self.entity_vector_db = arguments.vdb_storage_type(**entity_vdb_kwargs) @@ -178,6 +173,8 @@ async def insert_entities(self, entities: List[Entity]) -> "Index": :param entities: Entities to insert. :return: Self for method chaining. """ + assert self.embedder + if not entities: return self @@ -206,15 +203,28 @@ async def insert_entities(self, entities: List[Entity]) -> "Index": entities_to_insert.extend(incoming_group) await self.graph_backend.upsert_nodes(entities_to_insert) - vdb_data = { - e.id: { - "entity_name": e.entity_name, - "content": f"{e.entity_name} - {e.description}", - } - for e in entities_to_insert - } - embeddings = await self._build_embeddings(vdb_data, tqdm_title="Entities vectorization") - await self.entity_vector_db.upsert(embeddings) + + dense_embeddings = await self.embedder.batch_embed_text( + [f"{e.entity_name} - {e.description}" for e in entities_to_insert], + desc="Entities vectorization", + ) + sparse_embeddings = self.sparse_embedder.embed_document( + [f"{e.entity_name} - {e.description}" for e in entities_to_insert] + ) if self.sparse_embedder else [None for _ in entities_to_insert] + + vdb_data = [ + Point( + id=entity.id, + dense_embedding=np.array(dense), + sparse_embedding=sparse, + metadata={ + "entity_name": entity.entity_name, + "content": f"{entity.entity_name} - {entity.description}", + } + ) for entity, dense, sparse in zip(entities_to_insert, dense_embeddings, sparse_embeddings) + ] + + await self.entity_vector_db.upsert(vdb_data) await self.graph_backend.index_done_callback() await self.entity_vector_db.index_done_callback() @@ -232,6 +242,8 @@ async def update_entities(self, entities: List[Entity]) -> "Index": :return: Self for method chaining. :raises ValueError: If entity IDs are missing/duplicated in request or absent in storage. """ + assert self.embedder + if not entities: return self @@ -253,15 +265,26 @@ async def update_entities(self, entities: List[Entity]) -> "Index": entities_to_update = [group[0] for group in incoming_by_id.values()] await self.graph_backend.upsert_nodes(entities_to_update) - vdb_data = { - e.id: { - "entity_name": e.entity_name, - "content": f"{e.entity_name} - {e.description}", - } - for e in entities_to_update - } - embeddings = await self._build_embeddings(vdb_data, tqdm_title="Entities vectorization") - await self.entity_vector_db.upsert(embeddings) + dense_embeddings = await self.embedder.batch_embed_text( + [f"{e.entity_name} - {e.description}" for e in entities_to_update], + desc="Entities vectorization", + ) + sparse_embeddings = self.sparse_embedder.embed_document( + [f"{e.entity_name} - {e.description}" for e in entities_to_update] + ) if self.sparse_embedder else [None for _ in entities_to_update] + + vdb_data = [ + Point( + id=entity.id, + dense_embedding=np.array(dense), + sparse_embedding=sparse, + metadata={ + "entity_name": entity.entity_name, + "content": f"{entity.entity_name} - {entity.description}", + } + ) for entity, dense, sparse in zip(entities_to_update, dense_embeddings, sparse_embeddings) + ] + await self.entity_vector_db.upsert(vdb_data, sparse_data=sparse_embeddings) await self.graph_backend.index_done_callback() await self.entity_vector_db.index_done_callback() @@ -283,6 +306,8 @@ async def insert_relations(self, relations: List[Relation]) -> "Index": :return: Self for method chaining. :raises ValueError: If referenced entities don't exist. """ + assert self.embedder + if not relations: return self @@ -321,18 +346,29 @@ async def insert_relations(self, relations: List[Relation]) -> "Index": await self.graph_backend.upsert_edges(relations_to_insert) - vdb_data = { - r.id: { - "subject": r.subject_id, - "object": r.object_id, - "content": r.description, - } - for r in relations_to_insert - } if existing_relation_ids: await self.relation_vector_db.delete(existing_relation_ids) - embeddings = await self._build_embeddings(vdb_data, tqdm_title="Relations vectorization") - await self.relation_vector_db.upsert(embeddings) + + dense_embeddings = await self.embedder.batch_embed_text( + [r.description for r in relations_to_insert], + desc="Relations vectorization", + ) + sparse_embeddings = self.sparse_embedder.embed_document( + [r.description for r in relations_to_insert] + ) if self.sparse_embedder else [None for _ in relations_to_insert] + + vdb_data = [ + Point( + id=relation.id, + dense_embedding=np.array(dense), + sparse_embedding=sparse, + metadata={ + "content": relation.description, + } + ) for relation, dense, sparse in zip(relations_to_insert, dense_embeddings, sparse_embeddings) + ] + + await self.relation_vector_db.upsert(vdb_data) await self.graph_backend.index_done_callback() await self.relation_vector_db.index_done_callback() @@ -354,6 +390,8 @@ async def update_relations(self, relations: List[Relation]) -> "Index": :raises ValueError: If relation IDs are missing/duplicated in request, IDs are absent in storage, or referenced entities don't exist. """ + assert self.embedder + if not relations: return self @@ -387,17 +425,24 @@ async def update_relations(self, relations: List[Relation]) -> "Index": await self.graph_backend.upsert_edges(relations_to_update) - vdb_data = { - r.id: { - "subject": r.subject_id, - "object": r.object_id, - "content": r.description, - } - for r in relations_to_update - } - await self.relation_vector_db.delete(relation_ids) - embeddings = await self._build_embeddings(vdb_data, tqdm_title="Relations vectorization") - await self.relation_vector_db.upsert(embeddings) + dense_embeddings = await self.embedder.batch_embed_text( + [r.description for r in relations_to_update], + desc="Relations vectorization", + ) + sparse_embeddings = self.sparse_embedder.embed_document( + [r.description for r in relations_to_update] + ) if self.sparse_embedder else [None for _ in relations_to_update] + + vdb_data = [ + Point( + id=relation.id, + dense_embedding=np.array(dense), + sparse_embedding=sparse, + metadata={ + "content": relation.description, + } + ) for relation, dense, sparse in zip(relations_to_update, dense_embeddings, sparse_embeddings) + ] await self.graph_backend.index_done_callback() await self.relation_vector_db.index_done_callback() @@ -414,6 +459,8 @@ async def upsert_chunks(self, chunks: List[Chunk]) -> "Index": :param chunks: Chunks to upsert. :return: Self for method chaining. """ + assert self.embedder + if not chunks: return self @@ -426,12 +473,21 @@ async def upsert_chunks(self, chunks: List[Chunk]) -> "Index": await self.chunks_kv_storage.upsert(kv_data) - vdb_data = { - c.id: {"content": c.content, "doc_id": c.doc_id} - for c in chunks - } - embeddings = await self._build_embeddings(vdb_data, tqdm_title="Chunks vectorization") - await self.chunk_vector_db.upsert(embeddings) + dense_embeddings = await self.embedder.batch_embed_text( + [c.content for c in chunks], + desc="Chunks vectorization" + ) + sparse_embeddings = self.sparse_embedder.embed_document([c.content for c in chunks]) \ + if self.sparse_embedder else [None for _ in chunks] + + vdb_data = [Point( + id=c.id, + dense_embedding=np.array(dense), + sparse_embedding=sparse, + metadata={"content": c.content, "doc_id": c.doc_id} + ) for c, dense, sparse in zip(chunks, dense_embeddings, sparse_embeddings)] + + await self.chunk_vector_db.upsert(vdb_data) await self.chunk_vector_db.index_done_callback() await self.chunks_kv_storage.index_done_callback() @@ -764,77 +820,6 @@ async def get_communities(self, community_ids: List[str]) -> List[Optional[Commu return communities - async def query_entities(self, query: str, top_k: int = 20) -> List[Entity]: - """ - Search for entities using semantic similarity. - - :param query: Search query text. - :param top_k: Number of results to return. - :return: Matching entities. - """ - query_vector = await self.embedder.embed_text(query) - - results = await self.entity_vector_db.query(Embedding(vector=query_vector), top_k=top_k) - entity_ids = [r.id for r in results] - entities = await self.get_entities(entity_ids) - return [e for e in entities if e is not None] - - async def query_relations(self, query: str, top_k: int = 20) -> List[Relation]: - """ - Search for relations using semantic similarity. - - :param query: Search query text. - :param top_k: Number of results to return. - :return: Matching relations. - """ - query_vector = await self.embedder.embed_text(query) - - results = await self.relation_vector_db.query(Embedding(vector=query_vector), top_k=top_k) - edge_specs: List[EdgeSpec] = [ - ( - str(r.metadata.get("subject")), - str(r.metadata.get("object")), - r.id, - ) - for r in results - if r.metadata.get("subject") and r.metadata.get("object") - ] - if not edge_specs: - return [] - relations = await self.get_relations(edge_specs) - return [r for r in relations if r is not None] - - async def _build_embeddings( - self, - payloads: Dict[str, Dict[str, Any]], - tqdm_title: Optional[str]="Vectorization" - ) -> List[Embedding]: - """ - Convert text payloads to vector embeddings. - - :param payloads: Mapping ``id -> payload`` where payload contains ``content``. - :return: Embeddings ready for vector storage upsert. - """ - if not self.embedder: - raise ValueError("Embedder is required for vector operations.") - if not payloads: - return [] - - payload_items = list(payloads.items()) - contents: List[str] = [str(payload.get("content", "")) for _, payload in payload_items] - vectors = await self.embedder.batch_embed_text(contents, desc=tqdm_title) - - embeddings: List[Embedding] = [] - for (record_id, payload), vector in zip(payload_items, vectors): - metadata = {key: value for key, value in payload.items() if key != "content"} - embeddings.append( - Embedding( - id=record_id, - vector=vector, - metadata=metadata, - ) - ) - return embeddings async def _validate_relation_endpoints_exist(self, relations: List[Relation]) -> None: """ @@ -1074,8 +1059,8 @@ async def _rebuild_reverse_indexes(self) -> None: self._chunk_to_entities.clear() self._chunk_to_relations.clear() - all_entities = await self.graph_backend.get_all_nodes() - all_relations = await self.graph_backend.get_all_edges() + all_entities: List[Entity] = await self.graph_backend.get_all_nodes() + all_relations: List[Relation] = await self.graph_backend.get_all_edges() await self._update_reverse_indexes( entities=all_entities, diff --git a/ragu/graph/knowledge_graph.py b/ragu/graph/knowledge_graph.py index 2462884..bbb4c33 100644 --- a/ragu/graph/knowledge_graph.py +++ b/ragu/graph/knowledge_graph.py @@ -14,6 +14,7 @@ ) from ragu.graph.types import Entity, Relation, CommunitySummary from ragu.models.embedder import Embedder +from ragu.models.sparse_embedder import SparseEmbedder from ragu.models.llm import LLM from ragu.graph.index import Index, StorageArguments from ragu.triplet.base_artifact_extractor import BaseArtifactExtractor @@ -38,6 +39,7 @@ def __init__( self, llm: Optional[LLM], embedder: Embedder, + sparse_embedder: Optional[SparseEmbedder] = None, chunker: Optional[BaseChunker] = None, artifact_extractor: Optional[BaseArtifactExtractor] = None, builder_settings: Optional[BuilderArguments] = None, @@ -50,6 +52,7 @@ def __init__( :param llm: LLM client used by extraction and summarization modules. :param embedder: Embedder used by vector storage and optional clustering. + :param sparse_embedder: Optional sparse embedder used for hybrid retrieval. :param chunker: Optional chunker used to split input documents. :param artifact_extractor: Optional entity/relation extractor. :param builder_settings: Optional graph builder settings. @@ -60,25 +63,28 @@ def __init__( self.builder_settings = builder_settings or BuilderArguments() self.storage_settings = storage_settings or StorageArguments() self.language = language or Settings.language + self.embedder = embedder + self.sparse_embedder = sparse_embedder - if not additional_modules: - additional_modules = [] + what_to_add = additional_modules if additional_modules else [] if self.builder_settings.remove_isolated_nodes: - additional_modules.append(RemoveIsolatedNodes()) + what_to_add.append(RemoveIsolatedNodes()) + # Build graph self.pipeline = InMemoryGraphBuilder( llm=llm, chunker=chunker, artifact_extractor=artifact_extractor, build_parameters=self.builder_settings, embedder=embedder, - additional_pipeline=additional_modules, + additional_pipeline=what_to_add, language=self.language, ) - + # Store graph self.index = Index( embedder=embedder, + sparse_embedder=sparse_embedder, arguments=self.storage_settings, ) @@ -119,8 +125,7 @@ async def build_from_docs(self, docs: List[str]) -> "KnowledgeGraph": communities, summaries, ) - - + if not is_vector_only: await self.index.insert_entities(entities) await self.index.insert_relations(relations) @@ -313,47 +318,6 @@ async def update_summary(self, summary_id: str, new_summary: CommunitySummary) - await self.index.upsert_summaries([new_summary]) return self - async def find_similar_entities(self, entity: Entity, top_k: int = 10) -> List[Entity]: - """ - Find entities semantically similar to the given entity. - - :param entity: Reference entity to search against. - :param top_k: Maximum number of results. - :return: Similar entities ordered by relevance. - """ - query = f"{entity.entity_name} - {entity.description}" - return await self.index.query_entities(query, top_k=top_k) - - async def find_similar_relations(self, relation: Relation, top_k: int = 10) -> List[Relation]: - """ - Find relations semantically similar to the given relation. - - :param relation: Reference relation to search against. - :param top_k: Maximum number of results. - :return: Similar relations ordered by relevance. - """ - return await self.index.query_relations(relation.description, top_k=top_k) - - async def find_similar_entity_by_query(self, query: str, top_k: int = 10) -> List[Entity]: - """ - Find entities matching a free-text query. - - :param query: Search query text. - :param top_k: Maximum number of results. - :return: Matching entities ordered by relevance. - """ - return await self.index.query_entities(query, top_k=top_k) - - async def find_similar_relation_by_query(self, query: str, top_k: int = 10) -> List[Relation]: - """ - Find relations matching a free-text query. - - :param query: Search query text. - :param top_k: Maximum number of results. - :return: Matching relations ordered by relevance. - """ - return await self.index.query_relations(query, top_k=top_k) - async def _deduplicate_chunks_by_id(self, chunks: List[Chunk]) -> List[Chunk]: """ Deduplicate chunks by ``chunk.id`` preserving original order. @@ -385,3 +349,41 @@ async def _deduplicate_chunks_by_id(self, chunks: List[Chunk]) -> List[Chunk]: ) return unique_chunks + + @property + async def reindex_community(self) -> "KnowledgeGraph": + if not self.pipeline.community_summarizer: + raise ValueError() + + await self.index.community_kv_storage.drop() + await self.index.community_summary_kv_storage.drop() + + entities = await self.index.graph_backend.get_all_nodes() + relations = await self.index.graph_backend.get_all_edges() + + entities = list( + map( + lambda node: Entity( + entity_name=node.entity_name, + description=node.description, + entity_type=node.entity_type, + source_chunk_id=node.source_chunk_id, + documents_id=node.documents_id, + clusters=[]), + entities + ) + ) + + communities = await self.pipeline.cluster_graph(entities=entities, relations=relations) + summaries = await self.pipeline.community_summarizer.summarize(communities=communities) + + await self.index.upsert_communities(communities) + await self.index.upsert_summaries(summaries) + + return self + + async def reindex_descriptions(self)-> "KnowledgeGraph": + ... + + async def reindex_graph(self) -> "KnowledgeGraph": + return await (await self.reindex_descriptions()).reindex_community From 8b0b2b55c96e007f7ef861352005ec3f059333c0 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 20 Apr 2026 15:30:36 +0700 Subject: [PATCH 08/42] Add retriever --- ragu/graph/graph_retrieve_backend.py | 130 +++++++++++++++++++++++++++ ragu/search_engine/local_search.py | 22 ++--- ragu/search_engine/naive_search.py | 20 +++-- 3 files changed, 154 insertions(+), 18 deletions(-) create mode 100644 ragu/graph/graph_retrieve_backend.py diff --git a/ragu/graph/graph_retrieve_backend.py b/ragu/graph/graph_retrieve_backend.py new file mode 100644 index 0000000..f22142e --- /dev/null +++ b/ragu/graph/graph_retrieve_backend.py @@ -0,0 +1,130 @@ +from typing import List + +from ragu.graph.types import Entity, Relation +from ragu.models.embedder import Embedder +from ragu.models.scorer import Scorer +from ragu.models.sparse_embedder import SparseEmbedder +from ragu.storage.base_storage import EdgeSpec +from ragu.storage.types import Point, EmbeddingHit, SparseEmbedding, DenseEmbedding + +from ragu.graph.knowledge_graph import KnowledgeGraph + + +class GraphRetriever: + """ + Query-time retrieval helper for graph vector search. + """ + def __init__( + self, + knowledge_graph: KnowledgeGraph, + embedder: Embedder, + sparse_embedder: SparseEmbedder | None = None, + reranker: Scorer | None = None, + ) -> None: + """ + Initialize a retriever bound to an existing knowledge graph. + + :param knowledge_graph: Graph container exposing storage backends. + :param embedder: Dense embedder used for query encoding. + :param sparse_embedder: Optional sparse embedder for hybrid retrieval. + :param reranker: Optional reranker reserved for post-retrieval scoring. + """ + self.knowledge_graph = knowledge_graph + self.embedder = embedder + self.sparse_embedder = sparse_embedder + self.reranker = reranker + + async def query_entities(self, query: str, top_k: int = 20) -> List[Entity]: + """ + Find entities matching a free-text query. + + :param query: Search query text. + :param top_k: Maximum number of results. + :return: Matching entities ordered by relevance. + """ + point = await self.build_query_vectors(query) + results = await self.knowledge_graph.index.entity_vector_db.query( + point, + top_k=top_k, + ) + entity_ids = [result.id for result in results] + entities = await self.knowledge_graph.index.get_entities(entity_ids) + return [entity for entity in entities if entity is not None] + + async def query_relations(self, query: str, top_k: int = 20) -> List[Relation]: + """ + Find relations matching a free-text query. + + :param query: Search query text. + :param top_k: Maximum number of results. + :return: Matching relations ordered by relevance. + """ + point = await self.build_query_vectors(query) + results = await self.knowledge_graph.index.relation_vector_db.query( + point, + top_k=top_k, + ) + edge_specs: List[EdgeSpec] = [ + ( + str(result.metadata.get("subject")), + str(result.metadata.get("object")), + result.id, + ) + for result in results + if result.metadata.get("subject") and result.metadata.get("object") + ] + if not edge_specs: + return [] + relations = await self.knowledge_graph.index.get_relations(edge_specs) + return [relation for relation in relations if relation is not None] + + async def find_similar_entities(self, entity: Entity, top_k: int = 10) -> List[Entity]: + """ + Find entities semantically similar to the given entity. + + :param entity: Reference entity to search against. + :param top_k: Maximum number of results. + :return: Similar entities ordered by relevance. + """ + query = f"{entity.entity_name} - {entity.description}" + return await self.query_entities(query, top_k=top_k) + + async def find_similar_relations(self, relation: Relation, top_k: int = 10) -> List[Relation]: + """ + Find relations semantically similar to the given relation. + + :param relation: Reference relation to search against. + :param top_k: Maximum number of results. + :return: Similar relations ordered by relevance. + """ + return await self.query_relations(relation.description, top_k=top_k) + + async def query_chunk_hits(self, query: str, top_k: int = 20) -> List[EmbeddingHit]: + """ + Search chunk vectors and return raw embedding hits. + + :param query: Search query text. + :param top_k: Maximum number of hits. + :return: Ranked chunk hits with metadata. + """ + point = await self.build_query_vectors(query) + return await self.knowledge_graph.index.chunk_vector_db.query( + point=point, + top_k=top_k, + ) + + async def build_query_vectors(self, query: str) -> Point: + """ + Encode a query into dense and optional sparse vectors. + + :param query: Search query text. + :return: Point carrying query-time vector payloads. + """ + dense_query: DenseEmbedding = await self.embedder.embed_text(query) # type: ignore + sparse_query: SparseEmbedding | None = None + if self.sparse_embedder is not None: + sparse_vectors = self.sparse_embedder.embed_query([query]) + if len(sparse_vectors) != 1: + raise ValueError("Sparse query embedder must return exactly one vector for one query") + sparse_query = sparse_vectors[0] + return Point(dense_embedding=dense_query, sparse_embedding=sparse_query) diff --git a/ragu/search_engine/local_search.py b/ragu/search_engine/local_search.py index b424fd1..c242aa6 100644 --- a/ragu/search_engine/local_search.py +++ b/ragu/search_engine/local_search.py @@ -5,10 +5,12 @@ from pydantic import BaseModel from ragu.common.global_parameters import Settings +from ragu.graph.graph_retrieve_backend import GraphRetriever from ragu.graph.knowledge_graph import KnowledgeGraph from ragu.models.embedder import Embedder from ragu.models.llm import LLM from ragu.models.scorer import Scorer +from ragu.models.sparse_embedder import SparseEmbedder from ragu.search_engine.base_engine import BaseEngine from ragu.search_engine.search_functional import ( _find_most_related_edges_from_entities, @@ -18,7 +20,6 @@ _rerank_items, ) from ragu.search_engine.types import LocalSearchResult -from ragu.storage import Embedding from ragu.utils.token_truncation import TokenTruncation from ragu.common.prompts.prompt_storage import RAGUInstruction @@ -44,6 +45,7 @@ def __init__( llm: LLM, knowledge_graph: KnowledgeGraph, embedder: Embedder, + sparse_embedder: SparseEmbedder | None = None, reranker: Scorer | None = None, max_context_length: int = 30_000, tokenizer_backend: str = "tiktoken", @@ -57,7 +59,8 @@ def __init__( :param llm: LLM used to generate the final answer. :param knowledge_graph: Knowledge graph used for entity and relation retrieval. - :param embedder: Embedding model used for similarity search. + :param embedder: Dense embedder used for retrieval queries. + :param sparse_embedder: Optional sparse embedder used for hybrid retrieval queries. :param reranker: Optional reranker used to reorder retrieved context sections. :param max_context_length: Max tokens allowed for the final context (after truncation). :param tokenizer_backend: Tokenizer backend used for token counting/truncation. @@ -74,7 +77,12 @@ def __init__( ) self.knowledge_graph = knowledge_graph - self.embedder = embedder + self.retriever = GraphRetriever( + knowledge_graph=knowledge_graph, + embedder=embedder, + sparse_embedder=sparse_embedder, + reranker=reranker, + ) self.reranker = reranker self.language = language if language else Settings.language @@ -87,13 +95,7 @@ async def a_search(self, query: str, top_k: int = 20, *args, **kwargs) -> LocalS :param top_k: Number of top entities to retrieve from the entity vector DB. :return: LocalSearchResult containing entities, relations, summaries, chunks, and document ids. """ - embedding = await self.embedder.embed_text(query) - embedding_hits = await self.knowledge_graph.index.entity_vector_db.query( - Embedding(embedding), - top_k=top_k, - ) - entities = await self.knowledge_graph.index.get_entities([hit.id for hit in embedding_hits]) - entities = [e for e in entities if e is not None] + entities = await self.retriever.query_entities(query, top_k=top_k) relations = await _find_most_related_edges_from_entities(entities, self.knowledge_graph) relations = [relation for relation in relations if relation is not None] diff --git a/ragu/search_engine/naive_search.py b/ragu/search_engine/naive_search.py index 5b79111..778a95c 100644 --- a/ragu/search_engine/naive_search.py +++ b/ragu/search_engine/naive_search.py @@ -4,13 +4,14 @@ from ragu.chunker.types import Chunk from ragu.common.global_parameters import Settings +from ragu.graph.graph_retrieve_backend import GraphRetriever from ragu.graph.knowledge_graph import KnowledgeGraph from ragu.models.embedder import Embedder from ragu.models.llm import LLM from ragu.models.scorer import Scorer +from ragu.models.sparse_embedder import SparseEmbedder from ragu.search_engine.base_engine import BaseEngine from ragu.search_engine.types import NaiveSearchResult -from ragu.storage import Embedding from ragu.utils.token_truncation import TokenTruncation from ragu.common.prompts.prompt_storage import RAGUInstruction @@ -30,6 +31,7 @@ def __init__( llm: LLM, knowledge_graph: KnowledgeGraph, embedder: Embedder, + sparse_embedder: SparseEmbedder | None = None, reranker: Optional[Scorer] = None, max_context_length: int = 30_000, tokenizer_backend: str = "tiktoken", @@ -43,7 +45,8 @@ def __init__( :param llm: LLM used to generate the final answer. :param knowledge_graph: Knowledge graph containing chunk vector DB and chunk KV storage. - :param embedder: Embedding model (kept for interface parity; retrieval uses graph index DBs). + :param embedder: Dense embedder used for retrieval queries. + :param sparse_embedder: Optional sparse embedder used for hybrid retrieval queries. :param reranker: Optional reranker used to improve ranking of retrieved chunks. :param max_context_length: Max tokens allowed for context after truncation. :param tokenizer_backend: Tokenizer backend used for token truncation. @@ -60,7 +63,12 @@ def __init__( ) self.graph = knowledge_graph - self.embedder = embedder + self.retriever = GraphRetriever( + knowledge_graph=knowledge_graph, + embedder=embedder, + sparse_embedder=sparse_embedder, + reranker=reranker, + ) self.reranker = reranker self.llm = llm self.language = language if language else Settings.language @@ -82,11 +90,7 @@ async def a_search( If None, keeps all reranked chunks. Used only when reranker is set. :return: NaiveSearchResult with retrieved chunks, scores, and document ids. """ - vectorized_query = await self.embedder.embed_text(query) - results = await self.graph.index.chunk_vector_db.query( - Embedding(vector=vectorized_query), - top_k=top_k - ) + results = await self.retriever.query_chunk_hits(query, top_k=top_k) if not results: return NaiveSearchResult(chunks=[], scores=[], documents_id=[]) From 4f0d4678279cbdc9eeb87a1c4925af78008c6ed8 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 20 Apr 2026 15:31:05 +0700 Subject: [PATCH 09/42] Fix double dot in entity descriptions --- ragu/graph/artifacts_summarizer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ragu/graph/artifacts_summarizer.py b/ragu/graph/artifacts_summarizer.py index ea8c44f..c5357cd 100644 --- a/ragu/graph/artifacts_summarizer.py +++ b/ragu/graph/artifacts_summarizer.py @@ -217,9 +217,9 @@ async def _summarize_by_cluster_if_needed(self, descriptions: List[str]) -> str: ) result_description.extend([r.content for r in result if r]) - return ". ".join(result_description) + return " ".join(result_description) - return ". ".join(descriptions) + return " ".join(descriptions) class RelationSummarizer(RaguGenerativeModule): From 444df804200fe32129a0252b7e432ba0cb5b8b61 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 20 Apr 2026 15:31:19 +0700 Subject: [PATCH 10/42] Update init --- ragu/graph/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ragu/graph/__init__.py b/ragu/graph/__init__.py index 9bc47e3..5ba8b2b 100644 --- a/ragu/graph/__init__.py +++ b/ragu/graph/__init__.py @@ -1,2 +1,2 @@ from ragu.graph.graph_builder_pipeline import InMemoryGraphBuilder, BuilderArguments - +from ragu.graph.graph_retrieve_backend import GraphRetriever From 8cb1c8804e6baf3a4c86320e488abc365fd2f0a5 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 20 Apr 2026 15:34:15 +0700 Subject: [PATCH 11/42] Add object size estimation and batch splitter --- ragu/utils/ragu_utils.py | 43 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/ragu/utils/ragu_utils.py b/ragu/utils/ragu_utils.py index 79875cd..b56a540 100755 --- a/ragu/utils/ragu_utils.py +++ b/ragu/utils/ragu_utils.py @@ -1,5 +1,6 @@ import asyncio import functools +import json import logging import time from collections.abc import Awaitable, Collection, MutableMapping @@ -7,7 +8,7 @@ from dataclasses import is_dataclass, asdict from hashlib import md5 from pathlib import Path -from typing import Any +from typing import Any, Iterable, Iterator from typing import Callable, TypeVar, cast from typing import List @@ -170,3 +171,43 @@ def serialize(obj: Any) -> Any: } return str(obj) + +def serialized_size(obj) -> int: + """ + Estimate size of object after JSON serialization (bytes). + """ + try: + return len(json.dumps(obj, ensure_ascii=False, separators=(",", ":")).encode("utf-8")) + except TypeError: + return len(str(obj).encode("utf-8")) + +T = TypeVar("T") +def split_on_batches_by_size( + objects: Iterable[T], + max_size_in_bytes: int, +) -> Iterator[List[T]]: + current_batch: list[T] = [] + current_size = 0 + + for obj in objects: + size = serialized_size(obj) + if size > max_size_in_bytes: + if current_batch: + yield current_batch + current_batch = [] + current_size = 0 + yield [obj] + continue + + if current_size + size > max_size_in_bytes: + if current_batch: + yield current_batch + current_batch = [obj] + current_size = size + else: + current_batch.append(obj) + current_size += size + + if current_batch: + yield current_batch + From 6373bb9c6757dba037c0dc9da7344efb8d839fb8 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 20 Apr 2026 15:35:00 +0700 Subject: [PATCH 12/42] Add text normalizers --- ragu/utils/text_normalize.py | 78 ++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 ragu/utils/text_normalize.py diff --git a/ragu/utils/text_normalize.py b/ragu/utils/text_normalize.py new file mode 100644 index 0000000..2c4e38d --- /dev/null +++ b/ragu/utils/text_normalize.py @@ -0,0 +1,78 @@ +import re +from abc import ABC, abstractmethod +from typing import List, Set +from typing_extensions import override + +import pymorphy3 + + +class BaseNormalizer(ABC): + @abstractmethod + def normalize(self, text: str) -> str: + ... + + def normalize_batch(self, texts: List[str]) -> List[str]: + return [self.normalize(t) for t in texts] + + +class PymorphyNormalizer(BaseNormalizer): + """ + Class that provide text normalization via pymorphy3 (for Russian language). + """ + + _word_re = re.compile(r"\w+", flags=re.UNICODE) + _DEFAULT_STOP_POS = {"PREP", "CONJ", "PRCL", "INTJ"} + + def __init__( + self, + lowercase: bool = True, + min_token_length: int = 2, + remove_numbers: bool = True, + stopwords: Set[str] | None = None, + stop_pos: Set[str] | None = None, + ) -> None: + self.lowercase = lowercase + self.min_token_length = min_token_length + self.remove_numbers = remove_numbers + self.stopwords = set(stopwords) if stopwords is not None else set() + self.stop_pos = set(stop_pos) if stop_pos is not None else self._DEFAULT_STOP_POS + + self._morph = pymorphy3.MorphAnalyzer() + + def _tokenize(self, text: str) -> List[str]: + if self.lowercase: + text = text.lower() + return self._word_re.findall(text) + + def _is_valid(self, token: str) -> bool: + if len(token) < self.min_token_length: + return False + if self.remove_numbers and token.isdigit(): + return False + return True + + @override + def normalize(self, text: str) -> str: + """ + Normalize text. + """ + result: List[str] = [] + + for token in self._tokenize(text): + if not self._is_valid(token): + continue + + maybe_parsed = self._morph.parse(token) + + # If we can't lemmatize word, return it as is. + if not maybe_parsed: + result.append(token) + continue + + parsed = maybe_parsed[0] + if parsed.tag.POS in self.stop_pos or parsed.normal_form in self.stopwords: + continue + + result.append(parsed.normal_form) + + return " ".join(result) \ No newline at end of file From a982c77d062fd756316f2fa6e16f2520d89e72ea Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 20 Apr 2026 15:35:32 +0700 Subject: [PATCH 13/42] Update imports and version --- ragu/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ragu/__init__.py b/ragu/__init__.py index 76a6689..9cd19d2 100644 --- a/ragu/__init__.py +++ b/ragu/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.0.1" +__version__ = "0.0.2" # Default chunkers from ragu.chunker import SimpleChunker, SmartSemanticChunker @@ -6,6 +6,7 @@ # Knowledge Graph and builders from ragu.graph.knowledge_graph import KnowledgeGraph from ragu.graph.graph_builder_pipeline import InMemoryGraphBuilder, BuilderArguments +from ragu.graph.graph_retrieve_backend import GraphRetriever from ragu.graph.index import StorageArguments # Global settings @@ -34,6 +35,7 @@ "KnowledgeGraph", "InMemoryGraphBuilder", "BuilderArguments", + "GraphRetriever", "StorageArguments", "LocalSearchEngine", "GlobalSearchEngine", From 74f0219fa434ddcd1881439b204425bdd9b8ff24 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 20 Apr 2026 15:35:51 +0700 Subject: [PATCH 14/42] Add sparse embedders --- ragu/models/sparse_embedder.py | 224 +++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 ragu/models/sparse_embedder.py diff --git a/ragu/models/sparse_embedder.py b/ragu/models/sparse_embedder.py new file mode 100644 index 0000000..e8017f8 --- /dev/null +++ b/ragu/models/sparse_embedder.py @@ -0,0 +1,224 @@ +import inspect +from abc import ABC, abstractmethod +from typing import Any, Callable, List, Iterable + +from fastembed import SparseTextEmbedding +from fastembed.sparse.bm42 import Bm42 as FastEmbedBM42 +from fastembed.sparse.bm25 import Bm25 as FastEmbedBM25 + +from ragu.common.global_parameters import Settings +from ragu.storage.types import SparseEmbedding +from ragu.utils.text_normalize import BaseNormalizer + + +class SparseEmbedder(ABC): + @abstractmethod + def embed_query(self, texts: List[str]) -> List[SparseEmbedding]: + ... + + @abstractmethod + def embed_document(self, texts: List[str]) -> List[SparseEmbedding]: + ... + + +class BM25(SparseEmbedder): + """ + Sparse embedder backed by FastEmbed BM25. + """ + + def __init__( + self, + model_name: str = "Qdrant/bm25", + cache_dir: str | None = None, + k: float = 1.2, + b: float = 0.75, + avg_len: float = 256.0, + language: str | None = None, + token_max_length: int = 40, + disable_stemmer: bool = False, + specific_model_path: str | None = None, + normalizer: BaseNormalizer | None = None, + **kwargs: Any, + ) -> None: + self.specific_model_path = specific_model_path + self.normalizer = normalizer + self.kwargs = dict(kwargs) + + if not disable_stemmer and normalizer: + raise ValueError(f"You cannot use custom normalizer along with default fastembed stemmer." + f" Set `disable_stemmer` to True or remove normalizer") + + self._model = FastEmbedBM25( + model_name=model_name, + cache_dir=cache_dir, + k=k, + b=b, + avg_len=avg_len, + language=language if language else Settings.language, + token_max_length=token_max_length, + disable_stemmer=disable_stemmer, + specific_model_path=specific_model_path, + **kwargs, + ) + + def embed_document(self, texts: List[str]) -> List[SparseEmbedding]: + if not texts: + return [] + normalized_texts = self.normalizer.normalize_batch(texts) if self.normalizer else texts + return [ + SparseEmbedding( + indices=embedding.indices.astype(int).tolist(), + values=embedding.values.astype(float).tolist(), + ) + for embedding in self._model.embed(normalized_texts) + ] + + def embed_query(self, texts: List[str]) -> List[SparseEmbedding]: + if not texts: + return [] + normalized_texts = self.normalizer.normalize_batch(texts) if self.normalizer else texts + return [ + SparseEmbedding( + indices=embedding.indices.astype(int).tolist(), + values=embedding.values.astype(float).tolist(), + ) + for embedding in self._model.query_embed(normalized_texts) + ] + + +class BM42(SparseEmbedder): + """ + Sparse embedder backed by FastEmbed BM42. + + BM42 is expected to be used with Qdrant sparse vectors configured with + `modifier=IDF`, just like FastEmbed BM25. + """ + + def __init__( + self, + model_name: str = "Qdrant/bm42-all-minilm-l6-v2-attentions", + cache_dir: str | None = None, + alpha: float = 0.5, + normalizer: BaseNormalizer | None = None, + batch_size: int = 32, + parallel: int | None = None, + threads: int | None = None, + providers: list[str | tuple[str, dict[Any, Any]]] | None = None, + cuda: bool | str = "auto", + device_ids: list[int] | None = None, + lazy_load: bool = False, + specific_model_path: str | None = None, + **kwargs: Any, + ) -> None: + if normalizer is not None: + raise ValueError("BM42 does not support a custom normalizer because FastEmbed BM42 " + "already applies its own tokenizer, stopword filtering, and stemming.") + + self.normalizer = normalizer + self.batch_size = batch_size + self.parallel = parallel + self.kwargs = dict(kwargs) + + self._model = FastEmbedBM42( + model_name=model_name, + cache_dir=cache_dir, + threads=threads, + providers=providers, + alpha=alpha, + cuda=cuda, + device_ids=device_ids, + lazy_load=lazy_load, + specific_model_path=specific_model_path, + **kwargs, + ) + + def embed_document(self, texts: List[str]) -> List[SparseEmbedding]: + if not texts: + return [] + + return [ + SparseEmbedding( + indices=embedding.indices.astype(int).tolist(), + values=embedding.values.astype(float).tolist(), + ) + for embedding in self._model.embed(texts, batch_size=self.batch_size, parallel=self.parallel) + ] + + def embed_query(self, texts: List[str]) -> List[SparseEmbedding]: + if not texts: + return [] + + return [ + SparseEmbedding( + indices=embedding.indices.astype(int).tolist(), + values=embedding.values.astype(float).tolist(), + ) + for embedding in self._model.query_embed(texts) + ] + + +class SPLADE(SparseEmbedder): + """ + Sparse embedder backed by FastEmbed SPLADE++. + + Notes: + - Default model is English-only: `prithivida/Splade_PP_en_v1`. + - Unlike BM25, SPLADE does not use Qdrant IDF modifier. + - In FastEmbed docs, SPLADE is used via `SparseTextEmbedding`. + """ + + def __init__( + self, + model_name_or_path: str = "prithivida/Splade_PP_en_v1", + cache_dir: str | None = None, + normalizer: BaseNormalizer | None = None, + batch_size: int = 32, + parallel: int = 4, + **kwargs: Any, + ) -> None: + self.normalizer = normalizer + self.parallel = parallel + self.batch_size = batch_size + self.kwargs = dict(kwargs) + + init_signature = inspect.signature(SparseTextEmbedding.__init__) + supported_kwargs = { + key: value + for key, value in kwargs.items() + if key in init_signature.parameters + } + + self._model = SparseTextEmbedding( + model_name=model_name_or_path, + cache_dir=cache_dir, + **supported_kwargs, + ) + + def embed_document(self, texts: List[str]) -> List[SparseEmbedding]: + if not texts: + return [] + + normalized_texts = self.normalizer.normalize_batch(texts) if self.normalizer else texts + + return [ + SparseEmbedding( + indices=embedding.indices.astype(int).tolist(), + values=embedding.values.astype(float).tolist(), + ) for embedding in self._model.embed(normalized_texts, batch_size=self.batch_size, parallel=self.parallel) + ] + + def embed_query(self, texts: List[str]) -> List[SparseEmbedding]: + if not texts: + return [] + + normalized_texts = self.normalizer.normalize_batch(texts) if self.normalizer else texts + + # Some FastEmbed models/classes expose query_embed, but SPLADE docs + # consistently show plain `.embed(...)`. So we safely fall back to it. + query_embed: Callable[[Iterable[str]], Iterable[Any]] = getattr(self._model, "query_embed", self._model.embed) + return [ + SparseEmbedding( + indices=embedding.indices.astype(int).tolist(), + values=embedding.values.astype(float).tolist(), + ) for embedding in query_embed(normalized_texts) + ] From edc326d9217805f53c1f1fb4332465ea9b623af8 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Wed, 22 Apr 2026 00:58:20 +0700 Subject: [PATCH 15/42] Add index consistency check --- ragu/graph/index.py | 218 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) diff --git a/ragu/graph/index.py b/ragu/graph/index.py index 08e6b93..0a90ed8 100644 --- a/ragu/graph/index.py +++ b/ragu/graph/index.py @@ -56,6 +56,56 @@ class StorageArguments: graph_storage_kwargs: Dict[str, Any] = field(default_factory=dict[str, Any]) +@dataclass(frozen=True) +class ConsistencyIssue: + """ + One consistency violation detected during graph storage audit. + + :param check: Stable machine-readable check identifier. + :param message: Short human-readable explanation of the violation. + :param details: Additional structured context for the violation. + """ + check: str + message: str + details: Dict[str, Any] = field(default_factory=dict[str, Any]) + + +@dataclass(frozen=True) +class ConsistencyReport: + """ + Result of :meth:`Index.check_consistency`. + + :param errors: Collected consistency violations. + """ + errors: List[ConsistencyIssue] = field(default_factory=list[ConsistencyIssue]) + + @property + def is_consistent(self) -> bool: + return len(self.errors) == 0 + + def to_text(self) -> str: + if self.is_consistent: + return "Graph consistency: OK\nNo consistency issues found." + + lines = [ + "Graph consistency: FAILED", + f"Issues found: {len(self.errors)}", + "", + ] + for issue in self.errors: + lines.append(f"- {issue.check}: {issue.message}") + for key, value in sorted(issue.details.items()): + if isinstance(value, list): + rendered_value = ", ".join(str(item) for item in value) if value else "-" + else: + rendered_value = str(value) + lines.append(f" {key}: {rendered_value}") + return "\n".join(lines) + + def __str__(self) -> str: + return self.to_text() + + class Index: """ Manages all storage operations for a knowledge graph. @@ -1139,3 +1189,171 @@ def _build_storage_kwargs( os.path.abspath(os.path.join(storage_folder, filename)), ) return kwargs + + async def check_consistency(self) -> ConsistencyReport: + """ + Audit cross-storage graph consistency and collect invariant violations. + + Checked invariants: relation endpoints exist as entities in a graph; + ``source_chunk_id`` values exist in chunk storage; community + entity/relation references exists in the graph; graph and chunk items + have vector representations; and every vector has a matching entity, + relation, or chunk endpoint. + + :returns: Structured consistency report. + """ + all_entities = [entity for entity in await self.graph_backend.get_all_nodes() if entity is not None] + all_relations = [relation for relation in await self.graph_backend.get_all_edges() if relation is not None] + + all_entity_ids_from_graph = {entity.id for entity in all_entities if entity.id} + all_relation_ids_from_graph = {relation.id for relation in all_relations if relation.id} + all_chunk_ids_from_storage = set(await self.chunks_kv_storage.all_keys()) + + errors: List[ConsistencyIssue] = [] + + referenced_chunk_ids = { + chunk_id + for entity in all_entities + for chunk_id in entity.source_chunk_id + } | { + chunk_id + for relation in all_relations + for chunk_id in relation.source_chunk_id + } + missing_chunk_ids = sorted(referenced_chunk_ids - all_chunk_ids_from_storage) + if missing_chunk_ids: + errors.append( + ConsistencyIssue( + check="source_chunk_references", + message="Entities or relations reference chunks missing from chunk storage.", + details={"missing_chunk_ids": missing_chunk_ids}, + ) + ) + + relation_endpoint_ids = { + endpoint_id + for relation in all_relations + for endpoint_id in (relation.subject_id, relation.object_id) + } + missing_relation_endpoint_ids = sorted(relation_endpoint_ids - all_entity_ids_from_graph) + if missing_relation_endpoint_ids: + missing_relation_endpoint_id_set = set(missing_relation_endpoint_ids) + affected_relation_ids = sorted([ + relation.id + for relation in all_relations + if relation.id and ( + relation.subject_id in missing_relation_endpoint_id_set + or relation.object_id in missing_relation_endpoint_id_set + ) + ]) + errors.append( + ConsistencyIssue( + check="relation_endpoints", + message="Relations reference entity endpoints that do not exist in the graph.", + details={ + "missing_entity_ids": missing_relation_endpoint_ids, + "relation_ids_with_empty_endpoints": affected_relation_ids, + }, + ) + ) + + community_ids = await self.community_kv_storage.all_keys() + community_rows = await self.community_kv_storage.get_by_ids(community_ids) if community_ids else [] + broken_community_ids: Set[str] = set() + missing_community_entity_ids: Set[str] = set() + missing_community_relation_ids: Set[str] = set() + for community_id, community in zip(community_ids, community_rows): + if community is None: + broken_community_ids.add(community_id) + continue + + missing_entities = set(community.get("entity_ids", [])) - all_entity_ids_from_graph + missing_relations = set(community.get("relation_ids", [])) - all_relation_ids_from_graph + if missing_entities or missing_relations: + broken_community_ids.add(community_id) + missing_community_entity_ids.update(missing_entities) + missing_community_relation_ids.update(missing_relations) + + if broken_community_ids: + errors.append( + ConsistencyIssue( + check="community_references", + message="Communities reference entities or relations missing from the graph.", + details={ + "community_ids": sorted(broken_community_ids), + "missing_entity_ids": sorted(missing_community_entity_ids), + "missing_relation_ids": sorted(missing_community_relation_ids), + }, + ) + ) + + entity_vector_ids = set(await self.entity_vector_db.get_all_ids()) + missing_entity_vector_ids = sorted(all_entity_ids_from_graph - entity_vector_ids) + if missing_entity_vector_ids: + errors.append( + ConsistencyIssue( + check="entity_vector_representations", + message="Graph entities exist without matching entity vectors.", + details={"missing_vector_ids": missing_entity_vector_ids}, + ) + ) + + orphan_entity_vector_ids = sorted(entity_vector_ids - all_entity_ids_from_graph) + if orphan_entity_vector_ids: + errors.append( + ConsistencyIssue( + check="entity_vector_endpoints", + message="Entity vectors exist without matching graph entities.", + details={"orphan_vector_ids": orphan_entity_vector_ids}, + ) + ) + + relation_vector_ids = set(await self.relation_vector_db.get_all_ids()) + missing_relation_vector_ids = sorted(all_relation_ids_from_graph - relation_vector_ids) + if missing_relation_vector_ids: + errors.append( + ConsistencyIssue( + check="relation_vector_representations", + message="Graph relations exist without matching relation vectors.", + details={"missing_vector_ids": missing_relation_vector_ids}, + ) + ) + + orphan_relation_vector_ids = sorted(relation_vector_ids - all_relation_ids_from_graph) + if orphan_relation_vector_ids: + errors.append( + ConsistencyIssue( + check="relation_vector_endpoints", + message="Relation vectors exist without matching graph relations.", + details={"orphan_vector_ids": orphan_relation_vector_ids}, + ) + ) + + chunks_vdb_ids = await self.chunk_vector_db.get_all_ids() + + # Empty ids means that we didn't vectorize chunks at all, that is valid if we don't want to use naive search. + if not chunks_vdb_ids: + return ConsistencyReport(errors=errors) + + chunk_vector_ids = set(chunks_vdb_ids) + missing_chunk_vector_ids = sorted(all_chunk_ids_from_storage - chunk_vector_ids) + if missing_chunk_vector_ids: + errors.append( + ConsistencyIssue( + check="chunk_vector_representations", + message="Chunks in storage exist without matching chunk vectors.", + details={"missing_vector_ids": missing_chunk_vector_ids}, + ) + ) + + orphan_chunk_vector_ids = sorted(chunk_vector_ids - all_chunk_ids_from_storage) + if orphan_chunk_vector_ids: + errors.append( + ConsistencyIssue( + check="chunk_vector_endpoints", + message="Chunk vectors exist without matching chunk records.", + details={"orphan_vector_ids": orphan_chunk_vector_ids}, + ) + ) + + return ConsistencyReport(errors=errors) From 9c84f93ff9af2140bc1a9078c5b9ba38b4a19c8c Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Wed, 22 Apr 2026 01:03:35 +0700 Subject: [PATCH 16/42] Add new methods in vdb to retrieve data by id --- ragu/storage/base_storage.py | 29 ++++ ragu/storage/vdb_storage_adapters/nano_vdb.py | 91 ++++++++++-- .../vdb_storage_adapters/qdrant_vdb.py | 129 +++++++++++++++++- 3 files changed, 233 insertions(+), 16 deletions(-) diff --git a/ragu/storage/base_storage.py b/ragu/storage/base_storage.py index c9edc94..3b7a9f2 100644 --- a/ragu/storage/base_storage.py +++ b/ragu/storage/base_storage.py @@ -80,6 +80,35 @@ async def delete(self, ids: List[str], **kwargs) -> None: """ ... + @abstractmethod + async def get_all_ids(self) -> List[str]: + """ + Return all stored record IDs. + + :return: List of identifiers currently present in the vector store. + """ + ... + + @abstractmethod + async def get_points_by_ids(self, ids: List[str]) -> List[Point | None]: + """ + Fetch stored points by ID. + + :param ids: Record identifiers to retrieve in order. + :return: Points aligned with ``ids``; missing IDs mapped to ``None``. + """ + ... + + @abstractmethod + async def get_payloads_by_ids(self, ids: List[str]) -> List[Dict | None]: + """ + Fetch stored payloads by ID. + + :param ids: Record identifiers to retrieve in order. + :return: Payloads aligned with ``ids``; missing IDs mapped to ``None``. + """ + ... + T = TypeVar("T") diff --git a/ragu/storage/vdb_storage_adapters/nano_vdb.py b/ragu/storage/vdb_storage_adapters/nano_vdb.py index 11cb168..35dccee 100644 --- a/ragu/storage/vdb_storage_adapters/nano_vdb.py +++ b/ragu/storage/vdb_storage_adapters/nano_vdb.py @@ -1,7 +1,7 @@ # Based on https://github.com/gusye1234/nano-graphrag/blob/main/nano_graphrag/_storage/vdb_nanovectordb.py import os -from typing import Any, List +from typing import Any, List, Dict from typing_extensions import override import numpy as np @@ -121,18 +121,6 @@ async def query( ) return hits - async def index_start_callback(self): - """ - Pre-index hook for interface compatibility. - """ - pass - - async def query_done_callback(self): - """ - Post-query hook for interface compatibility. - """ - pass - @override async def delete(self, ids: List[str], **kwargs: Any) -> None: """ @@ -145,6 +133,83 @@ async def delete(self, ids: List[str], **kwargs: Any) -> None: return self._client.delete(ids) + @override + async def get_all_ids(self) -> List[str]: + """ + Return all record IDs currently stored in NanoVectorDB. + """ + try: + _data = getattr(self._client, "_NanoVectorDB__storage", {}) + return [item["__id__"] for item in _data.get("data", [])] + except AttributeError: + raise RuntimeError("Seem that NanoVDB are non initialized.") + + @override + async def get_points_by_ids(self, ids: List[str]) -> List[Point | None]: + """ + Retrieve stored points by ID, preserving input order. + + :param ids: Record identifiers to fetch. + :return: Points aligned with ``ids``; missing IDs mapped to ``None``. + """ + data = self._client.get(ids) + points: List[Point | None] = [] + data_dict: Dict = {} + + try: + _data = getattr(self._client, "_NanoVectorDB__storage", {}) + _matrix: np.ndarray = _data.get("matrix", np.array([])) + except AttributeError: + raise RuntimeError("Seem that NanoVDB are non initialized.") + + for i, item in enumerate(data): + data_dict[item["__id__"]] = {"__vector__": _matrix[i], **item} + + for _id in ids: + if _id in data_dict: + point = Point( + id=_id, + dense_embedding=data_dict[_id]["__vector__"], + metadata={k: v for k, v in data_dict[_id].items() if k not in {"__id__", "__vector__"}}, + ) + points.append(point) + else: + points.append(None) + + return points + + @override + async def get_payloads_by_ids(self, ids: List[str]) -> List[Dict | None]: + """ + Retrieve stored payloads by ID, preserving input order. + + :param ids: Record identifiers to fetch. + :return: Payloads aligned with ``ids``; missing IDs mapped to ``None``. + """ + output: List[Dict | None] = [] + data = self._client.get(ids) + data_dict = {item["__id__"]: item for item in data} + + for _id in ids: + if _id in data_dict: + output.append(data_dict[_id]) + else: + output.append(None) + + return output + + async def index_start_callback(self): + """ + Pre-index hook for interface compatibility. + """ + pass + + async def query_done_callback(self): + """ + Post-query hook for interface compatibility. + """ + pass + async def index_done_callback(self) -> None: """ Save the current state of the NanoVectorDB to disk. diff --git a/ragu/storage/vdb_storage_adapters/qdrant_vdb.py b/ragu/storage/vdb_storage_adapters/qdrant_vdb.py index 465d04d..a134663 100644 --- a/ragu/storage/vdb_storage_adapters/qdrant_vdb.py +++ b/ragu/storage/vdb_storage_adapters/qdrant_vdb.py @@ -1,7 +1,7 @@ import asyncio import uuid from pathlib import Path -from typing import Any, List, Dict, override, Literal, cast +from typing import Any, List, Dict, override, Literal from qdrant_client.conversions.common_types import CollectionInfo, QueryResponse from qdrant_client.http.models import FusionQuery, Fusion @@ -9,7 +9,7 @@ from pydantic import BaseModel from ragu.common.global_parameters import Settings from ragu.storage.base_storage import BaseVectorStorage -from ragu.storage.types import EmbeddingHit, Point +from ragu.storage.types import EmbeddingHit, Point, SparseEmbedding from ragu.common.logger import logger from qdrant_client import AsyncQdrantClient, models @@ -334,7 +334,7 @@ async def upsert(self, data: List[Point], **kwargs: Any) -> None: if any(has_sparse) and not all(has_sparse): raise ValueError("All points in this batch must either have sparse embeddings or not have them.") - if not self._sparse_mode and has_sparse: + if not self._sparse_mode and any(has_sparse): raise ValueError(f"Try to insert sparse embeddings, but `sparse_type` parameter is set to None. " f"Please, set `sparse_type` parameter. Possible values: bm25, bm42, splade, custom") @@ -461,6 +461,129 @@ async def delete(self, ids: List[str], **kwargs: Any) -> None: wait=True, ) + @override + async def get_all_ids(self) -> List[str]: + """ + Return all RAGU record IDs stored in the Qdrant collection. + """ + ids: List[str] = [] + await self._ensure_collection() + + scroll_offset = None + while True: + points, scroll_offset = await self._get_client().scroll( + collection_name=self.collection_name, + limit=1000, + offset=scroll_offset, + with_payload=True, + with_vectors=False, + ) + + for point in points: + payload = dict(getattr(point, "payload", None) or {}) + record_id = payload.get("__ragu_id__") + if record_id is None: + raise ValueError("Qdrant point payload is missing '__ragu_id__'") + ids.append(str(record_id)) + + if scroll_offset is None: + break + + return ids + + @override + async def get_payloads_by_ids(self, ids: List[str]) -> List[Dict | None]: + """ + Retrieve stored payloads by RAGU IDs, preserving input order. + + :param ids: Record identifiers to fetch. + :return: Payloads aligned with ``ids``; missing IDs mapped to ``None``. + """ + if not ids: + return [] + + await self._ensure_collection() + qdrant_ids = [self._to_qdrant_point_id(record_id) for record_id in ids] + records = await self._get_client().retrieve( + collection_name=self.collection_name, + ids=qdrant_ids, + with_vectors=False, + with_payload=True, + ) + payloads_by_id = {} + for record in records: + payload = dict(getattr(record, "payload", None) or {}) + record_id = payload.pop("__ragu_id__", None) + if record_id is None: + raise ValueError("Qdrant point payload is missing '__ragu_id__'") + payloads_by_id[str(record_id)] = { + "__id__": str(record_id), + **payload, + } + return [payloads_by_id.get(record_id) for record_id in ids] + + @override + async def get_points_by_ids(self, ids: List[str]) -> List[Point | None]: + """ + Retrieve stored points by RAGU IDs, preserving input order. + + :param ids: Record identifiers to fetch. + :return: Points aligned with ``ids``; missing IDs mapped to ``None``. + """ + if not ids: + return [] + + await self._ensure_collection() + qdrant_ids = [self._to_qdrant_point_id(record_id) for record_id in ids] + records = await self._get_client().retrieve( + collection_name=self.collection_name, + ids=qdrant_ids, + with_vectors=True, + with_payload=True, + ) + points_by_id = {} + for record in records: + payload = dict(getattr(record, "payload", None) or {}) + record_id = payload.pop("__ragu_id__", None) + if record_id is None: + raise ValueError("Qdrant point payload is missing '__ragu_id__'") + + vector = getattr(record, "vector", None) + if vector is None: + dense_vector = None + elif isinstance(vector, dict): + dense_vector = vector.get(self.DENSE_VECTOR_NAME) + else: + dense_vector = vector + + if dense_vector is None: + continue + + sparse_embedding = None + if self._sparse_mode is not None and isinstance(vector, dict): + sparse_vector = vector.get(self._sparse_mode) + if sparse_vector is not None: + if isinstance(sparse_vector, dict): + indices = sparse_vector.get("indices") + values = sparse_vector.get("values") + else: + indices = getattr(sparse_vector, "indices", None) + values = getattr(sparse_vector, "values", None) + + if indices is not None and values is not None: + sparse_embedding = SparseEmbedding( + indices=list(indices), + values=list(values), + ) + + points_by_id[record_id] = Point( + id=record_id, + dense_embedding=dense_vector, + sparse_embedding=sparse_embedding, + metadata=payload, + ) + return [points_by_id.get(record_id) for record_id in ids] + async def index_start_callback(self): """ Ensure collection availability before indexing starts. From aea3508cd9efc8896856c0a485bf0df947a1f9b4 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Wed, 22 Apr 2026 22:59:43 +0700 Subject: [PATCH 17/42] Update graph types --- ragu/graph/types.py | 19 ++++++++----------- ragu/storage/types.py | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/ragu/graph/types.py b/ragu/graph/types.py index 1903060..e9b309b 100644 --- a/ragu/graph/types.py +++ b/ragu/graph/types.py @@ -20,21 +20,15 @@ community detection or summarization. """ - +from typing import List from dataclasses import dataclass, field -from typing import List, TypedDict import numpy as np -from ragu.storage.types import Edge, Node +from ragu.storage.types import ClusterInfo, Edge, Node from ragu.utils.ragu_utils import compute_mdhash_id -class ClusterInfo(TypedDict): - level: int - cluster_id: int # str or int? - - @dataclass(slots=True) class Entity(Node): """ @@ -55,7 +49,6 @@ class Entity(Node): documents_id: list[str] = field(default_factory=list[str]) clusters: list[ClusterInfo] = field(default_factory=list[ClusterInfo]) id: str = 'auto' - # if type-hint id as str | None, this will raise type checker errors in many places def __post_init__(self): """ @@ -72,6 +65,9 @@ def __post_init__(self): def __eq__(self, other): return self.id == other.id and self.description == other.description + def to_text(self): + return f"{self.entity_name} - {self.description}" + @dataclass(slots=True) class EntityEmbedding: @@ -110,7 +106,6 @@ class Relation(Edge): relation_strength: int | float = 1.0 source_chunk_id: list[str] = field(default_factory=list[str]) id: str = 'auto' - # if type-hint id as str | None, this will raise type checker errors in many places def __post_init__(self): """ @@ -124,6 +119,9 @@ def __post_init__(self): def __eq__(self, other): return self.id == other.id and self.description == other.description + def to_text(self): + return f"{self.description}" + @dataclass(slots=True) class RelationEmbedding: @@ -153,7 +151,6 @@ class Community: entities: List[Entity] relations: List[Relation] id: str = 'auto' - # if type-hint id as str | None, this will raise type checker errors in many places def __post_init__(self): """ diff --git a/ragu/storage/types.py b/ragu/storage/types.py index 800fb39..4858c19 100644 --- a/ragu/storage/types.py +++ b/ragu/storage/types.py @@ -1,21 +1,41 @@ import time from dataclasses import dataclass, field -from typing import Any, List, Dict +from typing import Any, Dict, List, TypedDict from ragu.utils.ragu_utils import FLOATS, compute_mdhash_id, serialize +class ClusterInfo(TypedDict): + """ + Represents graph cluster info. + """ + level: int + cluster_id: int + + class Node: """ Base graph node type for storage adapters. - Subclasses are expected to be dataclasses and define an ``id`` field. + Subclasses are expected to be dataclasses and define ``id``, + ``source_chunk_id``, and ``clusters`` fields. """ id: str + source_chunk_id: List[str] + clusters: List[ClusterInfo] def to_dict(self) -> Dict[str, Any]: + """ + Serialize node to dict. + """ return serialize(self) + + def to_text(self): + """ + Convert node to text representation. + """ + return str(f"{self.id}") class Edge: @@ -23,16 +43,26 @@ class Edge: Base graph edge type for storage adapters. Subclasses are expected to be dataclasses and define ``id``, - ``subject_id``, and ``object_id`` fields. + ``subject_id``, ``object_id``, and ``source_chunk_id`` fields. """ id: str subject_id: str object_id: str + source_chunk_id: List[str] def to_dict(self) -> Dict[str, Any]: + """ + Serialize edge to dict. + """ return serialize(self) + def to_text(self): + """ + Convert edge to text representation. + """ + return str(f"{self.subject_id} - {self.object_id}") + DenseEmbedding = FLOATS From 9927d6cd92006328b80106519077c930e3e7b76b Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Wed, 22 Apr 2026 23:00:11 +0700 Subject: [PATCH 18/42] Refactor public API, fix bugs, add parametrization --- ragu/graph/index.py | 979 +++++++++++++--------------------- ragu/graph/knowledge_graph.py | 517 ++++++++++++++---- 2 files changed, 769 insertions(+), 727 deletions(-) diff --git a/ragu/graph/index.py b/ragu/graph/index.py index 0a90ed8..2c43104 100644 --- a/ragu/graph/index.py +++ b/ragu/graph/index.py @@ -1,22 +1,15 @@ from __future__ import annotations import os -import re from collections import defaultdict from dataclasses import asdict, dataclass, field -from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Type, cast +from typing import Any, Dict, Generic, List, Optional, Set, Type, TypeVar, cast import numpy as np from ragu.chunker.types import Chunk from ragu.common.global_parameters import DEFAULT_FILENAMES from ragu.common.global_parameters import Settings -from ragu.graph.types import ( - ClusterInfo, - Entity, - Relation, - Community, - CommunitySummary -) +from ragu.graph.types import Community, CommunitySummary, Entity, Relation from ragu.models.embedder import Embedder from ragu.models.sparse_embedder import SparseEmbedder from ragu.storage.base_storage import ( @@ -27,7 +20,7 @@ ) from ragu.storage.graph_storage_adapters.networkx_adapter import NetworkXStorage from ragu.storage.kv_storage_adapters.json_storage import JsonKVStorage -from ragu.storage.types import Point +from ragu.storage.types import Node, Edge, Point from ragu.storage.vdb_storage_adapters.nano_vdb import NanoVectorDBStorage @@ -106,12 +99,12 @@ def __str__(self) -> str: return self.to_text() -class Index: - """ - Manages all storage operations for a knowledge graph. +NodeT = TypeVar("NodeT", bound=Node) +EdgeT = TypeVar("EdgeT", bound=Edge) - Coordinates three storage backends (graph, vector DB, KV) and provides - batch CRUD operations with cascading deletes and duplicate merging. +class Index(Generic[NodeT, EdgeT]): + """ + Coordinates graph, vector, and KV storage for generic nodes and edges. """ def __init__( @@ -119,6 +112,8 @@ def __init__( arguments: StorageArguments, embedder: Embedder | None = None, sparse_embedder: SparseEmbedder | None = None, + node_t: Type[NodeT] = Entity, + edge_t: Type[EdgeT] = Relation, ): """ Initialize storage backends and in-memory reverse indexes. @@ -133,8 +128,8 @@ def __init__( self.sparse_embedder = sparse_embedder # Reverse indexes for cascade operations - self._chunk_to_entities: Dict[str, Set[str]] = defaultdict(set) - self._chunk_to_relations: Dict[str, Set[str]] = defaultdict(set) + self._chunk_to_nodes: Dict[str, Set[str]] = defaultdict(set) + self._chunk_to_edges: Dict[str, Set[str]] = defaultdict(set) summary_kv_kwargs = self._build_storage_kwargs( storage_folder, @@ -151,17 +146,17 @@ def __init__( DEFAULT_FILENAMES["chunks_kv_storage_name"], arguments.chunks_kv_storage_kwargs, ) - entity_vdb_kwargs = self._build_storage_kwargs( + nodes_vdb_kwargs = self._build_storage_kwargs( storage_folder, DEFAULT_FILENAMES["entity_vdb_name"], arguments.vdb_storage_kwargs, ) - relation_vdb_kwargs = self._build_storage_kwargs( + edges_vdb_kwargs = self._build_storage_kwargs( storage_folder, DEFAULT_FILENAMES["relation_vdb_name"], arguments.vdb_storage_kwargs, ) - chunk_vdb_kwargs = self._build_storage_kwargs( + chunks_vdb_kwargs = self._build_storage_kwargs( storage_folder, DEFAULT_FILENAMES["chunk_vdb_name"], arguments.vdb_storage_kwargs, @@ -182,9 +177,9 @@ def __init__( dimensions_from_kwargs = [ storage_kwargs.get("embedding_dim") for storage_kwargs in [ - entity_vdb_kwargs, - relation_vdb_kwargs, - chunk_vdb_kwargs + nodes_vdb_kwargs, + edges_vdb_kwargs, + chunks_vdb_kwargs ] if storage_kwargs.get("embedding_dim")] number_of_dimensions = len(dimensions_from_kwargs) @@ -197,312 +192,281 @@ def __init__( resolved_dim = embedding_dim_from_embedder - entity_vdb_kwargs["embedding_dim"] = resolved_dim - relation_vdb_kwargs["embedding_dim"] = resolved_dim - chunk_vdb_kwargs["embedding_dim"] = resolved_dim + nodes_vdb_kwargs["embedding_dim"] = resolved_dim + edges_vdb_kwargs["embedding_dim"] = resolved_dim + chunks_vdb_kwargs["embedding_dim"] = resolved_dim # Vector storages - self.entity_vector_db = arguments.vdb_storage_type(**entity_vdb_kwargs) - self.relation_vector_db = arguments.vdb_storage_type(**relation_vdb_kwargs) - self.chunk_vector_db = arguments.vdb_storage_type(**chunk_vdb_kwargs) + self.nodes_vector_db = arguments.vdb_storage_type(**nodes_vdb_kwargs) + self.edges_vector_db = arguments.vdb_storage_type(**edges_vdb_kwargs) + self.chunks_vector_db = arguments.vdb_storage_type(**chunks_vdb_kwargs) # Graph storage - self.graph_backend = arguments.graph_backend_storage( - node_cls=Entity, - edge_cls=Relation, + self.graph_backend: BaseGraphStorage[NodeT, EdgeT] = arguments.graph_backend_storage( + node_cls=node_t, + edge_cls=edge_t, **graph_kwargs ) - async def insert_entities(self, entities: List[Entity]) -> "Index": + async def upsert_nodes(self, nodes: List[NodeT]) -> "Index[NodeT, EdgeT]": """ - Insert entities into graph and vector DB. - - Duplicate IDs in the incoming batch are merged. If an entity with the - same ID already exists, incoming and existing values are merged. + Insert or replace nodes in graph and vector DB by ID. - :param entities: Entities to insert. + :param nodes: Nodes to insert. :return: Self for method chaining. + :raises ValueError: If duplicate node IDs are provided in one request. """ assert self.embedder - if not entities: + if not nodes: return self - incoming_by_id: Dict[str, List[Entity]] = defaultdict(list) - for entity in entities: - assert entity.id # according to incoming_by_id type hint, should be not None - incoming_by_id[entity.id].append(entity) - - existing_entities = await self.graph_backend.get_nodes(list(incoming_by_id.keys())) - existing_by_id: Dict[str, Entity] = {} - for e in existing_entities: - if e is not None: - assert e.id # according to existing_by_id type hint, should be not None - existing_by_id[e.id] = e - - entities_to_insert: List[Entity] = [] - for entity_id, incoming_group in incoming_by_id.items(): - merged_group = list(incoming_group) - existing = existing_by_id.get(entity_id) - if existing is not None: - merged_group.append(existing) - - if len(merged_group) > 1: - entities_to_insert.extend(self._merge_entities({entity_id: merged_group})) - else: - entities_to_insert.extend(incoming_group) + incoming_by_id: Dict[str, List[NodeT]] = defaultdict(list) + for node in nodes: + assert node.id + incoming_by_id[node.id].append(node) + + duplicate_ids = [node_id for node_id, group in incoming_by_id.items() if len(group) > 1] + if duplicate_ids: + raise ValueError(f"Cannot insert duplicated node IDs in one request: {duplicate_ids}") + + node_ids = list(incoming_by_id.keys()) + existing_nodes = await self.graph_backend.get_nodes(node_ids) + existing_ids = [ + node_id + for node_id, existing in zip(node_ids, existing_nodes) + if existing is not None + ] - await self.graph_backend.upsert_nodes(entities_to_insert) + nodes_to_upsert = [group[0] for group in incoming_by_id.values()] dense_embeddings = await self.embedder.batch_embed_text( - [f"{e.entity_name} - {e.description}" for e in entities_to_insert], - desc="Entities vectorization", + [node.to_text() for node in nodes_to_upsert], + desc="Nodes vectorization", ) sparse_embeddings = self.sparse_embedder.embed_document( - [f"{e.entity_name} - {e.description}" for e in entities_to_insert] - ) if self.sparse_embedder else [None for _ in entities_to_insert] + [node.to_text() for node in nodes_to_upsert] + ) if self.sparse_embedder else [None for _ in nodes_to_upsert] vdb_data = [ Point( - id=entity.id, + id=node.id, dense_embedding=np.array(dense), sparse_embedding=sparse, - metadata={ - "entity_name": entity.entity_name, - "content": f"{entity.entity_name} - {entity.description}", - } - ) for entity, dense, sparse in zip(entities_to_insert, dense_embeddings, sparse_embeddings) + metadata=node.to_dict() + ) for node, dense, sparse in zip(nodes_to_upsert, dense_embeddings, sparse_embeddings) ] - await self.entity_vector_db.upsert(vdb_data) + await self.graph_backend.upsert_nodes(nodes_to_upsert) + await self.nodes_vector_db.upsert(vdb_data) await self.graph_backend.index_done_callback() - await self.entity_vector_db.index_done_callback() - await self._update_reverse_indexes(entities=entities_to_insert) + await self.nodes_vector_db.index_done_callback() + await self._update_reverse_indexes( + deleted_node_ids=existing_ids, + nodes=nodes_to_upsert, + ) return self - async def update_entities(self, entities: List[Entity]) -> "Index": + async def update_nodes(self, nodes: List[NodeT]) -> "Index[NodeT, EdgeT]": """ - Update entities by ID using replace semantics. + Update nodes by ID using replace semantics. - Existing entities are replaced by incoming payloads. No merge with + Existing nodes are replaced by incoming payloads. No merge with previous values is performed. - :param entities: Entities to update. + :param nodes: Nodes to update. :return: Self for method chaining. - :raises ValueError: If entity IDs are missing/duplicated in request or absent in storage. + :raises ValueError: If node IDs are missing/duplicated in request or absent in storage. """ assert self.embedder - if not entities: + if not nodes: return self - incoming_by_id: Dict[str, List[Entity]] = defaultdict(list) - for entity in entities: - assert entity.id - incoming_by_id[entity.id].append(entity) + incoming_by_id: Dict[str, List[NodeT]] = defaultdict(list) + for node in nodes: + assert node.id + incoming_by_id[node.id].append(node) - duplicate_ids = [entity_id for entity_id, group in incoming_by_id.items() if len(group) > 1] + duplicate_ids = [node_id for node_id, group in incoming_by_id.items() if len(group) > 1] if duplicate_ids: - raise ValueError(f"Cannot update duplicated entity IDs in one request: {duplicate_ids}") + raise ValueError(f"Cannot update duplicated node IDs in one request: {duplicate_ids}") - entity_ids = list(incoming_by_id.keys()) - existing_entities = await self.graph_backend.get_nodes(entity_ids) - missing_ids = [entity_id for entity_id, existing in zip(entity_ids, existing_entities) if existing is None] + node_ids = list(incoming_by_id.keys()) + existing_nodes = await self.graph_backend.get_nodes(node_ids) + missing_ids = [node_id for node_id, existing in zip(node_ids, existing_nodes) if existing is None] if missing_ids: - raise ValueError(f"Cannot update non-existent entities: {missing_ids}") + raise ValueError(f"Cannot update non-existent nodes: {missing_ids}") - entities_to_update = [group[0] for group in incoming_by_id.values()] + nodes_to_update = [group[0] for group in incoming_by_id.values()] - await self.graph_backend.upsert_nodes(entities_to_update) dense_embeddings = await self.embedder.batch_embed_text( - [f"{e.entity_name} - {e.description}" for e in entities_to_update], - desc="Entities vectorization", + [node.to_text() for node in nodes_to_update], + desc="Nodes vectorization", ) sparse_embeddings = self.sparse_embedder.embed_document( - [f"{e.entity_name} - {e.description}" for e in entities_to_update] - ) if self.sparse_embedder else [None for _ in entities_to_update] + [node.to_text() for node in nodes_to_update] + ) if self.sparse_embedder else [None for _ in nodes_to_update] vdb_data = [ Point( - id=entity.id, + id=node.id, dense_embedding=np.array(dense), sparse_embedding=sparse, - metadata={ - "entity_name": entity.entity_name, - "content": f"{entity.entity_name} - {entity.description}", - } - ) for entity, dense, sparse in zip(entities_to_update, dense_embeddings, sparse_embeddings) + metadata=node.to_dict() + ) for node, dense, sparse in zip(nodes_to_update, dense_embeddings, sparse_embeddings) ] - await self.entity_vector_db.upsert(vdb_data, sparse_data=sparse_embeddings) + await self.graph_backend.upsert_nodes(nodes_to_update) + await self.nodes_vector_db.upsert(vdb_data) await self.graph_backend.index_done_callback() - await self.entity_vector_db.index_done_callback() + await self.nodes_vector_db.index_done_callback() await self._update_reverse_indexes( - deleted_entity_ids=entity_ids, - entities=entities_to_update, + deleted_node_ids=node_ids, + nodes=nodes_to_update, ) return self - async def insert_relations(self, relations: List[Relation]) -> "Index": + async def upsert_edges(self, edges: List[EdgeT]) -> "Index[NodeT, EdgeT]": """ - Insert relations into graph and vector DB. - - Duplicate IDs in the incoming batch are merged. If a relation with the - same ID already exists, incoming and existing values are merged. + Insert edges in graph and vector DB. - :param relations: Relations to insert. + :param edges: Edges to insert. :return: Self for method chaining. - :raises ValueError: If referenced entities don't exist. + :raises ValueError: If edge IDs are duplicated in request or + referenced nodes don't exist. """ assert self.embedder - if not relations: + if not edges: return self - for relation in relations: - if not relation.id: - raise ValueError("Cannot insert relation without id") - - await self._validate_relation_endpoints_exist(relations) - - incoming_by_id: Dict[str, List[Relation]] = defaultdict(list) - for relation in relations: - assert relation.id - incoming_by_id[relation.id].append(relation) - - existing_relation_groups = await self._get_existing_relations_grouped_by_id(set(incoming_by_id.keys())) - existing_relation_ids = list(existing_relation_groups.keys()) + await self._validate_edge_endpoints_exist(edges) - relations_to_insert: List[Relation] = [] - for relation_id, incoming_group in incoming_by_id.items(): - merged_group = list(incoming_group) - merged_group.extend(existing_relation_groups.get(relation_id, [])) + incoming_by_id: Dict[str, List[EdgeT]] = defaultdict(list) + for edge in edges: + incoming_by_id[edge.id].append(edge) - if len(merged_group) > 1: - relations_to_insert.extend(self._merge_relations({relation_id: merged_group})) - else: - relations_to_insert.extend(incoming_group) + duplicate_ids = [edge_id for edge_id, group in incoming_by_id.items() if len(group) > 1] + if duplicate_ids: + raise ValueError(f"Cannot insert duplicated edge IDs in one request: {duplicate_ids}") - delete_specs: List[EdgeSpec] = [ - (relation.subject_id, relation.object_id, relation.id) - for relations_group in existing_relation_groups.values() - for relation in relations_group - if relation.id + edges_to_upsert = [group[0] for group in incoming_by_id.values()] + edge_specs = [ + (edge.subject_id, edge.object_id, edge.id) + for edge in edges_to_upsert + ] + existing_edges = await self.graph_backend.get_edges(edge_specs) + existing_edge_ids = [ + edge.id + for edge in existing_edges + if edge is not None ] - if delete_specs: - await self.graph_backend.delete_edges(delete_specs) - - await self.graph_backend.upsert_edges(relations_to_insert) - - if existing_relation_ids: - await self.relation_vector_db.delete(existing_relation_ids) dense_embeddings = await self.embedder.batch_embed_text( - [r.description for r in relations_to_insert], - desc="Relations vectorization", + [edge.to_text() for edge in edges_to_upsert], + desc="Edges vectorization", ) sparse_embeddings = self.sparse_embedder.embed_document( - [r.description for r in relations_to_insert] - ) if self.sparse_embedder else [None for _ in relations_to_insert] + [edge.to_text() for edge in edges_to_upsert] + ) if self.sparse_embedder else [None for _ in edges_to_upsert] vdb_data = [ Point( - id=relation.id, + id=edge.id, dense_embedding=np.array(dense), sparse_embedding=sparse, - metadata={ - "content": relation.description, - } - ) for relation, dense, sparse in zip(relations_to_insert, dense_embeddings, sparse_embeddings) + metadata=edge.to_dict() + ) for edge, dense, sparse in zip(edges_to_upsert, dense_embeddings, sparse_embeddings) ] - await self.relation_vector_db.upsert(vdb_data) + await self.graph_backend.upsert_edges(edges_to_upsert) + await self.edges_vector_db.upsert(vdb_data) await self.graph_backend.index_done_callback() - await self.relation_vector_db.index_done_callback() + await self.edges_vector_db.index_done_callback() await self._update_reverse_indexes( - deleted_relation_ids=existing_relation_ids, - relations=relations_to_insert, + deleted_edge_ids=existing_edge_ids, + edges=edges_to_upsert, ) return self - async def update_relations(self, relations: List[Relation]) -> "Index": + async def update_edges(self, edges: List[EdgeT]) -> "Index[NodeT, EdgeT]": """ - Update relations by ID using replace semantics. + Update edges by exact edge spec using replace semantics. - Existing relations are replaced by incoming payloads. No merge with - previous values is performed. + Existing edges at the same ``(subject_id, object_id, id)`` are + replaced by incoming payloads. No merge with previous values is + performed. - :param relations: Relations to update. + :param edges: Edges to update. :return: Self for method chaining. - :raises ValueError: If relation IDs are missing/duplicated in request, - IDs are absent in storage, or referenced entities don't exist. + :raises ValueError: If edge IDs are missing/duplicated in request, + matching edge specs are absent in storage, or referenced nodes + don't exist. """ assert self.embedder - if not relations: + if not edges: return self - incoming_by_id: Dict[str, List[Relation]] = defaultdict(list) - for relation in relations: - if not relation.id: - raise ValueError("Cannot update relation without id") - incoming_by_id[relation.id].append(relation) + incoming_by_id: Dict[str, List[EdgeT]] = defaultdict(list) + for edge in edges: + if not edge.id: + raise ValueError("Cannot update edge without id") + incoming_by_id[edge.id].append(edge) - duplicate_ids = [relation_id for relation_id, group in incoming_by_id.items() if len(group) > 1] + duplicate_ids = [edge_id for edge_id, group in incoming_by_id.items() if len(group) > 1] if duplicate_ids: - raise ValueError(f"Cannot update duplicated relation IDs in one request: {duplicate_ids}") - - relation_ids = list(incoming_by_id.keys()) - existing_relation_groups = await self._get_existing_relations_grouped_by_id(set(relation_ids)) - missing_ids = [relation_id for relation_id in relation_ids if relation_id not in existing_relation_groups] - if missing_ids: - raise ValueError(f"Cannot update non-existent relations: {missing_ids}") - - relations_to_update = [group[0] for group in incoming_by_id.values()] - await self._validate_relation_endpoints_exist(relations_to_update) + raise ValueError(f"Cannot update duplicated edge IDs in one request: {duplicate_ids}") - delete_specs: List[EdgeSpec] = [ - (relation.subject_id, relation.object_id, relation.id) - for relations_group in existing_relation_groups.values() - for relation in relations_group - if relation.id + edges_to_update = [group[0] for group in incoming_by_id.values()] + edge_specs = [ + (edge.subject_id, edge.object_id, edge.id) + for edge in edges_to_update ] - if delete_specs: - await self.graph_backend.delete_edges(delete_specs) + existing_edges = await self.graph_backend.get_edges(edge_specs) + missing_specs = [ + edge_spec + for edge_spec, existing_edge in zip(edge_specs, existing_edges) + if existing_edge is None + ] + if missing_specs: + raise ValueError(f"Cannot update non-existent edges: {missing_specs}") - await self.graph_backend.upsert_edges(relations_to_update) + await self._validate_edge_endpoints_exist(edges_to_update) dense_embeddings = await self.embedder.batch_embed_text( - [r.description for r in relations_to_update], - desc="Relations vectorization", + [edge.to_text() for edge in edges_to_update], + desc="Edges vectorization", ) sparse_embeddings = self.sparse_embedder.embed_document( - [r.description for r in relations_to_update] - ) if self.sparse_embedder else [None for _ in relations_to_update] + [edge.to_text() for edge in edges_to_update] + ) if self.sparse_embedder else [None for _ in edges_to_update] vdb_data = [ Point( - id=relation.id, + id=edge.id, dense_embedding=np.array(dense), sparse_embedding=sparse, - metadata={ - "content": relation.description, - } - ) for relation, dense, sparse in zip(relations_to_update, dense_embeddings, sparse_embeddings) + metadata=edge.to_dict() + ) for edge, dense, sparse in zip(edges_to_update, dense_embeddings, sparse_embeddings) ] + await self.graph_backend.upsert_edges(edges_to_update) + await self.edges_vector_db.upsert(vdb_data) + await self.graph_backend.index_done_callback() - await self.relation_vector_db.index_done_callback() + await self.edges_vector_db.index_done_callback() await self._update_reverse_indexes( - deleted_relation_ids=relation_ids, - relations=relations_to_update, + deleted_edge_ids=list(incoming_by_id.keys()), + edges=edges_to_update, ) return self - async def upsert_chunks(self, chunks: List[Chunk]) -> "Index": + async def upsert_chunks(self, chunks: List[Chunk]) -> "Index[NodeT, EdgeT]": """ Insert or update chunks into KV storage (and optionally vector DB). @@ -537,121 +501,13 @@ async def upsert_chunks(self, chunks: List[Chunk]) -> "Index": metadata={"content": c.content, "doc_id": c.doc_id} ) for c, dense, sparse in zip(chunks, dense_embeddings, sparse_embeddings)] - await self.chunk_vector_db.upsert(vdb_data) - await self.chunk_vector_db.index_done_callback() + await self.chunks_vector_db.upsert(vdb_data) + await self.chunks_vector_db.index_done_callback() await self.chunks_kv_storage.index_done_callback() return self - async def reindex_cluster_ids( - self, - entities: List[Entity], - communities: List[Community], - summaries: Optional[List[CommunitySummary]] = None, - ) -> tuple[List[Entity], List[Community], List[CommunitySummary]]: - """ - Remap cluster IDs to be globally unique per level across indexing runs. - - Levels are preserved to keep level-based filtering intact. - - :param entities: Entities whose cluster memberships should be remapped. - :param communities: Newly generated communities with local cluster IDs. - :param summaries: Optional summaries linked to community IDs. - :return: Tuple with remapped entities, remapped communities, and remapped summaries. - """ - if not communities: - return entities, communities, summaries or [] - - existing_keys = await self.community_kv_storage.all_keys() - existing_data = await self.community_kv_storage.get_by_ids(existing_keys) if existing_keys else [] - - max_cluster_id_by_level: Dict[int, int] = defaultdict(lambda: -1) - for row in existing_data: - if not row: - continue - try: - level = int(row.get("level")) - cluster_id = int(row.get("cluster_id")) - except (TypeError, ValueError): - continue - if cluster_id > max_cluster_id_by_level[level]: - max_cluster_id_by_level[level] = cluster_id - - local_ids_by_level: Dict[int, List[int]] = defaultdict(list) - for community in communities: - level = int(community.level) - cluster_id = int(community.cluster_id) - local_ids_by_level[level].append(cluster_id) - - local_to_global: Dict[Tuple[int, int], int] = {} - for level, local_ids in local_ids_by_level.items(): - for local_cluster_id in sorted(set(local_ids)): - max_cluster_id_by_level[level] += 1 - local_to_global[(level, local_cluster_id)] = max_cluster_id_by_level[level] - - old_to_new_community_id: Dict[str, str] = {} - remapped_communities: List[Community] = [] - for community in communities: - level = int(community.level) - local_cluster_id = int(community.cluster_id) - global_cluster_id = local_to_global[(level, local_cluster_id)] - - remapped_community = Community( - level=level, - cluster_id=global_cluster_id, - entities=community.entities, - relations=community.relations, - ) - if community.id: - old_to_new_community_id[str(community.id)] = str(remapped_community.id) - remapped_communities.append(remapped_community) - - valid_cluster_pairs: Set[Tuple[int, int]] = { - (int(community.level), int(community.cluster_id)) - for community in remapped_communities - } - - for entity in entities: - remapped_memberships: List[ClusterInfo] = [] - seen_memberships: Set[Tuple[int, int]] = set() - for membership in entity.clusters: - assert isinstance(membership, dict), f'What is membership? {membership}' - try: - level = membership['level'] - local_cluster_id = membership["cluster_id"] - except (TypeError, ValueError): - continue - - global_cluster_id = local_to_global.get((level, local_cluster_id), local_cluster_id) - if (level, global_cluster_id) not in valid_cluster_pairs: - continue - - membership_key = (level, global_cluster_id) - if membership_key in seen_memberships: - continue - - seen_memberships.add(membership_key) - remapped_memberships.append({ - "level": level, - "cluster_id": global_cluster_id, - }) - entity.clusters = remapped_memberships - - remapped_summaries: List[CommunitySummary] = [] - if summaries: - for summary in summaries: - assert summary is not None, 'Why summary is None?' - new_summary_id = old_to_new_community_id.get(str(summary.id), summary.id) - remapped_summaries.append( - CommunitySummary( - summary=summary.summary, - id=new_summary_id, - ) - ) - - return entities, remapped_communities, remapped_summaries - - async def upsert_communities(self, communities: List[Community]) -> "Index": + async def upsert_communities(self, communities: List[Community]) -> "Index[NodeT, EdgeT]": """ Insert or update communities into KV storage. @@ -674,7 +530,7 @@ async def upsert_communities(self, communities: List[Community]) -> "Index": await self.community_kv_storage.index_done_callback() return self - async def upsert_summaries(self, summaries: List[CommunitySummary]) -> "Index": + async def upsert_summaries(self, summaries: List[CommunitySummary]) -> "Index[NodeT, EdgeT]": """ Insert or update community summaries into KV storage. @@ -685,43 +541,42 @@ async def upsert_summaries(self, summaries: List[CommunitySummary]) -> "Index": return self kv_data = {s.id: s.summary for s in summaries} - await self.community_summary_kv_storage.upsert(kv_data) + await self.community_summary_kv_storage.upsert(kv_data) # type: ignore await self.community_summary_kv_storage.index_done_callback() return self - async def delete_entities(self, entity_ids: List[str]) -> "Index": + async def delete_nodes(self, node_ids: List[str]) -> "Index[NodeT, EdgeT]": """ - Delete entities from graph and vector DB. + Delete nodes from graph and vector DB. - All relations connected to the deleted - entities are also removed from the relation vector DB. + All edges connected to the deleted nodes are also removed from the edge vector DB. - :param entity_ids: IDs of entities to delete. + :param node_ids: IDs of nodes to delete. :return: Self for method chaining. """ - if not entity_ids: + if not node_ids: return self - relations_by_node = await self.graph_backend.get_all_edges_for_nodes(entity_ids) - relation_ids = self._unique_relation_ids_from_grouped(relations_by_node) + edges_by_node = await self.graph_backend.get_all_edges_for_nodes(node_ids) + edge_ids = self._unique_edge_ids_from_grouped(edges_by_node) - await self.graph_backend.delete_nodes(entity_ids) - await self.entity_vector_db.delete(entity_ids) + await self.graph_backend.delete_nodes(node_ids) + await self.nodes_vector_db.delete(node_ids) - await self.relation_vector_db.delete(relation_ids) - await self.relation_vector_db.index_done_callback() + await self.edges_vector_db.delete(edge_ids) + await self.edges_vector_db.index_done_callback() await self.graph_backend.index_done_callback() - await self.entity_vector_db.index_done_callback() + await self.nodes_vector_db.index_done_callback() await self._update_reverse_indexes( - deleted_entity_ids=entity_ids, - deleted_relation_ids=relation_ids, + deleted_node_ids=node_ids, + deleted_edge_ids=edge_ids, ) return self - async def delete_relations(self, edge_specs: List[EdgeSpec]) -> "Index": + async def delete_edges(self, edge_specs: List[EdgeSpec]) -> "Index[NodeT, EdgeT]": """ - Delete relations from graph and vector DB. + Delete edges from graph and vector DB. :param edge_specs: List of edge specs ``(subject_id, object_id, relation_id)``. :return: Self for method chaining. @@ -729,21 +584,20 @@ async def delete_relations(self, edge_specs: List[EdgeSpec]) -> "Index": if not edge_specs: return self - # Fetch relation IDs for vector DB deletion before removing from graph - relations = await self.graph_backend.get_edges(edge_specs) - found_relation_ids = [r.id for r in relations if r is not None and r.id] + existing_edges = await self.graph_backend.get_edges(edge_specs) + found_edge_ids = [edge.id for edge in existing_edges if edge is not None] await self.graph_backend.delete_edges(edge_specs) - if found_relation_ids: - await self.relation_vector_db.delete(found_relation_ids) + if found_edge_ids: + await self.edges_vector_db.delete(found_edge_ids) await self.graph_backend.index_done_callback() - await self.relation_vector_db.index_done_callback() - await self._update_reverse_indexes(deleted_relation_ids=found_relation_ids) + await self.edges_vector_db.index_done_callback() + await self._update_reverse_indexes(deleted_edge_ids=found_edge_ids) return self - async def delete_chunks(self, chunk_ids: List[str]) -> "Index": + async def delete_chunks(self, chunk_ids: List[str]) -> "Index[NodeT, EdgeT]": """ Delete chunks from KV and vector storage. @@ -753,39 +607,48 @@ async def delete_chunks(self, chunk_ids: List[str]) -> "Index": if not chunk_ids: return self - affected_entities = await self._find_entities_by_chunk_ids(chunk_ids) - entity_ids = [e.id for e in affected_entities] - relation_ids = [] + affected_nodes = await self._find_nodes_by_chunk_ids(chunk_ids) + node_ids = [node.id for node in affected_nodes] + edge_ids = await self._find_edge_ids_by_chunk_ids(chunk_ids) + + if node_ids: + edges_by_node = await self.graph_backend.get_all_edges_for_nodes(node_ids) + edge_ids.extend(self._unique_edge_ids_from_grouped(edges_by_node)) - if entity_ids: - # Collect relation IDs before graph cascade - relations_by_node = await self.graph_backend.get_all_edges_for_nodes(entity_ids) - relation_ids = self._unique_relation_ids_from_grouped(relations_by_node) + edge_ids = list(dict.fromkeys(edge_ids)) + delete_specs: List[EdgeSpec] = [] + if edge_ids: + # TODO: remove full scan here + delete_specs = await self.get_edge_specs_by_ids(set(edge_ids)) + + if delete_specs: + await self.graph_backend.delete_edges(delete_specs) - await self.graph_backend.delete_nodes(entity_ids) - await self.entity_vector_db.delete(entity_ids) + if node_ids: + await self.graph_backend.delete_nodes(node_ids) + await self.nodes_vector_db.delete(node_ids) - if relation_ids: - await self.relation_vector_db.delete(relation_ids) + if edge_ids: + await self.edges_vector_db.delete(edge_ids) await self.chunks_kv_storage.delete(chunk_ids) - await self.chunk_vector_db.delete(chunk_ids) + await self.chunks_vector_db.delete(chunk_ids) await self.chunks_kv_storage.index_done_callback() - await self.chunk_vector_db.index_done_callback() - if entity_ids: + await self.chunks_vector_db.index_done_callback() + if node_ids: await self.graph_backend.index_done_callback() - await self.entity_vector_db.index_done_callback() - if relation_ids: - await self.relation_vector_db.index_done_callback() + await self.nodes_vector_db.index_done_callback() + if edge_ids: + await self.edges_vector_db.index_done_callback() await self._update_reverse_indexes( deleted_chunk_ids=chunk_ids, - deleted_entity_ids=entity_ids, - deleted_relation_ids=relation_ids, + deleted_node_ids=node_ids, + deleted_edge_ids=edge_ids, ) return self - async def delete_communities(self, community_ids: List[str]) -> "Index": + async def delete_communities(self, community_ids: List[str]) -> "Index[NodeT, EdgeT]": """ Delete communities and their summaries from KV storage. @@ -802,21 +665,21 @@ async def delete_communities(self, community_ids: List[str]) -> "Index": await self.community_summary_kv_storage.index_done_callback() return self - async def get_entities(self, entity_ids: List[str]) -> List[Optional[Entity]]: + async def get_nodes(self, node_ids: List[str]) -> List[Optional[NodeT]]: """ - Retrieve entities by their IDs. + Retrieve nodes by their IDs. - :param entity_ids: Entity IDs to fetch. - :return: List of entities (``None`` for missing). + :param node_ids: Node IDs to fetch. + :return: List of nodes (``None`` for missing). """ - return await self.graph_backend.get_nodes(entity_ids) + return await self.graph_backend.get_nodes(node_ids) - async def get_relations(self, edge_specs: List[EdgeSpec]) -> List[Optional[Relation]]: + async def get_edges(self, edge_specs: List[EdgeSpec]) -> List[Optional[EdgeT]]: """ - Retrieve relations by edge specs. + Retrieve edges by edge specs. :param edge_specs: List of edge specs ``(subject_id, object_id, relation_id)``. - :return: List of relations (``None`` for missing). + :return: List of edges (``None`` for missing). """ return await self.graph_backend.get_edges(edge_specs) @@ -854,11 +717,13 @@ async def get_communities(self, community_ids: List[str]) -> List[Optional[Commu entity_ids = community_dict.get("entity_ids", []) relation_id_set = set(community_dict.get("relation_ids", [])) - entities = await self.get_entities(entity_ids) - all_relations = await self.graph_backend.get_all_edges() - relations = [relation for relation in all_relations if relation and relation.id in relation_id_set] - entities = [entity for entity in entities if entity] - relations = [relation for relation in relations if relation] + nodes = await self.get_nodes(entity_ids) + + # TODO: remove full scan here + all_edges = await self.graph_backend.get_all_edges() + edges = [edge for edge in all_edges if edge and edge.id in relation_id_set] + entities = cast(List[Entity], [entity for entity in nodes if entity]) + relations = cast(List[Relation], [relation for relation in edges if relation]) communities.append(Community( id=community_id, @@ -870,235 +735,94 @@ async def get_communities(self, community_ids: List[str]) -> List[Optional[Commu return communities - - async def _validate_relation_endpoints_exist(self, relations: List[Relation]) -> None: + async def _validate_edge_endpoints_exist(self, edges: List[EdgeT]) -> None: """ - Validate that all relation endpoints exist as entities. + Validate that all edge endpoints exist as nodes. - :param relations: Relations whose subject/object IDs must exist as nodes. - :raises ValueError: If at least one referenced entity is missing. + :param edges: Edges whose subject/object IDs must exist as nodes. + :raises ValueError: If at least one referenced node is missing. """ - all_entity_ids: set[str] = set() - for relation in relations: - all_entity_ids.add(relation.subject_id) - all_entity_ids.add(relation.object_id) + all_node_ids: set[str] = set() + for edge in edges: + all_node_ids.add(edge.subject_id) + all_node_ids.add(edge.object_id) - existing_entities = await self.graph_backend.get_nodes(list(all_entity_ids)) - existing_ids = {entity.id for entity in existing_entities if entity is not None} - missing_ids = all_entity_ids - existing_ids + existing_nodes = await self.graph_backend.get_nodes(list(all_node_ids)) + existing_ids = {node.id for node in existing_nodes if node is not None} + missing_ids = all_node_ids - existing_ids if missing_ids: raise ValueError( - f"Cannot insert/update relations referencing non-existent entities: {missing_ids}" + f"Cannot insert/update edges referencing non-existent nodes: {missing_ids}" ) - async def _get_existing_relations_grouped_by_id( + async def get_edge_specs_by_ids( self, - relation_ids: Set[str], - ) -> Dict[str, List[Relation]]: - """ - Retrieve existing relations grouped by relation ID. - - :param relation_ids: Relation IDs to search in graph storage. - :return: Mapping from relation ID to all matching stored relations. - """ - if not relation_ids: - return {} - - all_relations = await self.graph_backend.get_all_edges() - grouped: Dict[str, List[Relation]] = defaultdict(list) - for relation in all_relations: - assert relation # this should not None due to input args typing - assert relation.id - if relation.id in relation_ids: - grouped[relation.id].append(relation) - return dict(grouped) - - @staticmethod - def _unique_description_fragments(descriptions: Iterable[str]) -> List[str]: - """ - Split descriptions into normalized fragments and keep first-seen unique ones. - - This prevents repeated sentence fragments when previously merged descriptions - are merged again with incremental upserts. - - :param descriptions: Description texts to split and normalize. - :return: Deduplicated description fragments in first-seen order. - """ - unique_parts: List[str] = [] - seen: set[str] = set() - - for description in descriptions: - text = (description or "").strip() - if not text: - continue - raw_parts = re.split(r"\n+|(?<=[.!?])\s+", text) - for part in raw_parts: - cleaned = re.sub(r"\s+", " ", part).strip() - if not cleaned: - continue - key = cleaned.casefold() - if key in seen: - continue - seen.add(key) - unique_parts.append(cleaned) - - return unique_parts - - @staticmethod - def _merge_entities(entity_groups: Dict[str, List[Entity]]) -> List[Entity]: + edge_ids: Set[str], + ) -> List[EdgeSpec]: """ - Merge entities sharing the same ID by combining descriptions and metadata. + Retrieve edge specs for existing edges by edge ID. - :param entity_groups: Mapping of entity ID to list of entities. - :return: One merged entity per ID. + :param edge_ids: Edge IDs to search in graph storage. + :return: Edge specs for matching edges. """ - merged: list[Entity] = [] - - for entities in entity_groups.values(): - if len(entities) == 1: - merged.append(entities[0]) - continue - - by_richness = sorted(entities, key=lambda e: len(e.source_chunk_id), reverse=True) - primary = by_richness[0] - - descriptions = Index._unique_description_fragments( - [e.description for e in by_richness] - ) - - all_chunks = set[str]() - all_docs = set[str]() - all_clusters: list[ClusterInfo] = [] - for e in by_richness: - all_chunks.update(e.source_chunk_id) - all_docs.update(e.documents_id) - all_clusters.extend(e.clusters) - - deduplicated_clusters: List[ClusterInfo] = [] - seen_cluster_keys: Set[tuple[int, int]] = set() - for cluster in all_clusters: - assert isinstance(cluster, dict) # or what is cluster? - try: - level = cluster['level'] - cluster_id = cluster['cluster_id'] - except (TypeError, ValueError): - continue - - cluster_key = (level, cluster_id) - if cluster_key in seen_cluster_keys: - continue - - seen_cluster_keys.add(cluster_key) - normalized_cluster: ClusterInfo = {"level": level, "cluster_id": cluster_id} - for key, value in cluster.items(): - if key not in normalized_cluster: - normalized_cluster[key] = value - deduplicated_clusters.append(normalized_cluster) - - merged.append(Entity( - id=primary.id, - entity_name=primary.entity_name, - entity_type=primary.entity_type, - description=" ".join(descriptions), - source_chunk_id=sorted(all_chunks), - documents_id=sorted(all_docs), - clusters=deduplicated_clusters, - )) - - return merged - - @staticmethod - def _merge_relations(relation_groups: Dict[str, List[Relation]]) -> List[Relation]: - """ - Merge duplicate relations by combining descriptions and averaging strength. - - For each group, the relation with the most source chunks becomes the primary. - Unique descriptions are concatenated; relation_strength is averaged; - source_chunk_ids are unioned. - - :param relation_groups: Mapping of relation ID to list of duplicates. - :return: One merged relation per group. - """ - merged: list[Relation] = [] - - for relations in relation_groups.values(): - if len(relations) == 1: - merged.append(relations[0]) - continue - - by_richness = sorted(relations, key=lambda r: len(r.source_chunk_id), reverse=True) - primary = by_richness[0] - - descriptions = Index._unique_description_fragments( - [r.description for r in by_richness] - ) - - avg_strength = sum(r.relation_strength for r in by_richness) / len(by_richness) - - all_chunks = set[str]() - for r in by_richness: - all_chunks.update(r.source_chunk_id) - - merged.append(Relation( - id=primary.id, - subject_id=primary.subject_id, - object_id=primary.object_id, - subject_name=primary.subject_name, - object_name=primary.object_name, - relation_type=primary.relation_type, - description=" ".join(descriptions), - relation_strength=avg_strength, - source_chunk_id=sorted(all_chunks), - )) - - return merged + if not edge_ids: + return [] + + all_edges = await self.graph_backend.get_all_edges() + edge_specs: List[EdgeSpec] = [] + for edge in all_edges: + assert edge + assert edge.id + if edge.id in edge_ids: + edge_specs.append((edge.subject_id, edge.object_id, edge.id)) + return edge_specs async def _update_reverse_indexes( self, - entities: Optional[List[Entity]] = None, - relations: Optional[List[Relation]] = None, - deleted_entity_ids: Optional[List[str]] = None, - deleted_relation_ids: Optional[List[str]] = None, + nodes: Optional[List[NodeT]] = None, + edges: Optional[List[EdgeT]] = None, + deleted_node_ids: Optional[List[str]] = None, + deleted_edge_ids: Optional[List[str]] = None, deleted_chunk_ids: Optional[List[str]] = None, ) -> None: """ Incrementally update reverse indexes from changed entities/relations. - :param entities: Upserted entities to add to chunk-to-entity index. - :param relations: Upserted relations to add to chunk-to-relation index. - :param deleted_entity_ids: Entity IDs removed from the graph. - :param deleted_relation_ids: Relation IDs removed from the graph. + :param nodes: Upserted nodes to add to chunk-to-node index. + :param edges: Upserted edges to add to chunk-to-edge index. + :param deleted_node_ids: Node IDs removed from the graph. + :param deleted_edge_ids: Edge IDs removed from the graph. :param deleted_chunk_ids: Chunk IDs removed from KV/vector storage. """ if deleted_chunk_ids: for chunk_id in deleted_chunk_ids: - self._chunk_to_entities.pop(chunk_id, None) - self._chunk_to_relations.pop(chunk_id, None) - - if deleted_entity_ids: - removed_entities = set(deleted_entity_ids) - for chunk_id, entity_ids in list(self._chunk_to_entities.items()): - entity_ids.difference_update(removed_entities) - if not entity_ids: - self._chunk_to_entities.pop(chunk_id, None) - - if deleted_relation_ids: - removed_relations = set(deleted_relation_ids) - for chunk_id, relation_ids in list(self._chunk_to_relations.items()): - relation_ids.difference_update(removed_relations) - if not relation_ids: - self._chunk_to_relations.pop(chunk_id, None) - - if entities: - entities_map = self._get_items_map(entities) - for chunk_id, entity_ids in entities_map.items(): - self._chunk_to_entities[chunk_id].update(entity_ids) - - if relations: - relations_map = self._get_items_map(relations) - for chunk_id, relation_ids in relations_map.items(): - self._chunk_to_relations[chunk_id].update(relation_ids) + self._chunk_to_nodes.pop(chunk_id, None) + self._chunk_to_edges.pop(chunk_id, None) + + if deleted_node_ids: + removed_nodes = set(deleted_node_ids) + for chunk_id, node_ids in list(self._chunk_to_nodes.items()): + node_ids.difference_update(removed_nodes) + if not node_ids: + self._chunk_to_nodes.pop(chunk_id, None) + + if deleted_edge_ids: + removed_edges = set(deleted_edge_ids) + for chunk_id, edge_ids in list(self._chunk_to_edges.items()): + edge_ids.difference_update(removed_edges) + if not edge_ids: + self._chunk_to_edges.pop(chunk_id, None) + + if nodes: + nodes_map = self._get_items_map(nodes) + for chunk_id, node_ids in nodes_map.items(): + self._chunk_to_nodes[chunk_id].update(node_ids) + + if edges: + edges_map = self._get_items_map(edges) + for chunk_id, edge_ids in edges_map.items(): + self._chunk_to_edges[chunk_id].update(edge_ids) async def _rebuild_reverse_indexes(self) -> None: """ @@ -1106,68 +830,89 @@ async def _rebuild_reverse_indexes(self) -> None: Used as fallback for cold-start consistency with preloaded graphs. """ - self._chunk_to_entities.clear() - self._chunk_to_relations.clear() + self._chunk_to_nodes.clear() + self._chunk_to_edges.clear() - all_entities: List[Entity] = await self.graph_backend.get_all_nodes() - all_relations: List[Relation] = await self.graph_backend.get_all_edges() + all_nodes: List[NodeT] = await self.graph_backend.get_all_nodes() + all_edges: List[EdgeT] = await self.graph_backend.get_all_edges() await self._update_reverse_indexes( - entities=all_entities, - relations=all_relations, + nodes=all_nodes, + edges=all_edges, ) - async def _find_entities_by_chunk_ids(self, chunk_ids: List[str]) -> List[Entity]: + async def _find_nodes_by_chunk_ids(self, chunk_ids: List[str]) -> List[NodeT]: """ - Find all entities referencing any of the given chunk IDs. + Find all nodes referencing any of the given chunk IDs. :param chunk_ids: Chunk identifiers. - :return: Entities referencing these chunks. + :return: Nodes referencing these chunks. """ - if not self._chunk_to_entities: + if not self._chunk_to_nodes: await self._rebuild_reverse_indexes() - entity_ids = set[str]() + node_ids = set[str]() for chunk_id in chunk_ids: - entity_ids.update(self._chunk_to_entities.get(chunk_id, set())) + node_ids.update(self._chunk_to_nodes.get(chunk_id, set())) + + nodes = await self.graph_backend.get_nodes(list(node_ids)) + return [node for node in nodes if node is not None] + + async def _find_edge_ids_by_chunk_ids(self, chunk_ids: List[str]) -> List[str]: + """ + Find all edge IDs referencing any of the given chunk IDs. + + :param chunk_ids: Chunk identifiers. + :return: Edge IDs referencing these chunks. + """ + if not self._chunk_to_edges: + await self._rebuild_reverse_indexes() + + edge_ids: List[str] = [] + seen: Set[str] = set() + for chunk_id in chunk_ids: + for edge_id in self._chunk_to_edges.get(chunk_id, set()): + if edge_id in seen: + continue + seen.add(edge_id) + edge_ids.append(edge_id) - entities = await self.graph_backend.get_nodes(list(entity_ids)) - return [e for e in entities if e is not None] + return edge_ids @staticmethod - def _get_items_map(items: List[Entity] | List[Relation]) -> Dict[str, List[str]]: + def _get_items_map(items: List[NodeT] | List[EdgeT]) -> Dict[str, List[str]]: """ Build reverse mapping chunk_id -> list of item IDs using source_chunk_id. - :param items: Entities or relations with ``id`` and ``source_chunk_id`` fields. - :return: Mapping from chunk ID to list of entity/relation IDs. + :param items: Nodes or edges with ``id`` and ``source_chunk_id`` fields. + :return: Mapping from chunk ID to list of node/edge IDs. """ chunks_map: Dict[str, List[str]] = defaultdict(list) for item in items: if not item or not item.id: continue - for chunk_id in getattr(item, "source_chunk_id", []): + for chunk_id in item.source_chunk_id: chunks_map[chunk_id].append(item.id) return dict(chunks_map) @staticmethod - def _unique_relation_ids_from_grouped(relations_by_node: List[List[Relation]]) -> List[str]: + def _unique_edge_ids_from_grouped(edges_by_node: List[List[EdgeT]]) -> List[str]: """ - Flatten grouped relations and return unique relation IDs (first-seen order). + Flatten grouped edges and return unique edge IDs (first-seen order). - :param relations_by_node: Relations grouped by source node. - :return: Unique relation IDs preserving first-seen order. + :param edges_by_node: Edges grouped by source node. + :return: Unique edge IDs preserving first-seen order. """ - relation_ids: List[str] = [] + edge_ids: List[str] = [] seen: Set[str] = set() - for relations in relations_by_node: - for relation in relations: - relation_id = getattr(relation, "id", None) - if not relation_id or relation_id in seen: + for edges in edges_by_node: + for edge in edges: + edge_id = getattr(edge, "id", None) + if not edge_id or edge_id in seen: continue - seen.add(relation_id) - relation_ids.append(relation_id) - return relation_ids + seen.add(edge_id) + edge_ids.append(edge_id) + return edge_ids @staticmethod def _build_storage_kwargs( @@ -1287,7 +1032,7 @@ async def check_consistency(self) -> ConsistencyReport: ) ) - entity_vector_ids = set(await self.entity_vector_db.get_all_ids()) + entity_vector_ids = set(await self.nodes_vector_db.get_all_ids()) missing_entity_vector_ids = sorted(all_entity_ids_from_graph - entity_vector_ids) if missing_entity_vector_ids: errors.append( @@ -1308,7 +1053,7 @@ async def check_consistency(self) -> ConsistencyReport: ) ) - relation_vector_ids = set(await self.relation_vector_db.get_all_ids()) + relation_vector_ids = set(await self.edges_vector_db.get_all_ids()) missing_relation_vector_ids = sorted(all_relation_ids_from_graph - relation_vector_ids) if missing_relation_vector_ids: errors.append( @@ -1329,10 +1074,10 @@ async def check_consistency(self) -> ConsistencyReport: ) ) - chunks_vdb_ids = await self.chunk_vector_db.get_all_ids() + chunks_vdb_ids = await self.chunks_vector_db.get_all_ids() - # Empty ids means that we didn't vectorize chunks at all, that is valid if we don't want to use naive search. - if not chunks_vdb_ids: + # Empty chunk vectors are only valid when chunk storage is also empty. + if not chunks_vdb_ids and not all_chunk_ids_from_storage: return ConsistencyReport(errors=errors) chunk_vector_ids = set(chunks_vdb_ids) diff --git a/ragu/graph/knowledge_graph.py b/ragu/graph/knowledge_graph.py index bbb4c33..788011c 100644 --- a/ragu/graph/knowledge_graph.py +++ b/ragu/graph/knowledge_graph.py @@ -1,6 +1,8 @@ from __future__ import annotations -from typing import List, Optional +import re +from collections import defaultdict +from typing import Dict, Iterable, List, Optional, Set from ragu.chunker.base_chunker import BaseChunker from ragu.chunker.types import Chunk @@ -12,15 +14,164 @@ BuilderArguments, GraphBuilderModule ) -from ragu.graph.types import Entity, Relation, CommunitySummary +from ragu.graph.types import Community, Entity, Relation, CommunitySummary from ragu.models.embedder import Embedder from ragu.models.sparse_embedder import SparseEmbedder from ragu.models.llm import LLM from ragu.graph.index import Index, StorageArguments +from ragu.storage.types import ClusterInfo from ragu.triplet.base_artifact_extractor import BaseArtifactExtractor from ragu.storage.base_storage import EdgeSpec +def _duplicate_ids(items: List[Entity] | List[Relation]) -> List[str]: + counts: Dict[str, int] = defaultdict(int) + for item in items: + assert item.id + counts[item.id] += 1 + return [item_id for item_id, count in counts.items() if count > 1] + + +def _unique_description_fragments(descriptions: Iterable[str]) -> List[str]: + """ + Split descriptions into normalized fragments and keep first-seen unique ones. + + This prevents repeated sentence fragments when previously merged descriptions + are merged again with incremental upserts. + + :param descriptions: Description texts to split and normalize. + :return: Deduplicated description fragments in first-seen order. + """ + unique_parts: List[str] = [] + seen: set[str] = set() + + for description in descriptions: + text = (description or "").strip() + if not text: + continue + raw_parts = re.split(r"\n+|(?<=[.!?])\s+", text) + for part in raw_parts: + cleaned = re.sub(r"\s+", " ", part).strip() + if not cleaned: + continue + key = cleaned.casefold() + if key in seen: + continue + seen.add(key) + unique_parts.append(cleaned) + + return unique_parts + + +def default_merge_entities_policy(entities: List[Entity]) -> Entity: + """ + Default merge policy for entities (class Entity). + Default policy = concatenate unique descriptions, merge source_chunk_ids, docs ids and clusters. + + :param entities: Entities to merge. + :return: Merged entities. + """ + if len(entities) == 0: + raise ValueError("Cannot merge empty entity list") + + if len(entities) == 1: + return entities[0] + + entity_ids = {entity.id for entity in entities} + if len(entity_ids) != 1: + raise ValueError(f"Cannot merge entities with different IDs: {sorted(entity_ids)}") + + by_richness = sorted(entities, key=lambda e: len(e.source_chunk_id), reverse=True) + primary = by_richness[0] + + descriptions = _unique_description_fragments( + [entity.description for entity in by_richness] + ) + + all_chunks: set[str] = set() + all_docs: set[str] = set() + all_clusters: list[ClusterInfo] = [] + for entity in by_richness: + all_chunks.update(entity.source_chunk_id) + all_docs.update(entity.documents_id) + all_clusters.extend(entity.clusters) + + deduplicated_clusters: List[ClusterInfo] = [] + seen_cluster_keys: Set[tuple[int, int]] = set() + for cluster in all_clusters: + assert isinstance(cluster, dict) + try: + level = cluster["level"] + cluster_id = cluster["cluster_id"] + except (TypeError, ValueError): + continue + + cluster_key = (level, cluster_id) + if cluster_key in seen_cluster_keys: + continue + + seen_cluster_keys.add(cluster_key) + normalized_cluster: ClusterInfo = {"level": level, "cluster_id": cluster_id} + for key, value in cluster.items(): + if key not in normalized_cluster: + normalized_cluster[key] = value + deduplicated_clusters.append(normalized_cluster) + + return Entity( + id=primary.id, + entity_name=primary.entity_name, + entity_type=primary.entity_type, + description=" ".join(descriptions), + source_chunk_id=sorted(all_chunks), + documents_id=sorted(all_docs), + clusters=deduplicated_clusters, + ) + +def default_merge_relations_policy(relations: List[Relation]) -> Relation: + """ + Default merge policy for relations (class Relation). + Default policy = concatenate descriptions, merge source_chunk_ids, docs ids and clusters. + + :param entities: Relations to merge. + :return: New single relation. + """ + if len(relations) == 0: + raise ValueError("Cannot merge empty relation list") + + if len(relations) == 1: + return relations[0] + + relation_ids = {relation.id for relation in relations} + if len(relation_ids) != 1: + raise ValueError(f"Cannot merge relations with different IDs: {sorted(relation_ids)}") + + by_richness = sorted(relations, key=lambda r: len(r.source_chunk_id), reverse=True) + primary = by_richness[0] + + descriptions = _unique_description_fragments( + [relation.description for relation in by_richness] + ) + + avg_strength = sum(relation.relation_strength for relation in by_richness) / len(by_richness) + + all_chunks: set[str] = set() + for relation in by_richness: + all_chunks.update(relation.source_chunk_id) + + return Relation( + id=primary.id, + subject_id=primary.subject_id, + object_id=primary.object_id, + subject_name=primary.subject_name, + object_name=primary.object_name, + relation_type=primary.relation_type, + description=" ".join(descriptions), + relation_strength=avg_strength, + source_chunk_id=sorted(all_chunks), + ) + + + class KnowledgeGraph: """ High-level facade for building, storing, and querying a knowledge graph. @@ -82,10 +233,12 @@ def __init__( language=self.language, ) # Store graph - self.index = Index( + self.index: Index[Entity, Relation] = Index( embedder=embedder, sparse_embedder=sparse_embedder, arguments=self.storage_settings, + node_t=Entity, + edge_t=Relation, ) self.make_community_summary = self.builder_settings.make_community_summary @@ -120,143 +273,148 @@ async def build_from_docs(self, docs: List[str]) -> "KnowledgeGraph": should_store_communities = self.make_community_summary and not is_vector_only if should_store_communities and communities: - entities, communities, summaries = await self.index.reindex_cluster_ids( + entities, communities, summaries = await self._reindex_cluster_ids( entities, communities, summaries, ) if not is_vector_only: - await self.index.insert_entities(entities) - await self.index.insert_relations(relations) + await self.upsert_entities(entities) + await self.upsert_relations(relations) await self.index.upsert_chunks(chunks) if should_store_communities: await self.index.upsert_communities(communities) - await self.index.upsert_summaries(summaries) + await self.upsert_summaries(summaries) return self - async def insert_entities(self, entities: Entity | List[Entity]) -> "KnowledgeGraph": + async def upsert_entities(self, entities: List[Entity]) -> "KnowledgeGraph": """ - Add one or more entities to the knowledge graph. + Add entities to the knowledge graph. - Entities with duplicate (name, type) will be automatically merged. + Existing stored entities with matching IDs are merged before storage. + Duplicate IDs within the same request are rejected. :param entities: Single entity or list of entities to add. :return: Self for method chaining. """ - if isinstance(entities, Entity): - entities = [entities] - - await self.index.insert_entities(entities) + duplicate_ids = _duplicate_ids(entities) + if duplicate_ids: + raise ValueError(f"Cannot insert duplicated entity IDs in one request: {duplicate_ids}") + + existing_entities = await self.index.get_nodes([entity.id for entity in entities]) + existing_by_id = { + entity.id: entity + for entity in existing_entities + if entity is not None + } + + merged_entities: List[Entity] = [] + for entity in entities: + existing = existing_by_id.get(entity.id) + if existing is not None: + merged_entities.append(default_merge_entities_policy([entity, existing])) + else: + merged_entities.append(entity) + + await self.index.upsert_nodes(merged_entities) return self - async def update_entities(self, entities: Entity | List[Entity]) -> "KnowledgeGraph": + async def update_entities(self, entities: List[Entity]) -> "KnowledgeGraph": """ Replace one or more existing entities by ID. :param entities: Single entity or list of entities to replace. :return: Self for method chaining. """ - if isinstance(entities, Entity): - entities = [entities] + duplicate_ids = _duplicate_ids(entities) + if duplicate_ids: + raise ValueError(f"Cannot update duplicated entity IDs in one request: {duplicate_ids}") - await self.index.update_entities(entities) + await self.index.update_nodes(entities) return self - async def add_entity(self, entities: Entity | List[Entity]) -> "KnowledgeGraph": + async def get_entities(self, entity_ids: List[str]) -> List[Entity | None]: """ - Backward-compatible alias for :meth:`insert_entities`. + Retrieve one or more entities by ID in one batched operation. - :param entities: Single entity or list of entities to add. - :return: Self for method chaining. + :param entity_ids: Entity identifier or identifiers. + :return: Matching entity or list of entities, preserving input order and + using ``None`` for missing IDs. """ - await self.insert_entities(entities) - return self + return await self.index.get_nodes(entity_ids) - async def get_entity(self, entity_id) -> Entity | None: - """ - Retrieve one entity by ID. - - :param entity_id: Entity identifier. - :return: Entity if found, otherwise ``None``. - """ - entities = await self.index.graph_backend.get_nodes([entity_id]) - return entities[0] if entities else None - - async def delete_entity(self, entity_id: str) -> "KnowledgeGraph": + async def delete_entities(self, entity_ids: List[str]) -> "KnowledgeGraph": """ Delete an entity from the knowledge graph. - :param entity_id: ID of the entity to delete. - :return: Self for method chaining. - """ - await self.index.delete_entities([entity_id]) - return self - - async def update_entity(self, entity_id: str, new_entity: Entity) -> "KnowledgeGraph": - """ - Replace an entity's data while keeping its ID and graph connections. - - :param entity_id: ID of the entity to update. - :param new_entity: Entity with updated fields. + :param entity_ids: ID of the entity to delete. :return: Self for method chaining. - :raises ValueError: If the entity does not exist. """ - existing = await self.index.graph_backend.get_nodes([entity_id]) - if not existing or existing[0] is None: - raise ValueError(f"Entity '{entity_id}' does not exist") - - new_entity.id = entity_id - await self.update_entities([new_entity]) + await self.index.delete_nodes(entity_ids) return self - async def insert_relations(self, relation: Relation | List[Relation]) -> "KnowledgeGraph": + async def upsert_relations(self, relations: List[Relation]) -> "KnowledgeGraph": """ Add one or more relations to the knowledge graph. - Relations with duplicate IDs will be automatically merged. - Validates that referenced entities exist. + Existing stored relations with matching IDs are merged before storage. + Duplicate IDs within the same request are rejected. - :param relation: Single relation or list of relations to add. + :param relations: Relations to add. :return: Self for method chaining. """ - if isinstance(relation, Relation): - relation = [relation] - - await self.index.insert_relations(relation) + for item in relations: + if not item.id: + raise ValueError("Cannot insert relation without id") + duplicate_ids = _duplicate_ids(relations) + if duplicate_ids: + raise ValueError(f"Cannot insert duplicated relation IDs in one request: {duplicate_ids}") + + edge_specs = [ + (relation.subject_id, relation.object_id, relation.id) + for relation in relations + ] + existing_relations = await self.index.get_edges(edge_specs) + + merged_relations: List[Relation] = [] + for item, existing_relation in zip(relations, existing_relations): + if existing_relation is not None: + merged_relations.append(default_merge_relations_policy([item, existing_relation])) + else: + merged_relations.append(item) + + await self.index.upsert_edges(merged_relations) return self - async def update_relations(self, relation: Relation | List[Relation]) -> "KnowledgeGraph": + async def update_relations(self, relations: List[Relation]) -> "KnowledgeGraph": """ Replace one or more existing relations by ID. - :param relation: Single relation or list of relations to replace. + :param relations: Relations to replace. :return: Self for method chaining. """ - if isinstance(relation, Relation): - relation = [relation] + for item in relations: + if not item.id: + raise ValueError("Cannot update relation without id") + duplicate_ids = _duplicate_ids(relations) + if duplicate_ids: + raise ValueError(f"Cannot update duplicated relation IDs in one request: {duplicate_ids}") - await self.index.update_relations(relation) + await self.index.update_edges(relations) return self - async def delete_relation( - self, - subject_id: str, - object_id: str, - relation_id: str | None = None, - ) -> "KnowledgeGraph": + async def delete_relations(self, edge_specs: List[EdgeSpec]) -> "KnowledgeGraph": """ - Delete a relation from the knowledge graph. + Delete relations from the knowledge graph. - :param subject_id: Subject entity ID. - :param object_id: Object entity ID. - :param relation_id: Optional relation ID for precise delete. + :param edge_specs: Edge specifications ``(subject_id, object_id, relation_id)``. :return: Self for method chaining. """ - await self.index.delete_relations([(subject_id, object_id, relation_id)]) + await self.index.delete_edges(edge_specs) return self async def edges_degrees(self, edge_specs: List[EdgeSpec]) -> List[int]: @@ -271,51 +429,70 @@ async def edges_degrees(self, edge_specs: List[EdgeSpec]) -> List[int]: """ return await self.index.graph_backend.edges_degrees(edge_specs) - async def add_summary(self, summary: CommunitySummary | List[CommunitySummary]) -> "KnowledgeGraph": + async def get_relations(self, edge_specs: List[EdgeSpec]) -> List[Relation | None]: """ - Add one or more community summaries. + Retrieve one or more relations by edge spec in one batched operation. - :param summary: Single summary or list of summaries to add. - :return: Self for method chaining. + :param edge_specs: One edge spec or a list of edge specs. + :return: Matching relation or list of relations, preserving input order + and using ``None`` for missing edges. """ - if isinstance(summary, CommunitySummary): - summary = [summary] - await self.index.upsert_summaries(summary) - return self + return await self.index.get_edges(edge_specs) - async def get_summary(self, summary_id: str) -> CommunitySummary | None: + async def get_chunks(self, chunk_ids: List[str]) -> List[Chunk | None]: """ - Retrieve a community summary by ID. + Retrieve one or more chunks by ID in one batched operation. - :param summary_id: ID of the summary to retrieve. - :return: The summary, or ``None`` if not found. + :param chunk_ids: Chunk identifier or identifiers. + :return: Matching chunk or list of chunks, preserving input order and + using ``None`` for missing IDs. """ - result = await self.index.community_summary_kv_storage.get_by_id(summary_id) - if result is None: - return None - return CommunitySummary(id=summary_id, summary=result) + return await self.index.get_chunks(chunk_ids) - async def delete_summary(self, summary_id: str) -> "KnowledgeGraph": + async def get_communities(self, community_ids: List[str]) -> List[Community | None]: """ - Delete a community summary. + Retrieve one or more communities by ID in one batched operation. - :param summary_id: ID of the summary to delete. + :param community_ids: Community identifier or identifiers. + :return: Matching community or list of communities, preserving input + order and using ``None`` for missing IDs. + """ + return await self.index.get_communities(community_ids) + + async def upsert_summaries(self, summaries: List[CommunitySummary]) -> "KnowledgeGraph": + """ + Add or replace community summaries. + + :param summaries: Summaries to upsert. :return: Self for method chaining. """ - await self.index.community_summary_kv_storage.delete([summary_id]) - await self.index.community_summary_kv_storage.index_done_callback() + await self.index.upsert_summaries(summaries) return self - async def update_summary(self, summary_id: str, new_summary: CommunitySummary) -> "KnowledgeGraph": + async def get_summaries(self, summary_ids: List[str]) -> List[CommunitySummary | None]: + """ + Retrieve one or more community summaries by ID in one batched operation. + + :param summary_ids: Summary identifier or identifiers. + :return: Matching summary or list of summaries, preserving input order + and using ``None`` for missing IDs. + """ + results = await self.index.community_summary_kv_storage.get_by_ids(summary_ids) + summaries = [ + None if summary is None else CommunitySummary(id=summary_id, summary=summary) + for summary_id, summary in zip(summary_ids, results) + ] + return summaries + + async def delete_summaries(self, summary_ids: List[str]) -> "KnowledgeGraph": """ - Replace a community summary's content. + Delete community summaries. - :param summary_id: ID of the summary to update. - :param new_summary: Summary with updated content. + :param summary_ids: IDs of the summaries to delete. :return: Self for method chaining. """ - new_summary.id = summary_id - await self.index.upsert_summaries([new_summary]) + await self.index.community_summary_kv_storage.delete(summary_ids) + await self.index.community_summary_kv_storage.index_done_callback() return self async def _deduplicate_chunks_by_id(self, chunks: List[Chunk]) -> List[Chunk]: @@ -350,7 +527,6 @@ async def _deduplicate_chunks_by_id(self, chunks: List[Chunk]) -> List[Chunk]: return unique_chunks - @property async def reindex_community(self) -> "KnowledgeGraph": if not self.pipeline.community_summarizer: raise ValueError() @@ -378,12 +554,133 @@ async def reindex_community(self) -> "KnowledgeGraph": summaries = await self.pipeline.community_summarizer.summarize(communities=communities) await self.index.upsert_communities(communities) - await self.index.upsert_summaries(summaries) + await self.upsert_summaries(summaries) return self - async def reindex_descriptions(self)-> "KnowledgeGraph": - ... + async def reindex_descriptions(self) -> "KnowledgeGraph": + assert self.pipeline.entity_summarizer is not None + all_entities = await self.index.graph_backend.get_all_nodes() + entities = await self.pipeline.entity_summarizer.run(entities=all_entities) + + all_relations = await self.index.graph_backend.get_all_edges() + relations = await self.pipeline.relation_summarizer.run(relations=all_relations) + + await self.index.graph_backend.index_start_callback() + await self.index.graph_backend.delete_nodes([e.id for e in all_entities]) + await self.index.graph_backend.delete_edges([(r.subject_id, r.object_id, r.id) for r in all_relations]) + await self.index.graph_backend.upsert_nodes(entities) + await self.index.graph_backend.upsert_edges(relations) + await self.index.graph_backend.index_done_callback() async def reindex_graph(self) -> "KnowledgeGraph": - return await (await self.reindex_descriptions()).reindex_community + return await (await self.reindex_descriptions()).reindex_community() + + async def _reindex_cluster_ids( + self, + entities: List[Entity], + communities: List[Community], + summaries: Optional[List[CommunitySummary]] = None, + ) -> Tuple[List[Entity], List[Community], List[CommunitySummary]]: + """ + Remap cluster IDs to be globally unique per level across indexing runs. + + Levels are preserved to keep level-based filtering intact. + + :param entities: Entities whose cluster memberships should be remapped. + :param communities: Newly generated communities with local cluster IDs. + :param summaries: Optional summaries linked to community IDs. + :return: Tuple with remapped entities, remapped communities, and remapped summaries. + """ + if not communities: + return entities, communities, summaries or [] + + existing_keys = await self.index.community_kv_storage.all_keys() + existing_data = await self.index.community_kv_storage.get_by_ids(existing_keys) if existing_keys else [] + + max_cluster_id_by_level: Dict[int, int] = defaultdict(lambda: -1) + for row in existing_data: + if not row: + continue + try: + level = int(row.get("level")) + cluster_id = int(row.get("cluster_id")) + except (TypeError, ValueError): + continue + if cluster_id > max_cluster_id_by_level[level]: + max_cluster_id_by_level[level] = cluster_id + + local_ids_by_level: Dict[int, List[int]] = defaultdict(list) + for community in communities: + level = int(community.level) + cluster_id = int(community.cluster_id) + local_ids_by_level[level].append(cluster_id) + + local_to_global: Dict[tuple[int, int], int] = {} + for level, local_ids in local_ids_by_level.items(): + for local_cluster_id in sorted(set(local_ids)): + max_cluster_id_by_level[level] += 1 + local_to_global[(level, local_cluster_id)] = max_cluster_id_by_level[level] + + old_to_new_community_id: Dict[str, str] = {} + remapped_communities: List[Community] = [] + for community in communities: + level = int(community.level) + local_cluster_id = int(community.cluster_id) + global_cluster_id = local_to_global[(level, local_cluster_id)] + + remapped_community = Community( + level=level, + cluster_id=global_cluster_id, + entities=community.entities, + relations=community.relations, + ) + if community.id: + old_to_new_community_id[str(community.id)] = str(remapped_community.id) + remapped_communities.append(remapped_community) + + valid_cluster_pairs = { + (int(community.level), int(community.cluster_id)) + for community in remapped_communities + } + + for entity in entities: + remapped_memberships: List[ClusterInfo] = [] + seen_memberships: Set[tuple[int, int]] = set() + for membership in entity.clusters: + assert isinstance(membership, dict), f"What is membership? {membership}" + try: + level = membership["level"] + local_cluster_id = membership["cluster_id"] + except (TypeError, ValueError): + continue + + global_cluster_id = local_to_global.get((level, local_cluster_id), local_cluster_id) + if (level, global_cluster_id) not in valid_cluster_pairs: + continue + + membership_key = (level, global_cluster_id) + if membership_key in seen_memberships: + continue + + seen_memberships.add(membership_key) + remapped_memberships.append({ + "level": level, + "cluster_id": global_cluster_id, + }) + entity.clusters = remapped_memberships + + remapped_summaries: List[CommunitySummary] = [] + if summaries: + for summary in summaries: + new_summary_id = old_to_new_community_id.get(str(summary.id), summary.id) + remapped_summaries.append( + CommunitySummary( + summary=summary.summary, + id=new_summary_id, + ) + ) + + return entities, remapped_communities, remapped_summaries + +__all__ = ["KnowledgeGraph"] From 987d18b6011fe9296f69a957bb29d29183ec74aa Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Wed, 22 Apr 2026 23:00:33 +0700 Subject: [PATCH 19/42] Minor update --- ragu/search_engine/search_functional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ragu/search_engine/search_functional.py b/ragu/search_engine/search_functional.py index 3c02fce..7919ac6 100644 --- a/ragu/search_engine/search_functional.py +++ b/ragu/search_engine/search_functional.py @@ -69,7 +69,7 @@ async def _find_most_related_text_unit_from_entities( elif relation.object_id == seed_id: neighbor_ids.append(relation.subject_id) neighbor_ids = list(dict.fromkeys(neighbor_ids)) - neighbors = await knowledge_graph.index.get_entities(neighbor_ids) + neighbors = await knowledge_graph.index.get_nodes(neighbor_ids) all_one_hop_text_units_lookup = { neighbor.id : neighbor.source_chunk_id for neighbor in neighbors if neighbor is not None From 456c78afa353cf048b9b827867cd8ccdecf2d246 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Wed, 22 Apr 2026 23:01:30 +0700 Subject: [PATCH 20/42] Refactor get_edges --- .../networkx_adapter.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/ragu/storage/graph_storage_adapters/networkx_adapter.py b/ragu/storage/graph_storage_adapters/networkx_adapter.py index 211d9da..c44cae8 100644 --- a/ragu/storage/graph_storage_adapters/networkx_adapter.py +++ b/ragu/storage/graph_storage_adapters/networkx_adapter.py @@ -186,20 +186,24 @@ async def get_edges(self, edge_specs: List[EdgeSpec]) -> List[Optional[EdgeT]]: results.append(None) continue - matches = self._graph.get_edge_data(u, v) - if key: - matches = [matches.get(key, {})] - else: - matches = list(matches.values()) + matches = self._graph.get_edge_data(u, v, default={}) + if key is not None: + edge_data = matches.get(key) + if edge_data is None: + results.append(None) + continue + + payload = dict(edge_data) # type: ignore + payload.pop("id", None) + results.append(self._edge_cls(subject_id=u, object_id=v, id=key, **payload)) + continue - for edge_data in matches: + for match_key, edge_data in matches.items(): if not edge_data: continue payload = dict(edge_data) - payload["subject_id"] = u - payload["object_id"] = v - edge = self._edge_cls(**payload) - results.append(edge) + payload.pop("id", None) + results.append(self._edge_cls(subject_id=u, object_id=v, id=match_key, **payload)) return results @override From e96561ff372c4ce0d11b5f52c2b992ef106b7f55 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Wed, 22 Apr 2026 23:02:22 +0700 Subject: [PATCH 21/42] Update public graph API usage --- ragu/graph/graph_retrieve_backend.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ragu/graph/graph_retrieve_backend.py b/ragu/graph/graph_retrieve_backend.py index f22142e..02fc468 100644 --- a/ragu/graph/graph_retrieve_backend.py +++ b/ragu/graph/graph_retrieve_backend.py @@ -43,12 +43,12 @@ async def query_entities(self, query: str, top_k: int = 20) -> List[Entity]: :return: Matching entities ordered by relevance. """ point = await self.build_query_vectors(query) - results = await self.knowledge_graph.index.entity_vector_db.query( + results = await self.knowledge_graph.index.nodes_vector_db.query( point, top_k=top_k, ) entity_ids = [result.id for result in results] - entities = await self.knowledge_graph.index.get_entities(entity_ids) + entities = await self.knowledge_graph.index.get_nodes(entity_ids) return [entity for entity in entities if entity is not None] async def query_relations(self, query: str, top_k: int = 20) -> List[Relation]: @@ -60,22 +60,22 @@ async def query_relations(self, query: str, top_k: int = 20) -> List[Relation]: :return: Matching relations ordered by relevance. """ point = await self.build_query_vectors(query) - results = await self.knowledge_graph.index.relation_vector_db.query( + results = await self.knowledge_graph.index.edges_vector_db.query( point, top_k=top_k, ) edge_specs: List[EdgeSpec] = [ ( - str(result.metadata.get("subject")), - str(result.metadata.get("object")), + str(result.metadata.get("subject_id")), + str(result.metadata.get("object_id")), result.id, ) for result in results - if result.metadata.get("subject") and result.metadata.get("object") + if result.metadata.get("subject_id") and result.metadata.get("object_id") ] if not edge_specs: return [] - relations = await self.knowledge_graph.index.get_relations(edge_specs) + relations = await self.knowledge_graph.index.get_edges(edge_specs) return [relation for relation in relations if relation is not None] async def find_similar_entities(self, entity: Entity, top_k: int = 10) -> List[Entity]: @@ -108,7 +108,7 @@ async def query_chunk_hits(self, query: str, top_k: int = 20) -> List[EmbeddingH :return: Ranked chunk hits with metadata. """ point = await self.build_query_vectors(query) - return await self.knowledge_graph.index.chunk_vector_db.query( + return await self.knowledge_graph.index.chunks_vector_db.query( point=point, top_k=top_k, ) From cb8e9aaa3f4b74f1d5b190a3dddadcf4b1f73090 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Wed, 22 Apr 2026 23:52:28 +0700 Subject: [PATCH 22/42] Remove useless classes --- ragu/graph/types.py | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/ragu/graph/types.py b/ragu/graph/types.py index e9b309b..9783a69 100644 --- a/ragu/graph/types.py +++ b/ragu/graph/types.py @@ -12,9 +12,6 @@ • **Entity**, **Relation** — primary units representing semantic nodes and edges within the knowledge graph. -• **EntityEmbedding**, **RelationEmbedding** — hold numeric vector - representations used for embedding-based retrieval and similarity search. - • **Community**, **CommunitySummary** — represent clustered subgraphs (communities) and their human-readable summaries produced during community detection or summarization. @@ -23,8 +20,6 @@ from typing import List from dataclasses import dataclass, field -import numpy as np - from ragu.storage.types import ClusterInfo, Edge, Node from ragu.utils.ragu_utils import compute_mdhash_id @@ -69,20 +64,6 @@ def to_text(self): return f"{self.entity_name} - {self.description}" -@dataclass(slots=True) -class EntityEmbedding: - """ - Stores vector embeddings for a single entity. - - :param id: ID corresponding to the associated :class:`Entity`. - :param name_embedding: Embedding vector for the entity name. - :param description_embedding: Embedding vector for the entity description. - """ - id: str - name_embedding: np.ndarray | None = None - description_embedding: np.ndarray | None = None, - - @dataclass(slots=True) class Relation(Edge): """ @@ -123,18 +104,6 @@ def to_text(self): return f"{self.description}" -@dataclass(slots=True) -class RelationEmbedding: - """ - Stores a vector embedding for a relation. - - :param id: ID corresponding to the associated :class:`Relation`. - :param embedding: Embedding vector capturing the relation’s semantic meaning. - """ - id: str - embedding: np.ndarray | None = None, - - @dataclass(slots=True) class Community: """ From 4bb669e811c392e2cf385d94bfbf13da049abbf5 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Thu, 23 Apr 2026 00:56:24 +0700 Subject: [PATCH 23/42] Add text truncation for embedders and llms --- ragu/graph/index.py | 13 +++++++++---- ragu/graph/knowledge_graph.py | 26 +++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/ragu/graph/index.py b/ragu/graph/index.py index 2c43104..b23569a 100644 --- a/ragu/graph/index.py +++ b/ragu/graph/index.py @@ -22,6 +22,7 @@ from ragu.storage.kv_storage_adapters.json_storage import JsonKVStorage from ragu.storage.types import Node, Edge, Point from ragu.storage.vdb_storage_adapters.nano_vdb import NanoVectorDBStorage +from ragu.utils.token_truncation import TokenTruncation @dataclass @@ -114,6 +115,7 @@ def __init__( sparse_embedder: SparseEmbedder | None = None, node_t: Type[NodeT] = Entity, edge_t: Type[EdgeT] = Relation, + context_truncator: TokenTruncation | None = None, ): """ Initialize storage backends and in-memory reverse indexes. @@ -127,6 +129,9 @@ def __init__( self.embedder = embedder self.sparse_embedder = sparse_embedder + # If truncator is not set, just return all text as is + self._context_truncator = context_truncator or (lambda x: str(x)) + # Reverse indexes for cascade operations self._chunk_to_nodes: Dict[str, Set[str]] = defaultdict(set) self._chunk_to_edges: Dict[str, Set[str]] = defaultdict(set) @@ -241,7 +246,7 @@ async def upsert_nodes(self, nodes: List[NodeT]) -> "Index[NodeT, EdgeT]": nodes_to_upsert = [group[0] for group in incoming_by_id.values()] dense_embeddings = await self.embedder.batch_embed_text( - [node.to_text() for node in nodes_to_upsert], + [self._context_truncator(node.to_text()) for node in nodes_to_upsert], desc="Nodes vectorization", ) sparse_embeddings = self.sparse_embedder.embed_document( @@ -302,7 +307,7 @@ async def update_nodes(self, nodes: List[NodeT]) -> "Index[NodeT, EdgeT]": nodes_to_update = [group[0] for group in incoming_by_id.values()] dense_embeddings = await self.embedder.batch_embed_text( - [node.to_text() for node in nodes_to_update], + [self._context_truncator(node.to_text()) for node in nodes_to_update], desc="Nodes vectorization", ) sparse_embeddings = self.sparse_embedder.embed_document( @@ -366,7 +371,7 @@ async def upsert_edges(self, edges: List[EdgeT]) -> "Index[NodeT, EdgeT]": ] dense_embeddings = await self.embedder.batch_embed_text( - [edge.to_text() for edge in edges_to_upsert], + [self._context_truncator(edge.to_text()) for edge in edges_to_upsert], desc="Edges vectorization", ) sparse_embeddings = self.sparse_embedder.embed_document( @@ -439,7 +444,7 @@ async def update_edges(self, edges: List[EdgeT]) -> "Index[NodeT, EdgeT]": await self._validate_edge_endpoints_exist(edges_to_update) dense_embeddings = await self.embedder.batch_embed_text( - [edge.to_text() for edge in edges_to_update], + [self._context_truncator(edge.to_text()) for edge in edges_to_update], desc="Edges vectorization", ) sparse_embeddings = self.sparse_embedder.embed_document( diff --git a/ragu/graph/knowledge_graph.py b/ragu/graph/knowledge_graph.py index 788011c..125a20b 100644 --- a/ragu/graph/knowledge_graph.py +++ b/ragu/graph/knowledge_graph.py @@ -2,7 +2,7 @@ import re from collections import defaultdict -from typing import Dict, Iterable, List, Optional, Set +from typing import Dict, Iterable, List, Optional, Set, Literal from ragu.chunker.base_chunker import BaseChunker from ragu.chunker.types import Chunk @@ -22,6 +22,7 @@ from ragu.storage.types import ClusterInfo from ragu.triplet.base_artifact_extractor import BaseArtifactExtractor from ragu.storage.base_storage import EdgeSpec +from ragu.utils.token_truncation import TokenTruncation def _duplicate_ids(items: List[Entity] | List[Relation]) -> List[str]: @@ -197,6 +198,11 @@ def __init__( storage_settings: Optional[StorageArguments] = None, additional_modules: Optional[List[GraphBuilderModule]] = None, language: Optional[str] = None, + llm_token_limit: int = 32_798, + embedder_token_limit: int = 8_192, + tokenizer_backend: Literal["tiktoken", "local"] = "tiktoken", + tokenizer_llm_name: str = "gpt-4o", + tokenizer_embedder_name: str = "text-embedding-3-large", ): """ Initialize KnowledgeGraph with pipeline and storage components. @@ -210,6 +216,11 @@ def __init__( :param storage_settings: Optional storage backend settings. :param additional_modules: Optional post-processing modules for graph items. :param language: Optional language override. Defaults to ``Settings.language``. + :param llm_token_limit: Token limit for LLM inputs. + :param embedder_token_limit: Token limit for Embedder inputs. + :param tokenizer_backend: Optional tokenizer backend. + :param tokenizer_llm_name: What tokenizer to use for llm inputs truncation. + :param tokenizer_embedder_name: What tokenizer to use for embedder inputs truncation. """ self.builder_settings = builder_settings or BuilderArguments() self.storage_settings = storage_settings or StorageArguments() @@ -222,6 +233,18 @@ def __init__( if self.builder_settings.remove_isolated_nodes: what_to_add.append(RemoveIsolatedNodes()) + self._llm_context_truncator = TokenTruncation( + model_id=tokenizer_llm_name, + tokenizer_type=tokenizer_backend, + max_tokens=llm_token_limit, + ) + + self._embedder_context_truncator = TokenTruncation( + model_id=tokenizer_embedder_name, + tokenizer_type=tokenizer_backend, + max_tokens=embedder_token_limit, + ) + # Build graph self.pipeline = InMemoryGraphBuilder( llm=llm, @@ -237,6 +260,7 @@ def __init__( embedder=embedder, sparse_embedder=sparse_embedder, arguments=self.storage_settings, + context_truncator=self._embedder_context_truncator, node_t=Entity, edge_t=Relation, ) From e987855013e2733006577d73b2eb0cb751ce5d4c Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Thu, 23 Apr 2026 00:57:01 +0700 Subject: [PATCH 24/42] Update type annotation for tokenizer_type --- ragu/utils/token_truncation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ragu/utils/token_truncation.py b/ragu/utils/token_truncation.py index e50d6a5..87340b6 100644 --- a/ragu/utils/token_truncation.py +++ b/ragu/utils/token_truncation.py @@ -1,3 +1,5 @@ +from typing import Literal + from ragu.common.logger import logger @@ -15,7 +17,7 @@ class TokenTruncation: def __init__( self, model_id: str = "gpt-4o", - tokenizer_type: str = "tiktoken", + tokenizer_type: Literal["tiktoken", "local"] = "tiktoken", max_tokens: int = 30000, safe_decode: bool = True, ): From a19f454b86ae7de4723cc200e66c2065f840dd0a Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Thu, 23 Apr 2026 23:48:54 +0700 Subject: [PATCH 25/42] Update tokenizer backend type --- ragu/search_engine/global_search.py | 4 ++-- ragu/search_engine/local_search.py | 4 ++-- ragu/search_engine/mix_search.py | 4 ++-- ragu/search_engine/naive_search.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ragu/search_engine/global_search.py b/ragu/search_engine/global_search.py index 8d2361a..23cbcc3 100644 --- a/ragu/search_engine/global_search.py +++ b/ragu/search_engine/global_search.py @@ -1,5 +1,5 @@ import asyncio -from typing import Any, List +from typing import Any, List, Literal from pydantic import BaseModel @@ -29,7 +29,7 @@ def __init__( llm: LLM, knowledge_graph: KnowledgeGraph, max_context_length: int = 30_000, - tokenizer_backend: str = "tiktoken", + tokenizer_backend: Literal["tiktoken", "local"] = "tiktoken", tokenizer_model: str = "gpt-4", language: str | None = None, *args: Any, diff --git a/ragu/search_engine/local_search.py b/ragu/search_engine/local_search.py index c242aa6..40f8004 100644 --- a/ragu/search_engine/local_search.py +++ b/ragu/search_engine/local_search.py @@ -1,5 +1,5 @@ # Partially based on https://github.com/gusye1234/nano-graphrag/blob/main/nano_graphrag/ -from typing import Any, List +from typing import Any, List, Literal from typing_extensions import override from pydantic import BaseModel @@ -48,7 +48,7 @@ def __init__( sparse_embedder: SparseEmbedder | None = None, reranker: Scorer | None = None, max_context_length: int = 30_000, - tokenizer_backend: str = "tiktoken", + tokenizer_backend: Literal["tiktoken", "local"] = "tiktoken", tokenizer_model: str = "gpt-4", language: str | None = None, *args: Any, diff --git a/ragu/search_engine/mix_search.py b/ragu/search_engine/mix_search.py index 0c794e4..78d2958 100644 --- a/ragu/search_engine/mix_search.py +++ b/ragu/search_engine/mix_search.py @@ -1,5 +1,5 @@ import asyncio -from typing import Any, List +from typing import Any, List, Literal from typing_extensions import override from pydantic import BaseModel @@ -31,7 +31,7 @@ def __init__( engines: List[BaseEngine], allow_partial_failures: bool = True, max_context_length: int = 30_000, - tokenizer_backend: str = "tiktoken", + tokenizer_backend: Literal["tiktoken", "local"] = "tiktoken", tokenizer_model: str = "gpt-4", language: str | None = None, *args: Any, diff --git a/ragu/search_engine/naive_search.py b/ragu/search_engine/naive_search.py index 778a95c..dc0c977 100644 --- a/ragu/search_engine/naive_search.py +++ b/ragu/search_engine/naive_search.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, List +from typing import Any, Optional, List, Literal from pydantic import BaseModel @@ -34,7 +34,7 @@ def __init__( sparse_embedder: SparseEmbedder | None = None, reranker: Optional[Scorer] = None, max_context_length: int = 30_000, - tokenizer_backend: str = "tiktoken", + tokenizer_backend: Literal["tiktoken", "local"] = "tiktoken", tokenizer_model: str = "gpt-4", language: str | None = None, *args: Any, From c5f77c480dabf37c165279296a8fa086cccd398d Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Thu, 23 Apr 2026 23:55:45 +0700 Subject: [PATCH 26/42] Move truncator to base class --- ragu/search_engine/base_engine.py | 18 ++++++++++++++++-- ragu/search_engine/global_search.py | 18 +++++++++--------- ragu/search_engine/local_search.py | 15 ++++++++------- ragu/search_engine/mix_search.py | 16 +++++++++------- ragu/search_engine/naive_search.py | 16 ++++++++-------- 5 files changed, 50 insertions(+), 33 deletions(-) diff --git a/ragu/search_engine/base_engine.py b/ragu/search_engine/base_engine.py index 9099264..95ad2a3 100644 --- a/ragu/search_engine/base_engine.py +++ b/ragu/search_engine/base_engine.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Any +from typing import Any, Literal from pydantic import BaseModel @@ -13,6 +13,7 @@ NaiveSearchResult, ) from ragu.utils.ragu_utils import always_get_an_event_loop +from ragu.utils.token_truncation import TokenTruncation class BaseEngine(RaguGenerativeModule, ABC): @@ -23,7 +24,15 @@ class BaseEngine(RaguGenerativeModule, ABC): (a_query method) on top of a knowledge graph. """ - def __init__(self, llm: LLM, *args: Any, **kwargs: Any): + def __init__( + self, + llm: LLM, + *args: Any, + max_context_length: int = 30_000, + tokenizer_backend: Literal["tiktoken", "local"] = "tiktoken", + tokenizer_model: str = "gpt-4", + **kwargs: Any, + ): """ Initialize engine with an LLM client. @@ -31,6 +40,11 @@ def __init__(self, llm: LLM, *args: Any, **kwargs: Any): """ super().__init__(*args, **kwargs) self.llm = llm + self.truncation = TokenTruncation( + tokenizer_model, + tokenizer_backend, + max_context_length, + ) @abstractmethod async def a_search( diff --git a/ragu/search_engine/global_search.py b/ragu/search_engine/global_search.py index 23cbcc3..f893b56 100644 --- a/ragu/search_engine/global_search.py +++ b/ragu/search_engine/global_search.py @@ -9,7 +9,6 @@ from ragu.models.llm import LLM from ragu.search_engine.base_engine import BaseEngine from ragu.search_engine.types import GlobalSearchResult -from ragu.utils.token_truncation import TokenTruncation from ragu.common.prompts.prompt_storage import RAGUInstruction from ragu.common.prompts.messages import ChatMessages, render @@ -45,18 +44,19 @@ def __init__( :param tokenizer_model: Model name for tokenizer calibration (default: ``"gpt-4"``). """ _PROMPTS = ["global_search_context", "global_search"] - super().__init__(llm=llm, prompts=_PROMPTS, *args, **kwargs) + super().__init__( + llm=llm, + prompts=_PROMPTS, + max_context_length=max_context_length, + tokenizer_backend=tokenizer_backend, + tokenizer_model=tokenizer_model, + *args, + **kwargs, + ) - self.llm = llm self.knowledge_graph = knowledge_graph self.language = language if language else Settings.language - self.truncation = TokenTruncation( - tokenizer_model, - tokenizer_backend, - max_context_length, - ) - async def a_search(self, query: str, *args, **kwargs) -> GlobalSearchResult: """ Perform a global semantic search across all communities in the knowledge graph. diff --git a/ragu/search_engine/local_search.py b/ragu/search_engine/local_search.py index 40f8004..6bbd96a 100644 --- a/ragu/search_engine/local_search.py +++ b/ragu/search_engine/local_search.py @@ -20,7 +20,6 @@ _rerank_items, ) from ragu.search_engine.types import LocalSearchResult -from ragu.utils.token_truncation import TokenTruncation from ragu.common.prompts.prompt_storage import RAGUInstruction from ragu.common.prompts.messages import ChatMessages, render @@ -68,12 +67,14 @@ def __init__( :param language: Default output language (fed into prompt template). """ _PROMPTS_NAMES = ["local_search"] - super().__init__(llm=llm, prompts=_PROMPTS_NAMES, *args, **kwargs) - - self.truncation = TokenTruncation( - tokenizer_model, - tokenizer_backend, - max_context_length, + super().__init__( + llm=llm, + prompts=_PROMPTS_NAMES, + max_context_length=max_context_length, + tokenizer_backend=tokenizer_backend, + tokenizer_model=tokenizer_model, + *args, + **kwargs, ) self.knowledge_graph = knowledge_graph diff --git a/ragu/search_engine/mix_search.py b/ragu/search_engine/mix_search.py index 78d2958..45c1d67 100644 --- a/ragu/search_engine/mix_search.py +++ b/ragu/search_engine/mix_search.py @@ -8,7 +8,6 @@ from ragu.models.llm import LLM from ragu.search_engine.base_engine import BaseEngine from ragu.search_engine.types import MixSearchResult -from ragu.utils.token_truncation import TokenTruncation from ragu.common.prompts.prompt_storage import RAGUInstruction from ragu.common.prompts.messages import ChatMessages, render @@ -50,7 +49,15 @@ def __init__( :param language: Default output language. """ prompts = ["mix_search_context", "mix_search"] - super().__init__(llm=llm, prompts=prompts, *args, **kwargs) + super().__init__( + llm=llm, + prompts=prompts, + max_context_length=max_context_length, + tokenizer_backend=tokenizer_backend, + tokenizer_model=tokenizer_model, + *args, + **kwargs, + ) self.engines = engines if not self.engines: @@ -58,11 +65,6 @@ def __init__( self.allow_partial_failures = allow_partial_failures self.language = language if language else Settings.language - self.truncation = TokenTruncation( - tokenizer_model, - tokenizer_backend, - max_context_length, - ) async def _search_all( self, diff --git a/ragu/search_engine/naive_search.py b/ragu/search_engine/naive_search.py index dc0c977..9415a2a 100644 --- a/ragu/search_engine/naive_search.py +++ b/ragu/search_engine/naive_search.py @@ -12,7 +12,6 @@ from ragu.models.sparse_embedder import SparseEmbedder from ragu.search_engine.base_engine import BaseEngine from ragu.search_engine.types import NaiveSearchResult -from ragu.utils.token_truncation import TokenTruncation from ragu.common.prompts.prompt_storage import RAGUInstruction from ragu.common.prompts.messages import ChatMessages, render @@ -54,12 +53,14 @@ def __init__( :param language: Default output language """ _PROMPTS_NAMES = ["naive_search"] - super().__init__(llm=llm, prompts=_PROMPTS_NAMES, *args, **kwargs) - - self.truncation = TokenTruncation( - tokenizer_model, - tokenizer_backend, - max_context_length, + super().__init__( + llm=llm, + prompts=_PROMPTS_NAMES, + max_context_length=max_context_length, + tokenizer_backend=tokenizer_backend, + tokenizer_model=tokenizer_model, + *args, + **kwargs, ) self.graph = knowledge_graph @@ -70,7 +71,6 @@ def __init__( reranker=reranker, ) self.reranker = reranker - self.llm = llm self.language = language if language else Settings.language async def a_search( From 1b409f0c99e54fa08a6525cc50dc57cce4902a2f Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Fri, 24 Apr 2026 00:27:28 +0700 Subject: [PATCH 27/42] Make all query methods returns relevance scores --- ragu/graph/graph_retrieve_backend.py | 117 +++++++++++++++++++-------- ragu/search_engine/local_search.py | 2 +- ragu/search_engine/naive_search.py | 26 +----- 3 files changed, 86 insertions(+), 59 deletions(-) diff --git a/ragu/graph/graph_retrieve_backend.py b/ragu/graph/graph_retrieve_backend.py index 02fc468..6a2ccbc 100644 --- a/ragu/graph/graph_retrieve_backend.py +++ b/ragu/graph/graph_retrieve_backend.py @@ -1,5 +1,6 @@ -from typing import List +from typing import List, Tuple +from ragu.chunker.types import Chunk from ragu.graph.types import Entity, Relation from ragu.models.embedder import Embedder from ragu.models.scorer import Scorer @@ -34,13 +35,13 @@ def __init__( self.sparse_embedder = sparse_embedder self.reranker = reranker - async def query_entities(self, query: str, top_k: int = 20) -> List[Entity]: + async def query_entities(self, query: str, top_k: int = 20) -> Tuple[List[Entity], List[EmbeddingHit]]: """ Find entities matching a free-text query. :param query: Search query text. :param top_k: Maximum number of results. - :return: Matching entities ordered by relevance. + :return: Matching entities ordered by relevance and their aligned vector hits. """ point = await self.build_query_vectors(query) results = await self.knowledge_graph.index.nodes_vector_db.query( @@ -49,70 +50,116 @@ async def query_entities(self, query: str, top_k: int = 20) -> List[Entity]: ) entity_ids = [result.id for result in results] entities = await self.knowledge_graph.index.get_nodes(entity_ids) - return [entity for entity in entities if entity is not None] - - async def query_relations(self, query: str, top_k: int = 20) -> List[Relation]: + filtered_entities: List[Entity] = [] + filtered_hits: List[EmbeddingHit] = [] + for hit, entity in zip(results, entities): + if entity is None: + continue + filtered_entities.append(entity) + filtered_hits.append(hit) + return filtered_entities, filtered_hits + + async def query_relations(self, query: str, top_k: int = 20) -> Tuple[List[Relation], List[EmbeddingHit]]: """ Find relations matching a free-text query. :param query: Search query text. :param top_k: Maximum number of results. - :return: Matching relations ordered by relevance. + :return: Matching relations ordered by relevance and their aligned vector hits. """ point = await self.build_query_vectors(query) results = await self.knowledge_graph.index.edges_vector_db.query( point, top_k=top_k, ) - edge_specs: List[EdgeSpec] = [ - ( - str(result.metadata.get("subject_id")), - str(result.metadata.get("object_id")), - result.id, + edge_specs: List[EdgeSpec] = [] + filtered_hits: List[EmbeddingHit] = [] + for result in results: + subject_id = result.metadata.get("subject_id") + object_id = result.metadata.get("object_id") + if not subject_id or not object_id: + continue + edge_specs.append( + ( + str(subject_id), + str(object_id), + result.id, + ) ) - for result in results - if result.metadata.get("subject_id") and result.metadata.get("object_id") - ] + filtered_hits.append(result) if not edge_specs: - return [] + return [], [] relations = await self.knowledge_graph.index.get_edges(edge_specs) - return [relation for relation in relations if relation is not None] + filtered_relations: List[Relation] = [] + aligned_hits: List[EmbeddingHit] = [] + for hit, relation in zip(filtered_hits, relations): + if relation is None: + continue + filtered_relations.append(relation) + aligned_hits.append(hit) + return filtered_relations, aligned_hits + + async def query_chunks(self, query: str, top_k: int = 20) -> Tuple[List[Chunk], List[EmbeddingHit]]: + """ + Search chunk vectors and return resolved chunks with aligned embedding hits. + + :param query: Search query text. + :param top_k: Maximum number of hits. + :return: Ranked chunks with aligned vector hits. + """ + point = await self.build_query_vectors(query) + results = await self.knowledge_graph.index.chunks_vector_db.query( + point=point, + top_k=top_k, + ) + chunk_ids = [result.id for result in results] + chunk_data_list = await self.knowledge_graph.index.chunks_kv_storage.get_by_ids(chunk_ids) + + chunks: List[Chunk] = [] + filtered_hits: List[EmbeddingHit] = [] + for chunk_id, chunk_data, hit in zip(chunk_ids, chunk_data_list, results): + if chunk_data is None: + continue + chunk = Chunk( + content=chunk_data.get("content", ""), + chunk_order_idx=chunk_data.get("chunk_order_idx", 0), + doc_id=chunk_data.get("doc_id", ""), + num_tokens=chunk_data.get("num_tokens"), + ) + setattr(chunk, "id", chunk_id) + chunks.append(chunk) + filtered_hits.append(hit) + return chunks, filtered_hits - async def find_similar_entities(self, entity: Entity, top_k: int = 10) -> List[Entity]: + async def find_similar_entities( + self, + entity: Entity, + top_k: int = 10, + ) -> Tuple[List[Entity], List[EmbeddingHit]]: """ Find entities semantically similar to the given entity. :param entity: Reference entity to search against. :param top_k: Maximum number of results. - :return: Similar entities ordered by relevance. + :return: Similar entities ordered by relevance and their aligned vector hits. """ query = f"{entity.entity_name} - {entity.description}" return await self.query_entities(query, top_k=top_k) - async def find_similar_relations(self, relation: Relation, top_k: int = 10) -> List[Relation]: + async def find_similar_relations( + self, + relation: Relation, + top_k: int = 10, + ) -> Tuple[List[Relation], List[EmbeddingHit]]: """ Find relations semantically similar to the given relation. :param relation: Reference relation to search against. :param top_k: Maximum number of results. - :return: Similar relations ordered by relevance. + :return: Similar relations ordered by relevance and their aligned vector hits. """ return await self.query_relations(relation.description, top_k=top_k) - async def query_chunk_hits(self, query: str, top_k: int = 20) -> List[EmbeddingHit]: - """ - Search chunk vectors and return raw embedding hits. - - :param query: Search query text. - :param top_k: Maximum number of hits. - :return: Ranked chunk hits with metadata. - """ - point = await self.build_query_vectors(query) - return await self.knowledge_graph.index.chunks_vector_db.query( - point=point, - top_k=top_k, - ) - async def build_query_vectors(self, query: str) -> Point: """ Encode a query into dense and optional sparse vectors. diff --git a/ragu/search_engine/local_search.py b/ragu/search_engine/local_search.py index 6bbd96a..1160961 100644 --- a/ragu/search_engine/local_search.py +++ b/ragu/search_engine/local_search.py @@ -96,7 +96,7 @@ async def a_search(self, query: str, top_k: int = 20, *args, **kwargs) -> LocalS :param top_k: Number of top entities to retrieve from the entity vector DB. :return: LocalSearchResult containing entities, relations, summaries, chunks, and document ids. """ - entities = await self.retriever.query_entities(query, top_k=top_k) + entities, _ = await self.retriever.query_entities(query, top_k=top_k) relations = await _find_most_related_edges_from_entities(entities, self.knowledge_graph) relations = [relation for relation in relations if relation is not None] diff --git a/ragu/search_engine/naive_search.py b/ragu/search_engine/naive_search.py index 9415a2a..b53b086 100644 --- a/ragu/search_engine/naive_search.py +++ b/ragu/search_engine/naive_search.py @@ -90,32 +90,12 @@ async def a_search( If None, keeps all reranked chunks. Used only when reranker is set. :return: NaiveSearchResult with retrieved chunks, scores, and document ids. """ - results = await self.retriever.query_chunk_hits(query, top_k=top_k) + chunks, scores = await self.retriever.query_chunks(query, top_k=top_k) - if not results: + if not scores: return NaiveSearchResult(chunks=[], scores=[], documents_id=[]) - chunk_ids = [r.id for r in results] - distances = [r.distance for r in results] - - chunk_data_list = await self.graph.index.chunks_kv_storage.get_by_ids(chunk_ids) - - chunks: List[Chunk] = [] - valid_distances: List[float] = [] - for chunk_id, chunk_data, distance in zip(chunk_ids, chunk_data_list, distances): - if chunk_data is not None: - chunk = Chunk( - content=chunk_data.get("content", ""), - chunk_order_idx=chunk_data.get("chunk_order_idx", 0), - doc_id=chunk_data.get("doc_id", ""), - num_tokens=chunk_data.get("num_tokens"), - ) - # Override the auto-generated id with the stored one - setattr(chunk, "id", chunk_id) - chunks.append(chunk) - valid_distances.append(distance) - - scores = valid_distances + scores = [r.distance for r in scores] if self.reranker is not None and chunks: chunk_contents = [c.content for c in chunks] rerank_results = await self.reranker.score(query, chunk_contents) From 6aa61b9130c958dd36bf76d8caecc856c0f90ead Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Fri, 24 Apr 2026 23:19:47 +0700 Subject: [PATCH 28/42] Fix module --- ragu/search_engine/types.py | 122 ------------------ .../vdb_storage_adapters/qdrant_vdb.py | 3 +- 2 files changed, 2 insertions(+), 123 deletions(-) delete mode 100644 ragu/search_engine/types.py diff --git a/ragu/search_engine/types.py b/ragu/search_engine/types.py deleted file mode 100644 index 3b87b4d..0000000 --- a/ragu/search_engine/types.py +++ /dev/null @@ -1,122 +0,0 @@ -from dataclasses import dataclass, field -from textwrap import dedent -from typing import Any - -from jinja2 import Template - -from ragu.chunker.types import Chunk -from ragu.graph.types import Entity, Relation - - -@dataclass -class LocalSearchResult: - """ - Structured retrieval payload returned by local graph search. - """ - - entities: list[Entity] = field(default_factory=list[Entity]) - relations: list[Relation] = field(default_factory=list[Relation]) - summaries: list[Any] = field(default_factory=list[Any]) - chunks: list[Chunk] = field(default_factory=list[Chunk]) - documents_id: list[str] = field(default_factory=list[str]) - - def __str__(self) -> str: - """ - Render search context into prompt-friendly text. - - :return: Human-readable context string. - """ - _template: Template = Template(dedent( - """ - **Entities** - Entity, entity type, entity description - {%- for e in entities %} - {{ e.entity_name }}, {{ e.entity_type }}, {{ e.description }} - {%- endfor %} - - **Relations** - Subject, relation type, object, relation description, rank - {%- for r in relations %} - {{ r.subject_name }}, {{ r.relation_type }}, {{ r.object_name }} - {{ r.description }}, {{ r.rank }} - {%- endfor %} - - {%- if summaries %} - **Summary** - {%- for s in summaries %} - {{ s.summary }} - {%- endfor %} - {% endif %} - - {%- if chunks %} - **Chunks** - {%- for c in chunks %} - {{ c.content }} - {%- endfor %} - {% endif %} - """ - ) - ) - return _template.render( - entities=self.entities, - relations=self.relations, - summaries=self.summaries, - chunks=self.chunks, - ) - - -@dataclass -class GlobalSearchResult: - """ - Aggregated global-search insights with relevance ratings. - """ - - insights: list[Any] = field(default_factory=list[Any]) - - def __str__(self) -> str: - """ - Render insights into prompt-friendly text. - - :return: Human-readable insights string. - """ - _template: Template = Template(dedent( - """ - {%- for insight in insights %} - {{ loop.index}}. Insight: {{ insight.response }}, rating: {{ insight.rating }} - {%- endfor %} - """) - ) - return _template.render(insights=self.insights) - - -@dataclass -class NaiveSearchResult: - """ - Retrieval payload for vector-only (naive) search. - """ - - chunks: list[Chunk] = field(default_factory=list[Chunk]) - scores: list[float] = field(default_factory=list[float]) - documents_id: list[str] = field(default_factory=list[str]) - - def __str__(self) -> str: - """ - Render retrieved chunks and scores into prompt-friendly text. - - :return: Human-readable chunk listing. - """ - _template: Template = Template(dedent( - """ - **Retrieved Chunks** - {%- for chunk, score in zip(chunks, scores) %} - [{{ loop.index }}] (score: {{ "%.3f"|format(score) }}) - {{ chunk.content }} - {%- endfor %} - """) - ) - return _template.render( - chunks=self.chunks, - scores=self.scores, - zip=zip, - ) - -MixSearchResult = list[NaiveSearchResult | LocalSearchResult | GlobalSearchResult] diff --git a/ragu/storage/vdb_storage_adapters/qdrant_vdb.py b/ragu/storage/vdb_storage_adapters/qdrant_vdb.py index a134663..80f16ce 100644 --- a/ragu/storage/vdb_storage_adapters/qdrant_vdb.py +++ b/ragu/storage/vdb_storage_adapters/qdrant_vdb.py @@ -1,7 +1,8 @@ import asyncio import uuid from pathlib import Path -from typing import Any, List, Dict, override, Literal +from typing import Any, List, Dict, Literal +from typing_extensions import override from qdrant_client.conversions.common_types import CollectionInfo, QueryResponse from qdrant_client.http.models import FusionQuery, Fusion From 76443bbdc4735a24b6f6df10a657f5559bb8e33d Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Fri, 24 Apr 2026 23:33:19 +0700 Subject: [PATCH 29/42] Add retrieval metrics --- ragu/search_engine/__init__.py | 20 ++++- ragu/search_engine/base_engine.py | 64 +++++++++++---- ragu/search_engine/global_search.py | 58 +++++++++++--- ragu/search_engine/local_search.py | 118 +++++++++++++++++++++++----- ragu/search_engine/mix_search.py | 70 +++++++++++++---- ragu/search_engine/naive_search.py | 79 +++++++++++++++---- ragu/search_engine/query_plan.py | 37 ++++++--- 7 files changed, 352 insertions(+), 94 deletions(-) diff --git a/ragu/search_engine/__init__.py b/ragu/search_engine/__init__.py index 8c472c8..d8dee35 100644 --- a/ragu/search_engine/__init__.py +++ b/ragu/search_engine/__init__.py @@ -1,5 +1,17 @@ -from ragu.search_engine.global_search import GlobalSearchEngine -from ragu.search_engine.local_search import LocalSearchEngine -from ragu.search_engine.mix_search import MixSearchEngine -from ragu.search_engine.naive_search import NaiveSearchEngine +from ragu.search_engine.global_search import ( + GlobalSearchEngine, + GlobalSearchRetrieve +) +from ragu.search_engine.local_search import ( + LocalSearchEngine, + LocalSearchRetrieve +) +from ragu.search_engine.mix_search import ( + MixSearchEngine, + MixSearchRetrieve +) +from ragu.search_engine.naive_search import ( + NaiveSearchEngine, + NaiveSearchRetrieve +) from ragu.search_engine.query_plan import QueryPlanEngine diff --git a/ragu/search_engine/base_engine.py b/ragu/search_engine/base_engine.py index 95ad2a3..99db22d 100644 --- a/ragu/search_engine/base_engine.py +++ b/ragu/search_engine/base_engine.py @@ -1,21 +1,53 @@ from abc import ABC, abstractmethod -from typing import Any, Literal +from dataclasses import dataclass, field +from typing import Any, Literal, TypeVar, Generic from pydantic import BaseModel - from ragu.common.base import RaguGenerativeModule -from ragu.common.prompts.default_models import GlobalSearchContextModel from ragu.models.llm import LLM -from ragu.search_engine.types import ( - GlobalSearchResult, - LocalSearchResult, - MixSearchResult, - NaiveSearchResult, -) from ragu.utils.ragu_utils import always_get_an_event_loop from ragu.utils.token_truncation import TokenTruncation +ResultT = TypeVar("ResultT") + + +@dataclass(slots=True) +class SearchEngineRetrieve(ABC, Generic[ResultT]): + """ + Base response class for retrieval. + """ + query: str + result: ResultT + metrics: dict[str, Any] = field(default_factory=dict) + + @abstractmethod + def to_text(self) -> str: + """ + How to format the retrieved result. + """ + ... + + def __str__(self) -> str: + return self.to_text() + + +@dataclass(slots=True) +class SearchEngineResponse: + """ + Default response for search engine. + """ + query: str + response: str | BaseModel + retrieval: SearchEngineRetrieve[Any] + payload: dict[str, Any] = field(default_factory=dict) + + def __str__(self) -> str: + if isinstance(self.response, BaseModel): + return self.response.model_dump_json(indent=4) + return self.response + + class BaseEngine(RaguGenerativeModule, ABC): """ Base interface for RAGU query/search engines. @@ -52,7 +84,7 @@ async def a_search( query, *args, **kwargs, - ) -> Any: + ) -> SearchEngineRetrieve: """ Retrieve context relevant to a query. @@ -62,25 +94,25 @@ async def a_search( pass @abstractmethod - async def a_query(self, query: str) -> str | BaseModel: + async def a_query(self, query: str, *args, **kwargs) -> SearchEngineResponse: """ Execute full query flow and return answer. :param query: Input query string. - :return: Generated answer as a string or Pydantic model when a response schema is set. + :return: Structured search result containing the final answer and retrieval details. """ pass - async def query(self, query: str) -> str | BaseModel: + async def query(self, query: str, *args, **kwargs) -> SearchEngineResponse: """ Synchronous wrapper for ``a_query``. :param query: Input query string. - :return: Generated answer as a string or Pydantic model when a response schema is set. + :return: Structured search result containing the final answer and retrieval details. """ loop = always_get_an_event_loop() return loop.run_until_complete( - self.a_query(query) + self.a_query(query, *args, **kwargs) ) async def search( @@ -88,7 +120,7 @@ async def search( query, *args, **kwargs, - ) -> NaiveSearchResult | LocalSearchResult | GlobalSearchResult | GlobalSearchContextModel | MixSearchResult: + ) -> SearchEngineRetrieve: """ Synchronous wrapper for ``a_search``. diff --git a/ragu/search_engine/global_search.py b/ragu/search_engine/global_search.py index f893b56..674652e 100644 --- a/ragu/search_engine/global_search.py +++ b/ragu/search_engine/global_search.py @@ -1,19 +1,41 @@ import asyncio +from dataclasses import field, dataclass +from textwrap import dedent from typing import Any, List, Literal -from pydantic import BaseModel +from jinja2 import Template from ragu.common.base import RaguGenerativeModule from ragu.common.global_parameters import Settings from ragu.graph.knowledge_graph import KnowledgeGraph from ragu.models.llm import LLM -from ragu.search_engine.base_engine import BaseEngine -from ragu.search_engine.types import GlobalSearchResult - +from ragu.search_engine.base_engine import ( + BaseEngine, + SearchEngineRetrieve, + SearchEngineResponse +) from ragu.common.prompts.prompt_storage import RAGUInstruction from ragu.common.prompts.messages import ChatMessages, render +@dataclass(slots=True) +class GlobalSearchResult: + insights: list[Any] = field(default_factory=list) + + +@dataclass(slots=True) +class GlobalSearchRetrieve(SearchEngineRetrieve[GlobalSearchResult]): + result: GlobalSearchResult + + def to_text(self) -> str: + template = Template(dedent(""" + {%- for insight in result.insights %} + {{ loop.index }}. Insight: {{ insight.response }}, rating: {{ insight.rating }} + {%- endfor %} + """)) + return template.render(result=self.result) + + class GlobalSearchEngine(BaseEngine, RaguGenerativeModule): """ Executes global retrieval-augmented search (RAG) across the entire knowledge graph. @@ -57,7 +79,7 @@ def __init__( self.knowledge_graph = knowledge_graph self.language = language if language else Settings.language - async def a_search(self, query: str, *args, **kwargs) -> GlobalSearchResult: + async def a_search(self, query: str, *args, **kwargs) -> GlobalSearchRetrieve: """ Perform a global semantic search across all communities in the knowledge graph. @@ -78,7 +100,16 @@ async def a_search(self, query: str, *args, **kwargs) -> GlobalSearchResult: responses = [r for r in responses if int(r.get("rating", 0)) > 0] responses = sorted(responses, key=lambda x: int(x.get("rating", 0)), reverse=True) - return GlobalSearchResult(responses) + return GlobalSearchRetrieve( + query=query, + result=GlobalSearchResult( + insights=responses, + ), + metrics={ + f"insight_{idx}_rating": r.get("rating", 0) + for idx, r in enumerate(responses) + }, + ) async def get_meta_responses(self, query: str, context: List[str]) -> List[dict]: """ @@ -111,7 +142,7 @@ async def get_meta_responses(self, query: str, context: List[str]) -> List[dict] return [r.model_dump() for r in meta_responses if r] - async def a_query(self, query: str) -> str | BaseModel: + async def a_query(self, query: str, *args, **kwargs) -> SearchEngineResponse: """ Execute a full global retrieval-augmented generation query. @@ -133,8 +164,15 @@ async def a_query(self, query: str) -> str | BaseModel: language=self.language, ) rendered = rendered_list[0] - return await self.llm.chat_completion( + answer = await self.llm.chat_completion( conversation=rendered.to_openai(), - output_schema=instruction.pydantic_model or str, # type: ignore - ) # type: ignore + output_schema=instruction.pydantic_model or str, # type: ignore[arg-type] + ) # type: ignore[assignment] + + return SearchEngineResponse( + query=query, + response=answer, + retrieval=context, + payload={} + ) diff --git a/ragu/search_engine/local_search.py b/ragu/search_engine/local_search.py index 1160961..d17062f 100644 --- a/ragu/search_engine/local_search.py +++ b/ragu/search_engine/local_search.py @@ -1,17 +1,27 @@ # Partially based on https://github.com/gusye1234/nano-graphrag/blob/main/nano_graphrag/ -from typing import Any, List, Literal +from dataclasses import dataclass, field from typing_extensions import override +from textwrap import dedent +from typing import Any, List, Literal -from pydantic import BaseModel +from jinja2 import Template +from ragu.chunker.types import Chunk from ragu.common.global_parameters import Settings +from ragu.common.prompts.messages import ChatMessages, render +from ragu.common.prompts.prompt_storage import RAGUInstruction from ragu.graph.graph_retrieve_backend import GraphRetriever from ragu.graph.knowledge_graph import KnowledgeGraph +from ragu.graph.types import Entity, Relation from ragu.models.embedder import Embedder from ragu.models.llm import LLM from ragu.models.scorer import Scorer from ragu.models.sparse_embedder import SparseEmbedder -from ragu.search_engine.base_engine import BaseEngine +from ragu.search_engine.base_engine import ( + BaseEngine, + SearchEngineRetrieve, + SearchEngineResponse +) from ragu.search_engine.search_functional import ( _find_most_related_edges_from_entities, _find_most_related_text_unit_from_entities, @@ -19,10 +29,50 @@ _find_most_related_community_from_entities, _rerank_items, ) -from ragu.search_engine.types import LocalSearchResult -from ragu.common.prompts.prompt_storage import RAGUInstruction -from ragu.common.prompts.messages import ChatMessages, render + +@dataclass(slots=True) +class LocalSearchResult: + entities: list[Entity] = field(default_factory=list) + relations: list[Relation] = field(default_factory=list) + summaries: list[Any] = field(default_factory=list) + chunks: list[Chunk] = field(default_factory=list) + documents_id: list[str] = field(default_factory=list) + + +@dataclass(slots=True) +class LocalSearchRetrieve(SearchEngineRetrieve[LocalSearchResult]): + result: LocalSearchResult + + def to_text(self) -> str: + template = Template(dedent(""" + **Entities** + Entity, entity type, entity description + {%- for e in result.entities %} + {{ e.entity_name }}, {{ e.entity_type }}, {{ e.description }} + {%- endfor %} + + **Relations** + Subject, relation type, object, relation description, rank + {%- for r in result.relations %} + {{ r.subject_name }}, {{ r.relation_type }}, {{ r.object_name }} - {{ r.description }}, {{ r.rank }} + {%- endfor %} + + {%- if result.summaries %} + **Summary** + {%- for s in result.summaries %} + {{ s.summary }} + {%- endfor %} + {% endif %} + + {%- if result.chunks %} + **Chunks** + {%- for c in result.chunks %} + {{ c.content }} + {%- endfor %} + {% endif %} + """)) + return template.render(result=self.result) class LocalSearchEngine(BaseEngine): @@ -88,7 +138,7 @@ def __init__( self.language = language if language else Settings.language @override - async def a_search(self, query: str, top_k: int = 20, *args, **kwargs) -> LocalSearchResult: + async def a_search(self, query: str, top_k: int = 20, *args, **kwargs) -> LocalSearchRetrieve: """ Retrieve local graph context for the given query. @@ -96,7 +146,12 @@ async def a_search(self, query: str, top_k: int = 20, *args, **kwargs) -> LocalS :param top_k: Number of top entities to retrieve from the entity vector DB. :return: LocalSearchResult containing entities, relations, summaries, chunks, and document ids. """ - entities, _ = await self.retriever.query_entities(query, top_k=top_k) + entities, entity_hits = await self.retriever.query_entities(query, top_k=top_k) + entity_scores_by_id = { + entity.id: hit.distance + for entity, hit in zip(entities, entity_hits) + if entity and entity.id + } relations = await _find_most_related_edges_from_entities(entities, self.knowledge_graph) relations = [relation for relation in relations if relation is not None] @@ -137,12 +192,26 @@ async def a_search(self, query: str, top_k: int = 20, *args, **kwargs) -> LocalS documents_id = await _find_documents_id(entities) - return LocalSearchResult( - entities=entities, - relations=relations, - summaries=summaries, - chunks=relevant_chunks, - documents_id=documents_id, + return LocalSearchRetrieve( + query=query, + result=LocalSearchResult( + entities=entities, + relations=relations, + summaries=summaries, + chunks=relevant_chunks, + documents_id=documents_id, + ), + metrics={ + "entities": [ + { + "id": entity.id, + "name": entity.entity_name, + "rank": idx, + "relevance_score": entity_scores_by_id.get(entity.id), + } + for idx, entity in enumerate(entities) + ], + }, ) @override @@ -150,9 +219,9 @@ async def a_query( self, query: str, top_k: int = 20, - use_summary: bool=False, - use_chunks: bool=False - ) -> str | BaseModel: + use_summary: bool = False, + use_chunks: bool = False + ) -> SearchEngineResponse: """ Execute a local RAG query. @@ -162,12 +231,12 @@ async def a_query( :param use_chunks: Whether to use chunks or not. :return: Generated answer as a string or Pydantic model when a response schema is set. """ - context: LocalSearchResult = await self.a_search(query, top_k) + context: LocalSearchRetrieve = await self.a_search(query, top_k) if not use_summary: - context.summaries = [] + context.result.summaries = [] if not use_chunks: - context.chunks = [] + context.result.chunks = [] truncated_context: str = self.truncation(str(context)) instruction: RAGUInstruction = self.get_prompt("local_search") @@ -179,7 +248,14 @@ async def a_query( language=self.language, ) rendered: ChatMessages = rendered_conversations[0] - return await self.llm.chat_completion( + response = await self.llm.chat_completion( conversation=rendered.to_openai(), output_schema=instruction.pydantic_model or str, # type: ignore ) # type: ignore + + return SearchEngineResponse( + query=query, + response=response, + retrieval=context, + payload={} + ) diff --git a/ragu/search_engine/mix_search.py b/ragu/search_engine/mix_search.py index 45c1d67..95e7a3d 100644 --- a/ragu/search_engine/mix_search.py +++ b/ragu/search_engine/mix_search.py @@ -1,18 +1,37 @@ import asyncio +from dataclasses import dataclass, field +from textwrap import dedent from typing import Any, List, Literal -from typing_extensions import override -from pydantic import BaseModel +from jinja2 import Template +from typing_extensions import override from ragu.common.global_parameters import Settings from ragu.models.llm import LLM -from ragu.search_engine.base_engine import BaseEngine -from ragu.search_engine.types import MixSearchResult - +from ragu.search_engine.base_engine import BaseEngine, SearchEngineRetrieve, SearchEngineResponse from ragu.common.prompts.prompt_storage import RAGUInstruction from ragu.common.prompts.messages import ChatMessages, render +@dataclass(slots=True) +class MixSearchResult: + results: list[SearchEngineRetrieve[Any]] | list[SearchEngineResponse] = field(default_factory=list) + + +@dataclass(slots=True) +class MixSearchRetrieve(SearchEngineRetrieve[MixSearchResult]): + result: MixSearchResult + + def to_text(self) -> str: + template = Template(dedent(""" + {%- for retrieve in result.results %} + **Engine {{ loop.index }} Context** + {{ retrieve }} + {% endfor %} + """)) + return template.render(result=self.result) + + class MixSearchEngine(BaseEngine): """ Performs ensemble retrieval-augmented search over multiple engines. @@ -71,7 +90,7 @@ async def _search_all( query: str, *args: Any, **kwargs: Any, - ) -> list[Any | None]: + ) -> list[SearchEngineRetrieve]: """ Execute ``a_search`` on each child engine. @@ -85,16 +104,15 @@ async def _search_all( ] results = await asyncio.gather(*tasks, return_exceptions=True) - contexts: list[Any | None] = [] + contexts: list[SearchEngineRetrieve] = [] for result in results: if isinstance(result, Exception): if not self.allow_partial_failures: raise result - contexts.append(None) continue contexts.append(result) - if not any(context is not None for context in contexts): + if not contexts: raise RuntimeError("MixSearchEngine could not retrieve context from any child engine") return contexts @@ -104,7 +122,7 @@ async def _query_all( query: str, *args: Any, **kwargs: Any, - ) -> list[Any | None]: + ) -> list[SearchEngineResponse]: """ Execute ``a_query`` on each child engine. @@ -118,22 +136,21 @@ async def _query_all( ] results = await asyncio.gather(*tasks, return_exceptions=True) - contexts: list[Any | None] = [] + contexts: list[SearchEngineResponse] = [] for result in results: if isinstance(result, Exception): if not self.allow_partial_failures: raise result - contexts.append(None) continue contexts.append(result) - if not any(context is not None for context in contexts): + if not contexts: raise RuntimeError("MixSearchEngine could not retrieve context from any child engine") return contexts @override - async def a_search(self, query: str, *args: Any, **kwargs: Any) -> MixSearchResult: + async def a_search(self, query: str, *args: Any, **kwargs: Any) -> SearchEngineRetrieve: """ Retrieve raw contexts from all child engines. @@ -142,7 +159,14 @@ async def a_search(self, query: str, *args: Any, **kwargs: Any) -> MixSearchResu the constructor. Failed engines are represented as ``None`` when partial failures are enabled. """ - return await self._search_all(query, *args, **kwargs) + results = await self._search_all(query, *args, **kwargs) + + # TODO: maybe it is good idea to pass every child engine metrics in 'metrics' field here. + return MixSearchRetrieve( + query=query, + result=MixSearchResult(results=results), + metrics={} + ) @override async def a_query( @@ -151,7 +175,7 @@ async def a_query( *args: Any, ensemble_responses: bool = False, **kwargs: Any, - ) -> str | BaseModel: + ) -> SearchEngineResponse: """ Execute an ensemble query across child engines. @@ -196,7 +220,19 @@ async def a_query( ) rendered = rendered_list[0] - return await self.llm.chat_completion( + response = await self.llm.chat_completion( conversation=rendered.to_openai(), output_schema=instruction.pydantic_model or str, # type: ignore[arg-type] ) # type: ignore[return-value] + + # TODO: maybe it is good idea to pass every child engine metrics in 'metrics' field here. + return SearchEngineResponse( + query=query, + response=response, + retrieval=MixSearchRetrieve( + query=query, + result=MixSearchResult(results), + metrics={} + ), + payload={} + ) diff --git a/ragu/search_engine/naive_search.py b/ragu/search_engine/naive_search.py index b53b086..496efdb 100644 --- a/ragu/search_engine/naive_search.py +++ b/ragu/search_engine/naive_search.py @@ -1,7 +1,8 @@ +from dataclasses import dataclass, field +from textwrap import dedent from typing import Any, Optional, List, Literal -from pydantic import BaseModel - +from jinja2 import Template from ragu.chunker.types import Chunk from ragu.common.global_parameters import Settings from ragu.graph.graph_retrieve_backend import GraphRetriever @@ -10,13 +11,37 @@ from ragu.models.llm import LLM from ragu.models.scorer import Scorer from ragu.models.sparse_embedder import SparseEmbedder -from ragu.search_engine.base_engine import BaseEngine -from ragu.search_engine.types import NaiveSearchResult - +from ragu.search_engine.base_engine import ( + BaseEngine, + SearchEngineRetrieve, + SearchEngineResponse +) from ragu.common.prompts.prompt_storage import RAGUInstruction from ragu.common.prompts.messages import ChatMessages, render +@dataclass(slots=True) +class NaiveSearchResult: + chunks: list[Chunk] = field(default_factory=list) + scores: list[float] = field(default_factory=list) + documents_id: list[str] = field(default_factory=list) + + +@dataclass(slots=True) +class NaiveSearchRetrieve(SearchEngineRetrieve[NaiveSearchResult]): + result: NaiveSearchResult + + def to_text(self) -> str: + template = Template(dedent(""" + **Retrieved Chunks** + {%- for chunk, score in zip(result.chunks, result.scores) %} + [{{ loop.index }}] (score: {{ "%.3f"|format(score) }}) + {{ chunk.content }} + {%- endfor %} + """)) + return template.render(result=self.result, zip=zip) + + class NaiveSearchEngine(BaseEngine): """ Performs naive vector RAG search over document chunks. @@ -80,7 +105,7 @@ async def a_search( rerank_top_k: Optional[int] = None, *args: Any, **kwargs: Any, - ) -> NaiveSearchResult: + ) -> NaiveSearchRetrieve: """ Perform a naive vector search over chunks. @@ -93,7 +118,11 @@ async def a_search( chunks, scores = await self.retriever.query_chunks(query, top_k=top_k) if not scores: - return NaiveSearchResult(chunks=[], scores=[], documents_id=[]) + return NaiveSearchRetrieve( + query=query, + result=NaiveSearchResult(), + metrics={} + ) scores = [r.distance for r in scores] if self.reranker is not None and chunks: @@ -114,13 +143,26 @@ async def a_search( documents_id = list({c.doc_id for c in chunks if c.doc_id}) - return NaiveSearchResult( - chunks=chunks, - scores=scores, - documents_id=documents_id, + return NaiveSearchRetrieve( + query=query, + result=NaiveSearchResult( + chunks=chunks, + scores=scores, + documents_id=documents_id, + ), + metrics={ + "chunks": [ + { + "id": chunk.id, + "rank": idx, + "score": score, + } + for idx, (chunk, score) in enumerate(zip(chunks, scores)) + ], + }, ) - async def a_query(self, query: str, top_k: int = 20, rerank_top_k: Optional[int] = None) -> str | BaseModel: + async def a_query(self, query: str, top_k: int = 20, rerank_top_k: Optional[int] = None) -> SearchEngineResponse: """ Execute a retrieval-augmented query using naive vector search. @@ -130,7 +172,7 @@ async def a_query(self, query: str, top_k: int = 20, rerank_top_k: Optional[int] :return: Generated answer as a string or Pydantic model when a response schema is set. :rtype: str | BaseModel """ - context: NaiveSearchResult = await self.a_search(query, top_k, rerank_top_k) + context: NaiveSearchRetrieve = await self.a_search(query, top_k, rerank_top_k) truncated_context: str = self.truncation(str(context)) instruction: RAGUInstruction = self.get_prompt("naive_search") @@ -143,7 +185,14 @@ async def a_query(self, query: str, top_k: int = 20, rerank_top_k: Optional[int] ) rendered: ChatMessages = rendered_list[0] - return await self.llm.chat_completion( + answer = await self.llm.chat_completion( conversation=rendered.to_openai(), - output_schema=instruction.pydantic_model or str, # type: ignore + output_schema=instruction.pydantic_model ) # type: ignore + + return SearchEngineResponse( + query=query, + response=answer, + retrieval=context, + payload={} + ) diff --git a/ragu/search_engine/query_plan.py b/ragu/search_engine/query_plan.py index d03b30d..1c5551a 100644 --- a/ragu/search_engine/query_plan.py +++ b/ragu/search_engine/query_plan.py @@ -1,9 +1,10 @@ -from typing import List, Dict +from typing import List, Dict, Tuple +from typing_extensions import override from pydantic import BaseModel from ragu.common.prompts.default_models import SubQuery, QueryPlan, RewriteQuery -from ragu.search_engine.base_engine import BaseEngine +from ragu.search_engine.base_engine import BaseEngine, SearchEngineResponse, SearchEngineRetrieve from ragu.search_engine.search_functional import _topological_sort from ragu.common.prompts.prompt_storage import RAGUInstruction @@ -49,14 +50,14 @@ async def process_query(self, query: str) -> List[SubQuery]: ) rendered = rendered_list[0] - response: List[QueryPlan] = await self.engine.llm.chat_completion( # type: ignore + response: QueryPlan = await self.engine.llm.chat_completion( # type: ignore rendered.to_openai(), output_schema=instruction.pydantic_model, ) return response.subqueries - async def _rewrite_subquery(self, subquery: SubQuery, context: Dict[str, str]) -> SubQuery: + async def _rewrite_subquery(self, subquery: SubQuery, context: Dict[str, SearchEngineResponse]) -> SubQuery: """ Rewrite a subquery by injecting answers from its dependency subqueries. @@ -85,7 +86,11 @@ async def _rewrite_subquery(self, subquery: SubQuery, context: Dict[str, str]) - rewritten = response.query if isinstance(response, RewriteQuery) else response return subquery.model_copy(update={"query": rewritten}) - async def _answer_subquery(self, subquery: SubQuery, context: Dict[str, str]) -> str: + async def _answer_subquery( + self, + subquery: SubQuery, + context: Dict[str, SearchEngineResponse] + ) -> Tuple[SubQuery, SearchEngineResponse]: """ Execute a single subquery, rewriting it first if it has dependencies. @@ -98,9 +103,10 @@ async def _answer_subquery(self, subquery: SubQuery, context: Dict[str, str]) -> result = await self.engine.a_query(subquery.query) - return result + return subquery, result - async def a_query(self, query: str) -> str | BaseModel: + @override + async def a_query(self, query: str, *args, **kwargs) -> SearchEngineResponse: """ Execute a complex query using the plan-and-execute pipeline. @@ -120,13 +126,22 @@ async def a_query(self, query: str) -> str | BaseModel: subqueries = await self.process_query(query) ordered = _topological_sort(subqueries) - context: Dict[str, str | BaseModel] = {} + context: Dict[str, SearchEngineResponse] = {} + retrieve: Dict[str, SearchEngineRetrieve] = {} for subquery in ordered: - answer = await self._answer_subquery(subquery, context) - context[subquery.id] = answer + rewritten_subquery, response = await self._answer_subquery(subquery, context) + context[subquery.id] = response + retrieve[subquery.id] = response.retrieval - return context[ordered[-1].id] + return SearchEngineResponse( + query=query, + response=context[ordered[-1].id].response, + retrieval=retrieve[ordered[-1].id], + payload=context, + ) + # TODO: maybe it is good idea to refactor this method so it will returns list of 'subcontexts' for every subquery. + @override async def a_search(self, query, *args, **kwargs): """ Perform a search using the underlying engine. From dd945a45eefec782de615e4bc460277b9b2f6d67 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Fri, 24 Apr 2026 23:35:05 +0700 Subject: [PATCH 30/42] Update prompts and schemas --- ragu/common/prompts/default_models.py | 9 -------- ragu/common/prompts/default_templates.py | 3 --- ragu/common/prompts/prompt_storage.py | 26 +++++++++++------------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/ragu/common/prompts/default_models.py b/ragu/common/prompts/default_models.py index 4d90148..399ae2e 100644 --- a/ragu/common/prompts/default_models.py +++ b/ragu/common/prompts/default_models.py @@ -123,15 +123,6 @@ class GlobalSearchContextModel(BaseModel): rating: confloat(ge=0, le=10) = Field(..., description="Relevance rating of the context 0–10") -class GlobalSearchResponseModel(BaseModel): - reasoning: str = Field(..., description="Reasoning about context relevance and the final answer") - response: str = Field(..., description="Final answer") - - -class DefaultResponseModel(BaseModel): - response: str = Field(..., description="Answer based on the provided context; if unknown, explicitly state so") - - class EntityDescriptionModel(BaseModel): entity_name: str = Field(description="Entity name") description: str = Field(description="Summarized description") diff --git a/ragu/common/prompts/default_templates.py b/ragu/common/prompts/default_templates.py index c3e4b7d..e579ace 100644 --- a/ragu/common/prompts/default_templates.py +++ b/ragu/common/prompts/default_templates.py @@ -131,7 +131,6 @@ Context: {{ context }} Provide the answer in the following language: {{ language }} -Return the result as valid JSON matching the provided schema. """ DEFAULT_RESPONSE_ONLY_PROMPT = """ @@ -146,7 +145,6 @@ Context: {{ context }} Provide the answer in the following language: {{ language }} -Return the result as valid JSON matching the provided schema. """ DEFAULT_MIX_SEARCH_PROMPT = """ @@ -166,7 +164,6 @@ {{ context }} Provide the answer in the following language: {{ language }} -Return the result as valid JSON matching the provided schema. """ DEFAULT_MIX_SEARCH_CONTEXT_PROMPT = """ diff --git a/ragu/common/prompts/prompt_storage.py b/ragu/common/prompts/prompt_storage.py index 57dba1b..fc5ccf3 100644 --- a/ragu/common/prompts/prompt_storage.py +++ b/ragu/common/prompts/prompt_storage.py @@ -1,16 +1,14 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Optional, Type +from typing import Type from pydantic import BaseModel from ragu.common.prompts.default_models import ( ArtifactsModel, CommunityReportModel, - GlobalSearchResponseModel, GlobalSearchContextModel, - DefaultResponseModel, EntityDescriptionModel, RelationDescriptionModel, ClusterSummarizationModel, @@ -47,8 +45,8 @@ @dataclass(frozen=True, slots=True) class RAGUInstruction: messages: ChatMessages - pydantic_model: Optional[Type[BaseModel]] = None - description: Optional[str] = None + pydantic_model: Type[BaseModel] | Type[str] = str + description: str | None = None DEFAULT_PROMPT_TEMPLATES: dict[str, RAGUInstruction] = { @@ -118,7 +116,7 @@ class RAGUInstruction: UserMessage(content=DEFAULT_GLOBAL_SEARCH_PROMPT), ] ), - pydantic_model=GlobalSearchResponseModel, + pydantic_model=str, description="Prompt for generating a synthesized global search response.", ), @@ -128,7 +126,7 @@ class RAGUInstruction: UserMessage(content=DEFAULT_RESPONSE_ONLY_PROMPT), ] ), - pydantic_model=DefaultResponseModel, + pydantic_model=str, description="Prompt for generating a local context-based search response.", ), @@ -138,7 +136,7 @@ class RAGUInstruction: UserMessage(content=DEFAULT_RESPONSE_ONLY_PROMPT), ] ), - pydantic_model=DefaultResponseModel, + pydantic_model=str, description="Prompt for generating a naive vector RAG search response.", ), @@ -148,7 +146,7 @@ class RAGUInstruction: UserMessage(content=DEFAULT_MIX_SEARCH_PROMPT), ] ), - pydantic_model=DefaultResponseModel, + pydantic_model=str, description="Prompt for generating an ensemble response from multiple search contexts.", ), @@ -158,7 +156,7 @@ class RAGUInstruction: UserMessage(content=DEFAULT_MIX_SEARCH_CONTEXT_PROMPT), ] ), - pydantic_model=None, + pydantic_model=str, description="Prompt for formatting ordered multi-engine contexts before final synthesis.", ), @@ -179,7 +177,7 @@ class RAGUInstruction: UserMessage(content=DEFAULT_RAGU_LM_ENTITY_EXTRACTION_PROMPT), ] ), - pydantic_model=None, + pydantic_model=str, description="Instruction for RAGU-lm entity extraction stage.", ), @@ -190,7 +188,7 @@ class RAGUInstruction: UserMessage(content=DEFAULT_RAGU_LM_ENTITY_NORMALIZATION_PROMPT), ] ), - pydantic_model=None, + pydantic_model=str, description="Instruction for RAGU-lm entity normalization stage.", ), @@ -201,7 +199,7 @@ class RAGUInstruction: UserMessage(content=DEFAULT_RAGU_LM_ENTITY_DESCRIPTION_PROMPT), ] ), - pydantic_model=None, + pydantic_model=str, description="Instruction for RAGU-lm entity description stage.", ), @@ -212,7 +210,7 @@ class RAGUInstruction: UserMessage(content=DEFAULT_RAGU_LM_RELATION_DESCRIPTION_PROMPT), ] ), - pydantic_model=None, + pydantic_model=str, description="Instruction for RAGU-lm relation description stage.", ), From 3662a4ba2299c32396b25f23e8d2520895c1f808 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Sat, 25 Apr 2026 00:31:11 +0700 Subject: [PATCH 31/42] Update docstrings --- ragu/search_engine/base_engine.py | 32 +++++++++++++-------- ragu/search_engine/global_search.py | 27 ++++++++++++++---- ragu/search_engine/local_search.py | 27 +++++++++++++++--- ragu/search_engine/mix_search.py | 37 ++++++++++++++++++------- ragu/search_engine/naive_search.py | 23 +++++++++++++-- ragu/search_engine/query_plan.py | 20 ++++++++----- ragu/search_engine/search_functional.py | 32 +++++++++++++++++++++ 7 files changed, 158 insertions(+), 40 deletions(-) diff --git a/ragu/search_engine/base_engine.py b/ragu/search_engine/base_engine.py index 99db22d..879a8f1 100644 --- a/ragu/search_engine/base_engine.py +++ b/ragu/search_engine/base_engine.py @@ -15,7 +15,11 @@ @dataclass(slots=True) class SearchEngineRetrieve(ABC, Generic[ResultT]): """ - Base response class for retrieval. + Base container for search-only results. + + ``result`` stores the engine-specific retrieval payload, while ``metrics`` + stores optional diagnostics such as relevance scores, ranks, timings, or + backend-specific retrieval metadata. """ query: str result: ResultT @@ -24,7 +28,7 @@ class SearchEngineRetrieve(ABC, Generic[ResultT]): @abstractmethod def to_text(self) -> str: """ - How to format the retrieved result. + Render the retrieved context as text suitable for prompt injection. """ ... @@ -35,7 +39,10 @@ def __str__(self) -> str: @dataclass(slots=True) class SearchEngineResponse: """ - Default response for search engine. + Response from search engine. + + ``response`` is the generated answer, ``retrieval`` is the context used to + produce it, and ``payload`` carries optional engine-specific metadata. """ query: str response: str | BaseModel @@ -66,9 +73,12 @@ def __init__( **kwargs: Any, ): """ - Initialize engine with an LLM client. + Initialize an engine with an LLM and context truncation settings. - :param client: LLM client. + :param llm: LLM used by concrete engines for answer generation. + :param max_context_length: Maximum context length after token truncation. + :param tokenizer_backend: Tokenizer backend used for truncation. + :param tokenizer_model: Tokenizer model name used by the backend. """ super().__init__(*args, **kwargs) self.llm = llm @@ -86,17 +96,17 @@ async def a_search( **kwargs, ) -> SearchEngineRetrieve: """ - Retrieve context relevant to a query. + Retrieve context relevant to a query without generating an answer. :param query: Input query string. - :return: Engine-specific retrieval result payload. + :return: Engine-specific retrieval container with result payload and metrics. """ pass @abstractmethod async def a_query(self, query: str, *args, **kwargs) -> SearchEngineResponse: """ - Execute full query flow and return answer. + Execute retrieval and answer generation for a query. :param query: Input query string. :return: Structured search result containing the final answer and retrieval details. @@ -105,7 +115,7 @@ async def a_query(self, query: str, *args, **kwargs) -> SearchEngineResponse: async def query(self, query: str, *args, **kwargs) -> SearchEngineResponse: """ - Synchronous wrapper for ``a_query``. + Delegate to ``a_query`` through the shared event-loop helper. :param query: Input query string. :return: Structured search result containing the final answer and retrieval details. @@ -122,10 +132,10 @@ async def search( **kwargs, ) -> SearchEngineRetrieve: """ - Synchronous wrapper for ``a_search``. + Delegate to ``a_search`` through the shared event-loop helper. :param query: Input query string. - :return: Engine-specific retrieval result payload. + :return: Engine-specific retrieval container with result payload and metrics. """ loop = always_get_an_event_loop() return loop.run_until_complete( diff --git a/ragu/search_engine/global_search.py b/ragu/search_engine/global_search.py index 674652e..7b44eb4 100644 --- a/ragu/search_engine/global_search.py +++ b/ragu/search_engine/global_search.py @@ -20,14 +20,28 @@ @dataclass(slots=True) class GlobalSearchResult: + """ + Ranked community-level insights selected for a global query. + + Each insight is expected to contain at least a ``response`` and ``rating`` + field as produced by the global-search context prompt. + """ insights: list[Any] = field(default_factory=list) @dataclass(slots=True) class GlobalSearchRetrieve(SearchEngineRetrieve[GlobalSearchResult]): + """ + Retrieval container returned by :class:`GlobalSearchEngine`. + + Metrics include per-insight ratings after filtering and sorting. + """ result: GlobalSearchResult def to_text(self) -> str: + """ + Render selected community insights for final answer synthesis. + """ template = Template(dedent(""" {%- for insight in result.insights %} {{ loop.index }}. Insight: {{ insight.response }}, rating: {{ insight.rating }} @@ -59,11 +73,12 @@ def __init__( """ Initialize a new `GlobalSearchEngine`. - :param client: Language model client for generation. + :param llm: Language model client for meta-evaluation and final answer generation. :param knowledge_graph: Knowledge graph providing access to community-level summaries. :param max_context_length: Maximum number of tokens allowed in the truncated context. :param tokenizer_backend: Tokenizer backend used for token counting (default: ``"tiktoken"``). :param tokenizer_model: Model name for tokenizer calibration (default: ``"gpt-4"``). + :param language: Default output language (fed into prompt templates). """ _PROMPTS = ["global_search_context", "global_search"] super().__init__( @@ -88,7 +103,8 @@ async def a_search(self, query: str, *args, **kwargs) -> GlobalSearchRetrieve: concatenation of the top relevant community insights. :param query: The input natural language query. - :return: Concatenated responses from the top-rated communities. + :return: ``GlobalSearchRetrieve`` containing positively rated insights + sorted by descending rating. """ communities_ids = await self.knowledge_graph.index.community_summary_kv_storage.all_keys() @@ -121,7 +137,8 @@ async def get_meta_responses(self, query: str, context: List[str]) -> List[dict] :param query: The user query used to assess community relevance. :param context: A list of community summary texts to evaluate. - :return: A list of structured responses with fields such as ``response`` and ``rating``. + :return: List of structured response dictionaries with fields such as + ``response`` and ``rating``. """ instruction: RAGUInstruction = self.get_prompt("global_search_context") @@ -150,7 +167,8 @@ async def a_query(self, query: str, *args, **kwargs) -> SearchEngineResponse: - Generates a final global answer. :param query: The natural language query from the user. - :return: Generated answer as a string or Pydantic model when a response schema is set. + :return: ``SearchEngineResponse`` containing the generated answer and + the ``GlobalSearchRetrieve`` used as context. """ context = await self.a_search(query) truncated_context: str = self.truncation(str(context)) @@ -175,4 +193,3 @@ async def a_query(self, query: str, *args, **kwargs) -> SearchEngineResponse: retrieval=context, payload={} ) - diff --git a/ragu/search_engine/local_search.py b/ragu/search_engine/local_search.py index d17062f..7894a5f 100644 --- a/ragu/search_engine/local_search.py +++ b/ragu/search_engine/local_search.py @@ -33,6 +33,13 @@ @dataclass(slots=True) class LocalSearchResult: + """ + Retrieved graph-local context for a query. + + Entities are the seed retrieval results. Relations, summaries, and chunks + are derived from those entities and optionally reranked. ``documents_id`` + contains unique source document IDs from the final entity set. + """ entities: list[Entity] = field(default_factory=list) relations: list[Relation] = field(default_factory=list) summaries: list[Any] = field(default_factory=list) @@ -42,9 +49,19 @@ class LocalSearchResult: @dataclass(slots=True) class LocalSearchRetrieve(SearchEngineRetrieve[LocalSearchResult]): + """ + Retrieval container returned by :class:`LocalSearchEngine`. + + Metrics use ``metrics["entities"]`` with one entry per final entity + containing ``id``, ``name``, zero-based ``rank``, and vector + ``relevance_score``. + """ result: LocalSearchResult def to_text(self) -> str: + """ + Render entities, relations, optional summaries, and optional chunks. + """ template = Template(dedent(""" **Entities** Entity, entity type, entity description @@ -144,7 +161,8 @@ async def a_search(self, query: str, top_k: int = 20, *args, **kwargs) -> LocalS :param query: Input query string. :param top_k: Number of top entities to retrieve from the entity vector DB. - :return: LocalSearchResult containing entities, relations, summaries, chunks, and document ids. + :return: ``LocalSearchRetrieve`` containing graph-local context and + entity relevance metrics. """ entities, entity_hits = await self.retriever.query_entities(query, top_k=top_k) entity_scores_by_id = { @@ -227,9 +245,10 @@ async def a_query( :param query: User query in natural language. :param top_k: Number of entities to retrieve into context. - :param use_summary: Whether to use summary or not. - :param use_chunks: Whether to use chunks or not. - :return: Generated answer as a string or Pydantic model when a response schema is set. + :param use_summary: Whether community summaries are included in the generated context. + :param use_chunks: Whether source chunks are included in the generated context. + :return: ``SearchEngineResponse`` containing the generated answer and + the ``LocalSearchRetrieve`` used as context. """ context: LocalSearchRetrieve = await self.a_search(query, top_k) diff --git a/ragu/search_engine/mix_search.py b/ragu/search_engine/mix_search.py index 95e7a3d..9c3488d 100644 --- a/ragu/search_engine/mix_search.py +++ b/ragu/search_engine/mix_search.py @@ -15,14 +15,29 @@ @dataclass(slots=True) class MixSearchResult: + """ + Aggregated child-engine outputs. + + ``results`` contains either retrieval containers from child ``a_search`` + calls or full ``SearchEngineResponse`` objects from child ``a_query`` calls, + depending on the synthesis mode. + """ results: list[SearchEngineRetrieve[Any]] | list[SearchEngineResponse] = field(default_factory=list) @dataclass(slots=True) class MixSearchRetrieve(SearchEngineRetrieve[MixSearchResult]): + """ + Retrieval container returned by :class:`MixSearchEngine`. + + Metrics are currently empty; child-engine metrics remain available inside each entry. + """ result: MixSearchResult def to_text(self) -> str: + """ + Render each child engine result as a separate context section. + """ template = Template(dedent(""" {%- for retrieve in result.results %} **Engine {{ loop.index }} Context** @@ -61,7 +76,7 @@ def __init__( :param llm: LLM used to generate the final synthesized answer. :param engines: Ordered list of child engines used for retrieval or answer ensembling. :param allow_partial_failures: Whether to tolerate failures from individual child engines. - Failed engines yield ``None`` in the ordered result list. + Failed engines are omitted from the result list. :param max_context_length: Max tokens allowed for the synthesized context after truncation. :param tokenizer_backend: Tokenizer backend used for token truncation. :param tokenizer_model: Model name used by the tokenizer backend. @@ -95,8 +110,9 @@ async def _search_all( Execute ``a_search`` on each child engine. :param query: Input query string. - :return: Ordered list of per-engine search contexts. Failed engines return ``None`` when - ``allow_partial_failures=True``. + :return: Ordered list of successful per-engine search contexts. Failed + engines are omitted when ``allow_partial_failures=True``. + :raises RuntimeError: If every child engine fails. """ tasks = [ engine.a_search(query, *args, **kwargs) @@ -127,8 +143,9 @@ async def _query_all( Execute ``a_query`` on each child engine. :param query: Input query string. - :return: Ordered list of per-engine answers. Failed engines return ``None`` when - ``allow_partial_failures=True``. + :return: Ordered list of successful per-engine answers. Failed engines + are omitted when ``allow_partial_failures=True``. + :raises RuntimeError: If every child engine fails. """ tasks = [ engine.a_query(query, *args, **kwargs) @@ -150,14 +167,13 @@ async def _query_all( return contexts @override - async def a_search(self, query: str, *args: Any, **kwargs: Any) -> SearchEngineRetrieve: + async def a_search(self, query: str, *args: Any, **kwargs: Any) -> MixSearchRetrieve: """ Retrieve raw contexts from all child engines. :param query: Input query string. - :return: Ordered list of per-engine search contexts matching the engine order passed to - the constructor. Failed engines are represented as ``None`` when partial failures - are enabled. + :return: ``MixSearchRetrieve`` containing successful child retrieval + contexts in engine order. """ results = await self._search_all(query, *args, **kwargs) @@ -188,7 +204,8 @@ async def a_query( :param query: Input query string. :param ensemble_responses: Whether to ensemble child-engine answers instead of child-engine search contexts. - :return: Generated answer as a string or Pydantic model when a response schema is set. + :return: ``SearchEngineResponse`` containing the synthesized answer and + the child contexts or responses used for synthesis. """ results = await ( self._query_all(query, *args, **kwargs) diff --git a/ragu/search_engine/naive_search.py b/ragu/search_engine/naive_search.py index 496efdb..fa10dc8 100644 --- a/ragu/search_engine/naive_search.py +++ b/ragu/search_engine/naive_search.py @@ -22,6 +22,13 @@ @dataclass(slots=True) class NaiveSearchResult: + """ + Retrieved chunk payload for naive vector search. + + ``chunks`` and ``scores`` are aligned by index after optional reranking and + truncation by ``rerank_top_k``. ``documents_id`` contains unique document IDs + present in the final chunk list. + """ chunks: list[Chunk] = field(default_factory=list) scores: list[float] = field(default_factory=list) documents_id: list[str] = field(default_factory=list) @@ -29,9 +36,18 @@ class NaiveSearchResult: @dataclass(slots=True) class NaiveSearchRetrieve(SearchEngineRetrieve[NaiveSearchResult]): + """ + Retrieval container returned by :class:`NaiveSearchEngine`. + + Metrics use ``metrics["chunks"]`` with one entry per final chunk containing + ``id``, zero-based ``rank``, and retrieval or reranker ``score``. + """ result: NaiveSearchResult def to_text(self) -> str: + """ + Render retrieved chunks and aligned scores for the answer prompt. + """ template = Template(dedent(""" **Retrieved Chunks** {%- for chunk, score in zip(result.chunks, result.scores) %} @@ -113,7 +129,8 @@ async def a_search( :param top_k: Number of top chunks to retrieve initially. :param rerank_top_k: Number of chunks to keep after reranking. If None, keeps all reranked chunks. Used only when reranker is set. - :return: NaiveSearchResult with retrieved chunks, scores, and document ids. + :return: ``NaiveSearchRetrieve`` with retrieved chunks, aligned scores, + document IDs, and chunk rank metrics. """ chunks, scores = await self.retriever.query_chunks(query, top_k=top_k) @@ -169,8 +186,8 @@ async def a_query(self, query: str, top_k: int = 20, rerank_top_k: Optional[int] :param query: User query in natural language. :param top_k: Number of chunks to search initially (default: 20). :param rerank_top_k: Number of chunks to use after reranking (default: None = use all). - :return: Generated answer as a string or Pydantic model when a response schema is set. - :rtype: str | BaseModel + :return: ``SearchEngineResponse`` containing the generated answer and + the ``NaiveSearchRetrieve`` used as context. """ context: NaiveSearchRetrieve = await self.a_search(query, top_k, rerank_top_k) truncated_context: str = self.truncation(str(context)) diff --git a/ragu/search_engine/query_plan.py b/ragu/search_engine/query_plan.py index 1c5551a..81492c8 100644 --- a/ragu/search_engine/query_plan.py +++ b/ragu/search_engine/query_plan.py @@ -27,6 +27,11 @@ class QueryPlanEngine(BaseEngine): """ def __init__(self, engine: BaseEngine, *args, **kwargs): + """ + Initialize a query planner around an existing search engine. + + :param engine: Engine used to answer each planned subquery. + """ _PROMPTS_NAMES = ["query_decomposition", "query_rewrite"] super().__init__(llm=engine.llm, prompts=_PROMPTS_NAMES, *args, **kwargs) self.engine: BaseEngine = engine @@ -65,8 +70,8 @@ async def _rewrite_subquery(self, subquery: SubQuery, context: Dict[str, SearchE to the rewrite prompt. :param subquery: The subquery to rewrite. - :param context: Mapping of {subquery_id -> answer} accumulated so far. - :return: Rewritten, self-contained query string. + :param context: Mapping of subquery ID to prior ``SearchEngineResponse``. + :return: Copy of ``subquery`` with a rewritten, self-contained query. """ dep_context = {k: v for k, v in context.items() if k in subquery.depends_on} @@ -96,7 +101,7 @@ async def _answer_subquery( :param subquery: The subquery to execute. :param context: Mapping of {subquery_id -> answer} for dependency injection. - :return: Answer string or Pydantic model for this subquery. + :return: Tuple containing the possibly rewritten subquery and its full response. """ if subquery.depends_on: subquery = await self._rewrite_subquery(subquery, context) @@ -120,8 +125,9 @@ async def a_query(self, query: str, *args, **kwargs) -> SearchEngineResponse: by injecting answers from their prerequisite subqueries. :param query: The complex natural-language query to answer. - :return: Pydantic model instance when a response schema is set, otherwise a plain string. - :rtype: str | BaseModel + :return: ``SearchEngineResponse`` whose ``response`` is the final + subquery answer, ``retrieval`` is the final subquery retrieval, + and ``payload`` contains all subquery responses by ID. """ subqueries = await self.process_query(query) ordered = _topological_sort(subqueries) @@ -144,11 +150,11 @@ async def a_query(self, query: str, *args, **kwargs) -> SearchEngineResponse: @override async def a_search(self, query, *args, **kwargs): """ - Perform a search using the underlying engine. + Delegate search directly to the underlying engine. :param query: The search query. :param args: Additional positional arguments passed to the underlying engine. :param kwargs: Additional keyword arguments passed to the underlying engine. - :return: Search results from the underlying engine. + :return: Retrieval container returned by the underlying engine. """ return await self.engine.a_search(query, *args, **kwargs) diff --git a/ragu/search_engine/search_functional.py b/ragu/search_engine/search_functional.py index 7919ac6..455fb49 100644 --- a/ragu/search_engine/search_functional.py +++ b/ragu/search_engine/search_functional.py @@ -16,6 +16,11 @@ async def _find_most_related_edges_from_entities( entities: list[Entity], knowledge_graph: KnowledgeGraph, ) -> list[Relation]: + """ + Return unique graph edges adjacent to the seed entities. + + Edges are deduplicated by stored relation ID and sorted by descending ``relation_strength``. + """ entity_ids = [entity.id for entity in entities if entity and entity.id] if not entity_ids: return [] @@ -51,6 +56,12 @@ async def _find_most_related_text_unit_from_entities( entities: List[Entity], knowledge_graph: KnowledgeGraph ) -> list[Chunk]: + """ + Return source chunks associated with seed entities. + + Chunks are ordered first by the seed entity order, then by how many one-hop + neighboring entities share the same chunk. + """ seed_entities = [entity for entity in entities if entity and entity.id] if not seed_entities: return [] @@ -111,6 +122,9 @@ async def _find_most_related_text_unit_from_entities( ] async def _find_documents_id(entities: List[Entity]): + """ + Collect unique document IDs referenced by the supplied entities. + """ documents_set = set() for entity in entities: if hasattr(entity, 'documents_id') and entity.documents_id: @@ -123,6 +137,13 @@ async def _find_most_related_community_from_entities( knowledge_graph: KnowledgeGraph, level: int = 2 ) -> list[CommunitySummary]: + """ + Return community summaries linked to seed entity cluster memberships. + + Only clusters with ``level <= level`` are considered. Cluster IDs may be + stored either as full community IDs or as numeric local cluster IDs that are + converted to stable :class:`Community` IDs. + """ if not entities: return [] @@ -177,6 +198,11 @@ async def _rerank_items( text_getter: Callable[[T], str], reranker: Scorer | None, ) -> list[T]: + """ + Rerank items with an optional scorer while preserving original items. + + If ``reranker`` is not provided, items are returned unchanged. + """ if reranker is None or not items: return items @@ -187,6 +213,12 @@ async def _rerank_items( return [items[idx] for idx, _ in rerank_results if 0 <= idx < len(items)] def _topological_sort(subqueries: List[SubQuery]) -> List[SubQuery]: + """ + Sort subqueries so dependencies appear before dependent subqueries. + + :param subqueries: Query-plan nodes keyed by their ``id`` fields. + :return: Topologically ordered subquery list. + """ by_id = {q.id: q for q in subqueries} visited = set() ordered: List[SubQuery] = [] From f8f65e6e82bfb2698b7841e729cb4b2b47291bfa Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Sat, 25 Apr 2026 12:53:41 +0700 Subject: [PATCH 32/42] Fix bug with async in sync functions --- ragu/search_engine/base_engine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ragu/search_engine/base_engine.py b/ragu/search_engine/base_engine.py index 879a8f1..7b9438f 100644 --- a/ragu/search_engine/base_engine.py +++ b/ragu/search_engine/base_engine.py @@ -113,7 +113,7 @@ async def a_query(self, query: str, *args, **kwargs) -> SearchEngineResponse: """ pass - async def query(self, query: str, *args, **kwargs) -> SearchEngineResponse: + def query(self, query: str, *args, **kwargs) -> SearchEngineResponse: """ Delegate to ``a_query`` through the shared event-loop helper. @@ -125,7 +125,7 @@ async def query(self, query: str, *args, **kwargs) -> SearchEngineResponse: self.a_query(query, *args, **kwargs) ) - async def search( + def search( self, query, *args, From fa944a7647d8a74dfe26d25ec8172e0cdeb8b1a1 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Sat, 25 Apr 2026 12:55:13 +0700 Subject: [PATCH 33/42] Small fix --- ragu/search_engine/global_search.py | 3 +-- ragu/search_engine/query_plan.py | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ragu/search_engine/global_search.py b/ragu/search_engine/global_search.py index 7b44eb4..184f5f4 100644 --- a/ragu/search_engine/global_search.py +++ b/ragu/search_engine/global_search.py @@ -5,7 +5,6 @@ from jinja2 import Template -from ragu.common.base import RaguGenerativeModule from ragu.common.global_parameters import Settings from ragu.graph.knowledge_graph import KnowledgeGraph from ragu.models.llm import LLM @@ -50,7 +49,7 @@ def to_text(self) -> str: return template.render(result=self.result) -class GlobalSearchEngine(BaseEngine, RaguGenerativeModule): +class GlobalSearchEngine(BaseEngine): """ Executes global retrieval-augmented search (RAG) across the entire knowledge graph. diff --git a/ragu/search_engine/query_plan.py b/ragu/search_engine/query_plan.py index 81492c8..403e43f 100644 --- a/ragu/search_engine/query_plan.py +++ b/ragu/search_engine/query_plan.py @@ -1,8 +1,6 @@ from typing import List, Dict, Tuple from typing_extensions import override -from pydantic import BaseModel - from ragu.common.prompts.default_models import SubQuery, QueryPlan, RewriteQuery from ragu.search_engine.base_engine import BaseEngine, SearchEngineResponse, SearchEngineRetrieve from ragu.search_engine.search_functional import _topological_sort From fb71db08f8363f288ac8a9fadc9cfe20de4a1f61 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Sat, 25 Apr 2026 13:21:54 +0700 Subject: [PATCH 34/42] Update types --- ragu/search_engine/global_search.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ragu/search_engine/global_search.py b/ragu/search_engine/global_search.py index 184f5f4..c5cfcbd 100644 --- a/ragu/search_engine/global_search.py +++ b/ragu/search_engine/global_search.py @@ -4,8 +4,10 @@ from typing import Any, List, Literal from jinja2 import Template - from ragu.common.global_parameters import Settings +from ragu.common.prompts.default_models import GlobalSearchContextModel +from ragu.common.prompts.messages import ChatMessages, render +from ragu.common.prompts.prompt_storage import RAGUInstruction from ragu.graph.knowledge_graph import KnowledgeGraph from ragu.models.llm import LLM from ragu.search_engine.base_engine import ( @@ -13,10 +15,8 @@ SearchEngineRetrieve, SearchEngineResponse ) -from ragu.common.prompts.prompt_storage import RAGUInstruction -from ragu.common.prompts.messages import ChatMessages, render - +# TODO: add the ability to use custom schemas instead of GlobalSearchContextModel @dataclass(slots=True) class GlobalSearchResult: """ @@ -25,7 +25,7 @@ class GlobalSearchResult: Each insight is expected to contain at least a ``response`` and ``rating`` field as produced by the global-search context prompt. """ - insights: list[Any] = field(default_factory=list) + insights: list[GlobalSearchContextModel] = field(default_factory=list) @dataclass(slots=True) @@ -110,7 +110,7 @@ async def a_search(self, query: str, *args, **kwargs) -> GlobalSearchRetrieve: communities = await self.knowledge_graph.index.community_summary_kv_storage.get_by_ids(communities_ids) communities = [c for c in communities if c is not None] - responses = await self.get_meta_responses(query, communities) + responses = [r.model_dump() for r in await self.get_meta_responses(query, communities)] responses = [r for r in responses if int(r.get("rating", 0)) > 0] responses = sorted(responses, key=lambda x: int(x.get("rating", 0)), reverse=True) @@ -126,7 +126,7 @@ async def a_search(self, query: str, *args, **kwargs) -> GlobalSearchRetrieve: }, ) - async def get_meta_responses(self, query: str, context: List[str]) -> List[dict]: + async def get_meta_responses(self, query: str, context: List[str]) -> List[GlobalSearchContextModel]: """ Generate and evaluate meta-responses for each community summary. @@ -148,7 +148,7 @@ async def get_meta_responses(self, query: str, context: List[str]) -> List[dict] language=self.language, ) - meta_responses = await asyncio.gather(*[ + meta_responses: List[GlobalSearchContextModel] = await asyncio.gather(*[ self.llm.chat_completion( conversation=rendered.to_openai(), output_schema=instruction.pydantic_model or str, # type: ignore @@ -156,7 +156,7 @@ async def get_meta_responses(self, query: str, context: List[str]) -> List[dict] for rendered in rendered_list ]) # type: ignore - return [r.model_dump() for r in meta_responses if r] + return meta_responses async def a_query(self, query: str, *args, **kwargs) -> SearchEngineResponse: """ From 17690858f60542d5ad5012cc6747e9000270e22c Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Sun, 26 Apr 2026 03:05:20 +0700 Subject: [PATCH 35/42] Update docs --- README.md | 147 ++++++++++++++++---- docs/en/ragu_components.md | 265 ++++++++++++++++++++++++++---------- docs/ru/ragu_components.md | 269 ++++++++++++++++++++++++++----------- 3 files changed, 509 insertions(+), 172 deletions(-) diff --git a/README.md b/README.md index 104e797..577806a 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,33 @@ ## Overview -RAGU provides a pipeline for building a **Knowledge Graph**, and performing retrieve over the indexed data. It contains different approaches to extract structured data from raw texts to enable efficient question-answering over structured knowledge. +RAGU is a modular GraphRAG engine for building, storing, and querying knowledge graphs from text. It combines: + +- chunking of raw documents into stable `Chunk` objects; +- LLM-based extraction of `Entity` and `Relation` graph artifacts; +- graph construction, deduplication, optional description summarization, and Leiden community detection; +- graph, key-value, and vector storage backends; +- retrieval strategies for local graph neighborhoods, global community summaries, naive chunk-vector RAG, and mixed search. Partially based on [nano-graphrag](https://github.com/gusye1234/nano-graphrag/tree/main) Our huggingface community is [here](https://huggingface.co/RaguTeam/) +### Module documentation + +- [Package facade](ragu/README.md) +- [Chunking](ragu/chunker/README.md) +- [Common settings, prompts, and utilities](ragu/common/README.md) +- [Prompt schemas and templates](ragu/common/prompts/README.md) +- [Graph construction and index](ragu/graph/README.md) +- [Models, embedders, sparse embedders, and rerankers](ragu/models/README.md) +- [Search engines](ragu/search_engine/README.md) +- [Storage contracts and adapters](ragu/storage/README.md) +- [Graph storage adapters](ragu/storage/graph_storage_adapters/README.md) +- [Vector DB adapters](ragu/storage/vdb_storage_adapters/README.md) +- [Triplet/entity-relation extraction](ragu/triplet/README.md) +- [Utilities](ragu/utils/README.md) + --- ## Install @@ -56,6 +77,7 @@ pip install graph_ragu[local] ### Simple example of building knowledge graph ```python +import asyncio import os import sys import shutil @@ -69,7 +91,7 @@ from ragu import ( KnowledgeGraph, BuilderArguments, Settings, - ArtifactsExtractorLLM, + TwoStageArtifactsExtractorLLM, ) from ragu.models.embedder import EmbedderOpenAI from ragu.models.llm import LLMOpenAI @@ -86,8 +108,15 @@ client = CachedAsyncOpenAI( debug_errors_storage='./llm_debug', ) -llm = LLMOpenAI(client, "mistralai/mistral-medium-3") -embedder = EmbedderOpenAI(client, "emb-qwen/qwen3-embedding-8b", dim=4096) +llm = LLMOpenAI( + client=client, + model_name="mistralai/mistral-medium-3", +) +embedder = EmbedderOpenAI( + client=client, + model_name="emb-qwen/qwen3-embedding-8b", + dim=4096, +) # Configure working directory and language Settings.storage_folder = "ragu_working_dir" @@ -102,15 +131,19 @@ docs = read_text_from_files("path/to/your/files") chunker = SimpleChunker(max_chunk_size=1000) # Set up artifact extractor -artifact_extractor = ArtifactsExtractorLLM( +artifact_extractor = TwoStageArtifactsExtractorLLM( llm=llm, - do_validation=False + do_entity_validation=True, + do_relation_validation=True, ) # Configure builder settings builder_settings = BuilderArguments( use_llm_summarization=True, - vectorize_chunks=True, + use_clustering=False, + build_only_vector_context=False, + make_community_summary=True, + remove_isolated_nodes=True, ) # Build knowledge graph @@ -136,13 +169,17 @@ Search over entities retrieved for the query and their connected context (relati from ragu.search_engine.local_search import LocalSearchEngine local_search = LocalSearchEngine( - llm, # or use another LLM for answering - knowledge_graph, - embedder, + llm=llm, # or use another LLM for answering + knowledge_graph=knowledge_graph, + embedder=embedder, tokenizer_model="gpt-4o-mini", ) # found = await local_search.a_search("What is the Betweenlands??") -local_answer = await local_search.a_query("Who wrote Romeo and Juliet?") +local_answer = await local_search.a_query( + "Who wrote Romeo and Juliet?", + use_summary=True, + use_chunks=True, +) print(local_answer.response) ``` @@ -156,7 +193,7 @@ global_search = GlobalSearchEngine( knowledge_graph=knowledge_graph, ) global_answer = await global_search.a_query("Your broad query here") -print(global_answer) +print(global_answer.response) ``` **Naive search (vector RAG):** @@ -169,7 +206,19 @@ naive_search = NaiveSearchEngine( embedder=embedder, ) naive_answer = await naive_search.a_query("Your query here") -print(naive_answer) +print(naive_answer.response) +``` + +**Mixed search:** +```python +from ragu import MixSearchEngine + +mix_search = MixSearchEngine( + llm=llm, + engines=[local_search, naive_search, global_search], +) +mixed_answer = await mix_search.a_query("Your query here") +print(mixed_answer.response) ``` ### Query planning wrapper @@ -197,7 +246,7 @@ print(result) #### Builder Settings -Configure the knowledge graph building pipeline using `BuilderSettings`: +Configure the knowledge graph building pipeline using `BuilderArguments`: ```python from ragu import BuilderArguments @@ -210,17 +259,63 @@ builder_arguments = BuilderArguments( remove_isolated_nodes=True, # Remove entities without relations vectorize_chunks=True, # Vectorize chunk for naive (vector) search cluster_only_if_more_than=10000, # Minimum entities before clustering kicks in + summarize_only_if_more_than=7, # Summarize descriptions only when there are many duplicates max_cluster_size=128, # Maximum entities per cluster + random_seed=42, ) # Pass to KnowledgeGraph -knowledge_graph = await KnowledgeGraph( - client=client, +knowledge_graph = KnowledgeGraph( + llm=llm, embedder=embedder, chunker=chunker, artifact_extractor=artifact_extractor, builder_settings=builder_arguments, -).build_from_docs(docs) +) +await knowledge_graph.build_from_docs(docs) +``` + +Common builder presets: + +```python +# Naive vector RAG only: chunks + chunk vectors, no entity/relation graph. +BuilderArguments( + build_only_vector_context=True, + make_community_summary=False, +) + +# Fast graph extraction: no extra LLM summarization and no communities. +BuilderArguments( + use_llm_summarization=False, + use_clustering=False, + make_community_summary=False, + remove_isolated_nodes=True, +) + +# Full GraphRAG: extraction, summarization, communities, global search support. +BuilderArguments( + use_llm_summarization=True, + use_clustering=False, + make_community_summary=True, + remove_isolated_nodes=True, +) +``` + +#### Storage Settings + +RAGU stores graph structure, KV data, and vectors through pluggable adapters. See [storage docs](ragu/storage/README.md), [graph storage adapters](ragu/storage/graph_storage_adapters/README.md), and [vector DB adapters](ragu/storage/vdb_storage_adapters/README.md). + +```python +from ragu import StorageArguments +from ragu.storage.vdb_storage_adapters.qdrant_vdb import QdrantVectorDBStorage + +storage_settings = StorageArguments( + vdb_storage_type=QdrantVectorDBStorage, + vdb_storage_kwargs={ + "location": ":memory:", + # Use sparse_type="bm25", "bm42", or "splade" for hybrid retrieval. + }, +) ``` --- @@ -231,6 +326,8 @@ Each text in corpus is processed to extract structured information. It consist o * **Relations** — textual description of the link between two entities (or a relation class), as well as its confidence/strength. > **RAGU uses entity and relation classes from [NEREL](https://github.com/nerel-ds/NEREL).** +> +> `Entity` and `Relation` are base graph model classes. They can be inherited to create richer domain-specific node and edge types, provided the storage `Node` / `Edge` contract is preserved. See [graph docs](ragu/graph/README.md) and [storage docs](ragu/storage/README.md). ### Entity types @@ -276,7 +373,12 @@ Each text in corpus is processed to extract structured information. It consist o File: ragu/triplet/llm_artifact_extractor.py. A baseline pipeline that uses LLM to extract entities, relations, and their descriptions in a single step. -#### 2. [RAGU-lm](https://huggingface.co/RaguTeam/RAGU-lm) (for russian language) +#### 2. Two-stage LLM Pipeline + +File: ragu/triplet/two_stage_extractor.py. +Extracts entities first, then extracts relations constrained by the entity list. It can separately validate entity and relation outputs. + +#### 3. [RAGU-lm](https://huggingface.co/RaguTeam/RAGU-lm) (for russian language) A compact model (Qwen-3-0.6B) fine-tuned on the NEREL dataset. The pipeline operates in several stages: 1. Extract unnormalized entities from text. @@ -303,9 +405,9 @@ All RAGU components that use LLMs inherit from `RaguGenerativeModule`, which pro from ragu import LocalSearchEngine search_engine = LocalSearchEngine( - client, - knowledge_graph, - embedder + llm=llm, + knowledge_graph=knowledge_graph, + embedder=embedder, ) # Get all prompts used by the search engine @@ -331,7 +433,6 @@ from textwrap import dedent from ragu.common.prompts.prompt_storage import RAGUInstruction from ragu.common.prompts.messages import ChatMessages, UserMessage, SystemMessage -from ragu.common.prompts.default_models import DefaultResponseModel # Create custom prompt instruction custom_instruction = RAGUInstruction( @@ -348,7 +449,7 @@ custom_instruction = RAGUInstruction( """ )) # Can store any conversation ]), - pydantic_model=DefaultResponseModel, # Your pydantic model (optional) + pydantic_model=str, # Or your own pydantic BaseModel subclass description="Custom local search prompt with academic focus" # Optional ) diff --git a/docs/en/ragu_components.md b/docs/en/ragu_components.md index bca3fa1..8157d21 100644 --- a/docs/en/ragu_components.md +++ b/docs/en/ragu_components.md @@ -43,8 +43,8 @@ To make processing more efficient, the input text corpus is divided into small f RAGU supports several chunking strategies: * **`SimpleChunker`** — fixed-length splitting by text size. -* **`SemanticChunker`** — chunking that preserves semantic coherence between sentences. -* **`SmartChunker`** — an advanced semantic chunking approach. +* **`SemanticTextChunker`** — chunking that preserves semantic coherence between sentences. +* **`SmartSemanticChunker`** — an advanced semantic chunking approach based on `smart_chunker`. --- @@ -56,6 +56,8 @@ For each text fragment, structured information is extracted: * **Relations** — description of the semantic link between two entities (or a relation class), and its confidence score. > **RAGU uses entity and relation classes from [NEREL](https://github.com/nerel-ds/NEREL).** +> +> `Entity` and `Relation` are base graph model classes. They can be inherited for domain-specific nodes and edges as long as storage adapters can still operate on the `Node` / `Edge` base contracts. **Entity classes:** @@ -94,11 +96,7 @@ For each text fragment, structured information is extracted: | 16. | FOUNDED_BY | 33. | PLACE_OF_BIRTH | | | | 17. | HAS_CAUSE | 34. | PLACE_OF_DEATH | | | -In the standard extraction pipeline, 29 NEREL entity types are used, while relation types are inferred dynamically. -This behavior is defined by the `artifacts_extractor_prompt` instruction. - -> **Planned updates:** -> Integration of entity/relation extraction using either a lightweight fine-tuned LLM or a combination of specialized NER and RE models — both aligned with NEREL taxonomy. +In the standard extraction pipeline, NEREL entity and relation types are injected into extraction prompts by default. You can pass custom `entity_types` and `relation_types` to the LLM extractors when you need a different ontology. --- @@ -131,7 +129,7 @@ Responsible components: ### 4. Graph Construction and Community Detection After entity and relation extraction, all elements are merged into a unified graph. -Logically, the graph is **directed** (subject → object), but for practical processing, it is stored as **undirected**. +The persisted graph is a directed multigraph: relations preserve source entity, target entity, and relation identity. Community detection builds an undirected projection internally for clustering. To enable **abstractive question answering**, RAGU follows the **GraphRAG methodology**, where the graph is clustered into **communities** and summarized. Community detection is performed using the [Leiden algorithm](https://en.wikipedia.org/wiki/Leiden_algorithm). @@ -232,36 +230,56 @@ RAGU implements several chunking strategies and supports user-defined ones. The chunker interface is defined in `BaseChunker` (`ragu/chunker/base_chunker.py`). -RAGU has "naive" chunker and semantic ones. +RAGU has a naive chunker and semantic chunkers: + +* **`SimpleChunker`** — fixed-size text splitting. +* **`SemanticTextChunker`** — sentence-aware semantic splitting. +* **`SmartSemanticChunker`** — semantic splitting backed by `smart_chunker`. -The semantic chunker is implemented as `SmartChunker`. +The advanced semantic chunker is implemented as `SmartSemanticChunker`. See details at: [smart_chunker GitHub](https://github.com/bond005/smart_chunker/tree/main). --- ### Graph Extraction Pipeline -RAGU supports two extraction strategies: +RAGU supports three extraction strategies: -1. Classical LLM-based pipeline. -2. a **lightweight fine-tuned model approach** with [RAGU-lm](https://huggingface.co/RaguTeam/RAGU-lm) (only for Russian language) +1. Single-step LLM extraction with `ArtifactsExtractorLLM`. +2. Two-stage LLM extraction with `TwoStageArtifactsExtractorLLM`. +3. a **lightweight fine-tuned model approach** with [RAGU-lm](https://huggingface.co/RaguTeam/RAGU-lm) (only for Russian language) -#### LLMArtifactExtractor +#### ArtifactsExtractorLLM -`LLMArtifactExtractor` implements the standard GraphRAG method for entity and relation extraction via LLMs. +`ArtifactsExtractorLLM` implements the standard GraphRAG method for entity and relation extraction via LLMs. ```python -from ragu.triplet import ArtifactsExtractorLLM +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.triplet import ArtifactsExtractorLLM, TwoStageArtifactsExtractorLLM -... +client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", +) +llm = LLMOpenAI( + client=client, + model_name="gpt-4o-mini", +) + +single_step_extractor = ArtifactsExtractorLLM( + llm=llm, + do_validation=True, +) -pipeline = ArtifactsExtractorLLM( - client=client, # LLM client from ragu.llm - do_validation=True # Enables artifact validation +two_stage_extractor = TwoStageArtifactsExtractorLLM( + llm=llm, + do_entity_validation=True, + do_relation_validation=True, ) ``` -Extraction and validation are driven by the `artifact_extraction` and `artifact_validation` prompts. +Single-step extraction and validation are driven by the `artifact_extraction` and `artifact_validation` prompts. Two-stage extraction uses `entity_extraction`, `entity_validation`, `relation_extraction`, and `relation_validation`. #### RAGU-lm @@ -429,15 +447,26 @@ sudo vllm serve RaguTeam/ragu-lm --max_model_len 4096 Initialize RaguLmArtifactExtractor and use it. ```python +from ragu.chunker.types import Chunk +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI from ragu.triplet.ragu_lm_artifact_extractor import RaguLmArtifactExtractor -... +client = CachedAsyncOpenAI( + base_url="http://0.0.0.0:8000/v1/", + api_key="dummy-api-token", +) +llm = LLMOpenAI( + client=client, + model_name="RaguTeam/ragu-lm", +) pipeline = RaguLmArtifactExtractor( - ragu_lm_vllm_url="http://0.0.0.0:8000/v1/", + llm=llm, ) -entities, relations = pipeline(["some_texts"]) +chunks = [Chunk(content="Some source text.", chunk_order_idx=0, doc_id="doc-1")] +entities, relations = await pipeline.extract(chunks) ``` --- @@ -448,34 +477,48 @@ For efficient retrieval, all graph elements are indexed and stored. The main abstraction for managing this data is the `KnowledgeGraph` class. ```python -# Step 1 — configure the graph building pipeline -pipeline = InMemoryGraphBuilder( - client, - chunker, - artifact_extractor, - embedder=embedder, - use_clustering=True, - cluster_only_if_more_than=2 +from ragu import BuilderArguments, KnowledgeGraph, SimpleChunker +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.triplet import TwoStageArtifactsExtractorLLM + +client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", +) +llm = LLMOpenAI( + client=client, + model_name="gpt-4o-mini" +) +embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-large", + dim=3072, ) -# Step 2 — configure storage -index = Index( - embedder, - graph_storage_kwargs={"clustering_params": {"max_cluster_size": 6}} +chunker = SimpleChunker(max_chunk_size=1000) +artifact_extractor = TwoStageArtifactsExtractorLLM(llm=llm) +builder_settings = BuilderArguments( + use_llm_summarization=True, + use_clustering=True, + cluster_only_if_more_than=2, + make_community_summary=True, ) -``` -```python knowledge_graph = KnowledgeGraph( - extraction_pipeline=pipeline, - index=index, - make_community_summary=True, + llm=llm, + embedder=embedder, + chunker=chunker, + artifact_extractor=artifact_extractor, + builder_settings=builder_settings, language="russian", ) + +await knowledge_graph.build_from_docs(["Text document to index."]) ``` -Each entity’s description is embedded into a vector representation and stored in a vector database (`nano-db` by default). -Other artifacts are stored in JSON format. +Each entity, relation, community summary, and source chunk is stored through the configured storage adapters. Dense vectors are produced by `embedder`; optional sparse vectors are produced by `sparse_embedder` and used for hybrid search. --- @@ -496,16 +539,15 @@ This structured context is used to answer the user’s query. ```python from ragu.search_engine import LocalSearchEngine -... - -search_engine = LocalSearchEngine( - client, # LLM - knowledge_graph, # Knowledge graph - embedder # Embedder for query vectorization +local_search = LocalSearchEngine( + llm=llm, # LLM for the final answer + knowledge_graph=knowledge_graph, # Knowledge graph + embedder=embedder, # Embedder for query vectorization ) -search_result = await search_engine.a_search("Who wrote the novel 'Quo Vadis'?") -response = await search_engine.a_query("Who wrote the novel 'Quo Vadis'?") +search_result = await local_search.a_search("Who wrote the novel 'Quo Vadis'?") +result = await local_search.a_query("Who wrote the novel 'Quo Vadis'?") +print(result.response) ``` #### Global Search @@ -520,15 +562,78 @@ Global search operates on community summaries: ```python from ragu.search_engine import GlobalSearchEngine -... +global_search = GlobalSearchEngine( + llm=llm, + knowledge_graph=knowledge_graph, +) + +search_result = await global_search.a_search("Who wrote the novel 'Quo Vadis'?") +result = await global_search.a_query("Who wrote the novel 'Quo Vadis'?") +print(result.response) +``` -search_engine = GlobalSearchEngine( - client, - knowledge_graph +#### Naive Search + +Naive search is vector RAG over source chunks. It does not expand through graph neighborhoods; it retrieves relevant chunks from vector storage and uses them as context for the LLM. + +```python +from ragu.search_engine import NaiveSearchEngine + +naive_search = NaiveSearchEngine( + llm=llm, + knowledge_graph=knowledge_graph, + embedder=embedder, +) + +search_result = await naive_search.a_search("Who wrote the novel 'Quo Vadis'?") +result = await naive_search.a_query("Who wrote the novel 'Quo Vadis'?") +print(result.response) +``` + +#### Mix Search + +Mix search runs several search engines and asks the LLM to synthesize a final answer from their responses. It is useful when you want graph neighborhood context, chunk-level vector context, and community-summary context in one answer. + +```python +from ragu.search_engine import MixSearchEngine + +mix_search = MixSearchEngine( + llm=llm, + engines=[local_search, naive_search, global_search], +) + +result = await mix_search.a_query("Who wrote the novel 'Quo Vadis'?") +print(result.response) +``` + +#### Query Planning + +`QueryPlanEngine` wraps any search engine and decomposes complex questions into dependent subqueries before producing the final answer. + +```python +from ragu.search_engine import QueryPlanEngine + +planned_search = QueryPlanEngine(local_search) + +result = await planned_search.a_query( + "Who wrote the novel 'Quo Vadis' and what country was the author from?" ) +print(result.response) +``` + +Example of a decomposed query: -search_result = await search_engine.a_search("Who wrote the novel 'Quo Vadis'?") -response = await search_engine.a_query("Who wrote the novel 'Quo Vadis'?") +```python +subqueries = await planned_search.process_query( + "Who wrote the novel 'Quo Vadis' and what country was the author from?" +) + +for subquery in subqueries: + print(subquery.id, subquery.query, subquery.depends_on) + +# Possible output: +# q1 Who wrote the novel 'Quo Vadis'? [] +# q2 What country was the author from? ['q1'] ``` --- @@ -536,7 +641,7 @@ response = await search_engine.a_query("Who wrote the novel 'Quo Vadis'?") ### Prompt Tuning Every LLM-powered component in RAGU allows you to change instructions. -Prompts are defined by `PromptTemplate` class. +Prompts are defined by the `RAGUInstruction` class. ```python @dataclass @@ -553,32 +658,52 @@ class PromptTemplate: supporting both single-instance and batched (list/tuple) generation. """ - template: str # Jinja2 template - schema: Type[BaseModel] = None # Pydantic schema - description: str = "" # Short instruction description + template: str # Jinja2 template + pydantic_model: Type[BaseModel] | Type[str] = str # Pydantic schema + description: str = "" # Short instruction description ``` Retrieve all available instructions: ```python +from ragu import LocalSearchEngine + search_engine = LocalSearchEngine( - client, - knowledge_graph, - embedder + llm=llm, + knowledge_graph=knowledge_graph, + embedder=embedder, ) -print(search_engine.get_prompts()) +all_prompts = search_engine.get_prompts() +print(all_prompts) + +local_search_prompt = search_engine.get_prompt("local_search") +print(local_search_prompt.messages.to_str()) ``` Update a specific instruction: ```python +from textwrap import dedent + +from ragu.common.prompts.messages import ChatMessages, SystemMessage, UserMessage +from ragu.common.prompts.prompt_storage import RAGUInstruction + search_engine.update_prompt( "local_search", - PromptTemplate( - template="New instruction as a Jinja2 template", - schema=SomeSchemaIfNeeded, - description="Description of your prompt (optional)" - ) + RAGUInstruction( + messages=ChatMessages.from_messages([ + SystemMessage(content="You answer using only the supplied graph context."), + UserMessage(content=dedent( + """ + Query: {{ query }} + Context: {{ context }} + Language: {{ language }} + """ + )), + ]), + pydantic_model=str, + description="Custom local-search instruction", + ), ) ``` diff --git a/docs/ru/ragu_components.md b/docs/ru/ragu_components.md index 47a8a87..99446c3 100644 --- a/docs/ru/ragu_components.md +++ b/docs/ru/ragu_components.md @@ -43,8 +43,8 @@ Для разбиения предусмотрены разные стратегии: * **`SimpleChunker`** - фиксированное разбиение по длине текста. -* **`SemanticChunker`** - разбиение с учётом семантической связи между предложениями. -* **`SmartChunker`** - ещё одно семантическое разбиение текста. +* **`SemanticTextChunker`** - разбиение с учётом семантической связи между предложениями. +* **`SmartSemanticChunker`** - семантическое разбиение текста на базе `smart_chunker`. --- @@ -56,6 +56,8 @@ * **Отношения**: текстовое описание отношения между двумя сущностями (или просто класс отношения), сила этого отношения. > **RAGU использует классы сущностей и отношений из [NEREL](https://github.com/nerel-ds/NEREL).** +> +> `Entity` и `Relation` являются базовыми классами модели графа. Их можно наследовать для более сложных доменных типов сущностей и отношений, если хранилища продолжают работать с базовыми контрактами `Node` / `Edge`. Представленные классы сущностей: @@ -95,10 +97,7 @@ |17. | HAS_CAUSE | 34. | PLACE_OF_DEATH | | ---- -> В планах — добавление извлечения сущностей и отношений через небольшую дообученную LLM / через набор узкоспециализированных моделей для NER и RE. -> Оба подхода используют набор классов для сущностей и отношений из NEREL. ---- +По умолчанию LLM-пайплайны используют типы сущностей и отношений из NEREL в инструкциях. Если нужна другая онтология, в экстракторы можно передать свои `entity_types` и `relation_types`. ### 3. Обработка триплетов @@ -129,7 +128,7 @@ ### 4. Построение графа и выделение комьюнити После выделения сущностей и отношений и их обработки происходит объединение всей информации в граф. -С логической точки зрения он является направленным (имеется субъект и объект), но из-за особенностей дальнейшей обработки хранится он как ненаправленный. +Граф хранится как направленный мультиграф: отношение сохраняет исходную сущность, целевую сущность и собственный идентификатор. Для выделения комьюнити внутри пайплайна строится ненаправленная проекция графа. Для возможности ответа на абстрактивные вопросы, следуя методологии GraphRAG, применяется выделение комьюнити в графе и получение саммари по этим комьюнити. Для выделения комьюнити используется [алгоритм Лейдена](https://en.wikipedia.org/wiki/Leiden_algorithm). @@ -228,7 +227,8 @@ RAGU реализует различные подходы к разбиению, За интерфейс чанкера отвечает класс BaseChunker (ragu/chunker/base_chunker) -Семантический чанкер представлен реализацией SmartChunker. +В RAGU доступны `SimpleChunker`, `SemanticTextChunker` и `SmartSemanticChunker`. +Семантический чанкер на базе `smart_chunker` представлен реализацией `SmartSemanticChunker`. Детали можете найти здесь: https://github.com/bond005/smart_chunker/tree/main @@ -236,25 +236,40 @@ https://github.com/bond005/smart_chunker/tree/main --- ### Пайплайн извлечения графа -В RAGU реализуется два варианта извлечения графа знаний из сырых текстов: классический, на базе LLM, и подход на основании небольших специализированных моделей на базе [RAGU-lm](https://huggingface.co/RaguTeam/RAGU-lm). -#### LLMArtifactExtractor +В RAGU реализуется три варианта извлечения графа знаний из сырых текстов: одношаговый LLM-пайплайн `ArtifactsExtractorLLM`, двухшаговый LLM-пайплайн `TwoStageArtifactsExtractorLLM` и подход на основании небольшой специализированной модели [RAGU-lm](https://huggingface.co/RaguTeam/RAGU-lm). +#### ArtifactsExtractorLLM -LLMArtifactExtractor - реализация классического варианта извлечения сущностей и отношений из GraphRAG, использующий LLM. +`ArtifactsExtractorLLM` - реализация классического варианта извлечения сущностей и отношений из GraphRAG, использующая LLM. Пример использования: ```python -from ragu.triplet import ArtifactsExtractorLLM +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.triplet import ArtifactsExtractorLLM, TwoStageArtifactsExtractorLLM -... +client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", +) +llm = LLMOpenAI( + client=client, + model_name="gpt-4o-mini", +) + +single_step_extractor = ArtifactsExtractorLLM( + llm=llm, + do_validation=True, +) -pipeline = ArtifactsExtractorLLM( - client=client, # LLM клиент из ragu.llm - do_validation=True # Можем поставить валидацию выделенных артефактов +two_stage_extractor = TwoStageArtifactsExtractorLLM( + llm=llm, + do_entity_validation=True, + do_relation_validation=True, ) ``` -За выделение/валидацию элементов графа отвечают инструкции `artifact_extraction` и `artifact_validation`. +За одношаговое выделение/валидацию элементов графа отвечают инструкции `artifact_extraction` и `artifact_validation`. Двухшаговый пайплайн использует `entity_extraction`, `entity_validation`, `relation_extraction` и `relation_validation`. #### RAGU-lm @@ -421,15 +436,26 @@ sudo vllm serve RaguTeam/ragu-lm --max_model_len 4096 Инициализируем класс RaguLmArtifactExtractor и дальше можем обращаться к модели в коде. ```python +from ragu.chunker.types import Chunk +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI from ragu.triplet.ragu_lm_artifact_extractor import RaguLmArtifactExtractor -... +client = CachedAsyncOpenAI( + base_url="http://0.0.0.0:8000/v1/", + api_key="dummy-api-token", +) +llm = LLMOpenAI( + client=client, + model_name="RaguTeam/ragu-lm", +) pipeline = RaguLmArtifactExtractor( - ragu_lm_vllm_url="http://0.0.0.0:8000/v1/", + llm=llm, ) -entities, relations = pipeline(["some_texts"]) +chunks = [Chunk(content="Текст источника.", chunk_order_idx=0, doc_id="doc-1")] +entities, relations = await pipeline.extract(chunks) ``` --- @@ -441,34 +467,45 @@ entities, relations = pipeline(["some_texts"]) За хранение и доступ к элементам графа знаний отвечает класс KnowledgeGraph. ```python -# Первое - настроенный пайплайн для построения графа -pipeline = InMemoryGraphBuilder( - client, - chunker, - artifact_extractor, - embedder=embedder, - use_clustering=True, - cluster_only_if_more_than=2 +from ragu import BuilderArguments, KnowledgeGraph, SimpleChunker +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.triplet import TwoStageArtifactsExtractorLLM + +client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", +) +llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") +embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-large", + dim=3072, ) -# Второе - настроенное хранилище графа знаний -index = Index( - embedder, - graph_storage_kwargs={"clustering_params": {"max_cluster_size": 6}} +chunker = SimpleChunker(max_chunk_size=1000) +artifact_extractor = TwoStageArtifactsExtractorLLM(llm=llm) +builder_settings = BuilderArguments( + use_llm_summarization=True, + use_clustering=True, + cluster_only_if_more_than=2, + make_community_summary=True, ) -``` -```python knowledge_graph = KnowledgeGraph( - extraction_pipeline=pipeline, - index=index, - make_community_summary=True, + llm=llm, + embedder=embedder, + chunker=chunker, + artifact_extractor=artifact_extractor, + builder_settings=builder_settings, language="russian", ) + +await knowledge_graph.build_from_docs(["Текстовый документ для индексации."]) ``` -Для каждой сущности создаётся эмбеддинг (векторное представление) её описания, который затем сохраняется в векторной базе данных. На данный момент используется `nano-db`. -Остальные данные хранятся в виде json. +Сущности, отношения, саммари комьюнити и исходные чанки сохраняются через настроенные адаптеры хранилищ. Плотные векторы строятся через `embedder`; при наличии `sparse_embedder` RAGU также сохраняет разреженные векторы для гибридного поиска. --- @@ -489,19 +526,18 @@ knowledge_graph = KnowledgeGraph( ```python from ragu.search_engine import LocalSearchEngine -... - -search_engine = LocalSearchEngine( - client, # LLM - knowledge_graph, # Граф знаний - embedder # Эмбеддер для векторизации запроса +local_search = LocalSearchEngine( + llm=llm, # LLM для финального ответа + knowledge_graph=knowledge_graph, # Граф знаний + embedder=embedder, # Эмбеддер для векторизации запроса ) # Поиск релевантного контекста в графе -search_result = await search_engine.a_search("Кто написал роман 'Камо Грядеши'?") +search_result = await local_search.a_search("Кто написал роман 'Камо Грядеши'?") # Получение ответа по графу знаний -response = await search_engine.a_query("Кто написал роман 'Камо Грядеши'?") +response = await local_search.a_query("Кто написал роман 'Камо Грядеши'?") +print(response.response) ``` @@ -517,64 +553,139 @@ response = await search_engine.a_query("Кто написал роман 'Кам ```python from ragu.search_engine import GlobalSearchEngine -... - -search_engine = GlobalSearchEngine( - client, - knowledge_graph +global_search = GlobalSearchEngine( + llm=llm, + knowledge_graph=knowledge_graph, ) # Поиск релевантного контекста в графе -search_result = await search_engine.a_search("Кто написал роман 'Камо Грядеши'?") +search_result = await global_search.a_search("Кто написал роман 'Камо Грядеши'?") # Получение ответа по графу знаний -response = await search_engine.a_query("Кто написал роман 'Камо Грядеши'?") +response = await global_search.a_query("Кто написал роман 'Камо Грядеши'?") +print(response.response) ``` ---- +#### Naive Search -### Prompt tuning +Naive search - это векторный RAG по исходным чанкам. Он не расширяет контекст через соседей в графе, а ищет релевантные чанки в векторном хранилище и передает их в LLM. -У каждого класса, который использует LLM под капотом, можно поменять используемую инструкцию. +```python +from ragu.search_engine import NaiveSearchEngine + +naive_search = NaiveSearchEngine( + llm=llm, + knowledge_graph=knowledge_graph, + embedder=embedder, +) + +# Поиск релевантных чанков +search_result = await naive_search.a_search("Кто написал роман 'Камо Грядеши'?") + +# Получение ответа по найденным чанкам +response = await naive_search.a_query("Кто написал роман 'Камо Грядеши'?") +print(response.response) +``` + +#### Mix Search + +Mix search запускает несколько поисковых движков и просит LLM собрать итоговый ответ из их результатов. Это полезно, когда нужен одновременно локальный графовый контекст, векторный контекст по чанкам и контекст по комьюнити. -Сейчас все инструкции представлены классом `PromptTemplate`. ```python -@dataclass -class PromptTemplate: - """ - Represents a Jinja2-based prompt template for instruction generation. +from ragu.search_engine import MixSearchEngine - Each template defines: - - a Jinja2 text pattern (`template`) - - an optional Pydantic schema for structured output validation (`schema`) - - a short description of its purpose (`description`) +mix_search = MixSearchEngine( + llm=llm, + engines=[local_search, naive_search, global_search], +) - The template can be rendered dynamically with keyword arguments, - supporting both single-instance and batched (list/tuple) generation. - """ +response = await mix_search.a_query("Кто написал роман 'Камо Грядеши'?") +print(response.response) +``` - template: str # jinja2 шаблон инструкции. - schema: Type[BaseModel] = None # pydantic модель для структурированного ответа. - description: str = "" # краткое описание инструкции. +#### Query Planning -... +`QueryPlanEngine` оборачивает любой поисковый движок и разбивает сложный вопрос на зависимые подзапросы перед формированием итогового ответа. + +```python +from ragu.search_engine import QueryPlanEngine + +planned_search = QueryPlanEngine(local_search) + +response = await planned_search.a_query( + "Кто написал роман 'Камо Грядеши' и из какой страны был автор?" +) +print(response.response) +``` + +Пример декомпозиции запроса: + +```python +subqueries = await planned_search.process_query( + "Кто написал роман 'Камо Грядеши' и из какой страны был автор?" +) + +for subquery in subqueries: + print(subquery.id, subquery.query, subquery.depends_on) + +# Возможный результат: +# q1 Кто написал роман 'Камо Грядеши'? [] +# q2 Из какой страны был автор? ['q1'] +``` + +--- + +### Prompt tuning + +У каждого класса, который использует LLM под капотом, можно поменять используемую инструкцию. + +Сейчас все инструкции представлены классом `RAGUInstruction`. +```python +@dataclass +class RAGUInstruction: + messages: ChatMessages + pydantic_model: Type[BaseModel] | Type[str] = str + description: str | None = None ``` Получение словаря вида `"название_инструкции" : "соответствующий_prompt_template"`: ```python +from ragu import LocalSearchEngine + search_engine = LocalSearchEngine( - client, - knowledge_graph, - embedder + llm=llm, + knowledge_graph=knowledge_graph, + embedder=embedder, ) print(search_engine.get_prompts()) -# {'local_search': PromptTemplate(template='\n**Goal**\nAnswer the query by summarizing relevant information from the context and, if necessary, well-known facts.\n\n**Instructions**\n1. If you do not know the correct answer, explicitly state that.\n2. Do not include unsupported information.\n\nQuery: {{ query }}\nContext: {{ context }}\n\nProvide the answer in the following language: {{ language }}\nReturn the result as valid JSON matching the provided schema.\n', schema=, description='Prompt for generating a local context-based search response.')} +local_search_prompt = search_engine.get_prompt("local_search") +print(local_search_prompt.messages.to_str()) ``` Обновление инструкции: ```python -search_engine.update_prompt("local_search", PromptTemplate(template="Новая инструкция в виде jinja2 шаблона", schema=SomeSchemaOfNeeded, description="Описание вашей инструкции (можно оставить пустой)")) +from textwrap import dedent + +from ragu.common.prompts.messages import ChatMessages, SystemMessage, UserMessage +from ragu.common.prompts.prompt_storage import RAGUInstruction + +search_engine.update_prompt( + "local_search", + RAGUInstruction( + messages=ChatMessages.from_messages([ + SystemMessage(content="Отвечай только на основе переданного контекста графа."), + UserMessage(content=dedent( + """ + Запрос: {{ query }} + Контекст: {{ context }} + Язык: {{ language }} + """ + )), + ]), + pydantic_model=str, + description="Пользовательская инструкция для local search", + ), +) ``` - From e839f7c3e7e9490da7ea432df54e3fa110884189 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Sun, 26 Apr 2026 03:32:07 +0700 Subject: [PATCH 36/42] Add per module readme --- ragu/chunker/README.md | 226 +++++++ ragu/common/README.md | 269 ++++++++ ragu/graph/README.md | 628 ++++++++++++++++++ ragu/search_engine/README.md | 399 +++++++++++ ragu/storage/README.md | 600 +++++++++++++++++ ragu/storage/graph_storage_adapters/README.md | 159 +++++ ragu/storage/kv_storage_adapters/README.md | 108 +++ ragu/storage/vdb_storage_adapters/README.md | 202 ++++++ ragu/triplet/README.md | 241 +++++++ ragu/utils/README.md | 213 ++++++ 10 files changed, 3045 insertions(+) create mode 100644 ragu/chunker/README.md create mode 100644 ragu/common/README.md create mode 100644 ragu/graph/README.md create mode 100644 ragu/search_engine/README.md create mode 100644 ragu/storage/README.md create mode 100644 ragu/storage/graph_storage_adapters/README.md create mode 100644 ragu/storage/kv_storage_adapters/README.md create mode 100644 ragu/storage/vdb_storage_adapters/README.md create mode 100644 ragu/triplet/README.md create mode 100644 ragu/utils/README.md diff --git a/ragu/chunker/README.md b/ragu/chunker/README.md new file mode 100644 index 0000000..b6d79c8 --- /dev/null +++ b/ragu/chunker/README.md @@ -0,0 +1,226 @@ +from smart_chunker import chunker + +# Module: ragu.chunker + +## Role in RAGU Pipeline + +`ragu.chunker` is the first stage of the RAGU indexing pipeline. It transforms raw documents into `Chunk` objects that can be sent to artifact extraction, vector storage, and naive retrieval. + +Pipeline position: + +```text +documents -> chunker -> List[Chunk] -> extraction / graph builder / chunk vector index +``` + +## Overview + +The module exists to give the rest of RAGU stable text units with deterministic IDs. Every chunk stores its text, order inside the source document, source document ID, and optional token count. `KnowledgeGraph.build_from_docs()` uses the configured chunker before running extraction. If no chunker is configured, each input document is treated as one chunk. + +## Key Components + +### Chunk + +Dataclass from `ragu.chunker.types`. + +- Purpose: immutable-ish text unit passed between indexing and retrieval stages. +- Important fields: `content`, `chunk_order_idx`, `doc_id`, `num_tokens`. +- ID behavior: `id` is generated in `__post_init__` from `content` with prefix `chunk-`. + +### BaseChunker + +Abstract interface for chunkers. + +- Purpose: standardizes `split(documents) -> list[Chunk]`. +- Used by: `KnowledgeGraph` and `InMemoryGraphBuilder`. + +### SimpleChunker + +Sentence-aware fixed-size chunker. + +- Purpose: split text by `razdel.sentenize`, then merge sentences until `max_chunk_size` characters. +- Important parameters: `max_chunk_size`, `overlap`. + +```python +import asyncio +from ragu.chunker.types import Chunk +from ragu.chunker import SimpleChunker + +documents = [ + "First document", + "Second document" +] + +async def main(): + chunker = SimpleChunker(max_chunk_size=512, overlap=0) + chunks: list[Chunk] = await chunker(documents) + + print(chunks) + +asyncio.run(main()) +``` + +### SemanticTextChunker + +Sentence-transformer based semantic splitter. + +- Purpose: split by sentence boundaries and recursively separate less similar sentence spans. +- Important parameters: `model_name`, `max_chunk_size`, `device`. +- External dependency: `sentence_transformers`. + +```python +import asyncio +from ragu.chunker.types import Chunk +from ragu.chunker import SemanticTextChunker + +documents = [ + "First document", + "Second document" +] + +async def main(): + chunker = SemanticTextChunker( + model_name="", + max_chunk_size=1024, + device="cuda:0" + ) + chunks: list[Chunk] = await chunker(documents) + + print(chunks) + +asyncio.run(main()) +``` + +### SmartSemanticChunker + +Wrapper around `smart_chunker.SmartChunker`. + +- Purpose: use a reranker-based algorithm for long-document chunking. +- Important parameters: `reranker_name`, `max_chunk_length`, `minibatch_size`, `device`. +- External dependency: `smart_chunker`. + +```python +import asyncio +from ragu.chunker.types import Chunk +from ragu.chunker import SmartSemanticChunker + +documents = [ + "First document", + "Second document" +] + +async def main(): + chunker = SmartSemanticChunker( + max_chunk_length=1024, + minibatch_size=16 + # And more parameter + ) + chunks: list[Chunk] = await chunker(documents) + + print(chunks) + +asyncio.run(main()) +``` + +## Data Flow + +Input: `str` or `list[str]` documents. + +Output: `list[Chunk]`. + +Used by: + +- `ragu.graph.KnowledgeGraph.build_from_docs` +- `ragu.graph.InMemoryGraphBuilder.extract_graph` +- `ragu.triplet` extractors +- `ragu.graph.Index.upsert_chunks` +- `ragu.search_engine.NaiveSearchEngine` + +## Usage Examples + +### Example 1 - Minimal usage + +```python +from ragu.chunker import SimpleChunker + +chunker = SimpleChunker(max_chunk_size=120, overlap=20) +chunks = chunker.split("Python was created by Guido van Rossum. It is widely used.") + +for chunk in chunks: + print(chunk.id, chunk.doc_id, chunk.chunk_order_idx, chunk.content) +``` + +### Example 2 - Pipeline usage + +```python +import asyncio + +from ragu import BuilderArguments, KnowledgeGraph, SimpleChunker +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.openai import CachedAsyncOpenAI + + +async def main(): + client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", + ) + embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, + ) + graph = KnowledgeGraph( + llm=None, + embedder=embedder, + chunker=SimpleChunker(max_chunk_size=80), + builder_settings=BuilderArguments(build_only_vector_context=True), + ) + + await graph.build_from_docs([ + "RAGU builds knowledge graphs from text.", + "Naive search uses chunk vectors.", + ]) + + chunk_ids = await graph.index.chunks_kv_storage.all_keys() + chunks = await graph.get_chunks(chunk_ids) + print([chunk.content for chunk in chunks if chunk]) + + +asyncio.run(main()) +``` + +## Integration Points + +- Extraction: `BaseArtifactExtractor.extract()` receives `Chunk` instances and writes `source_chunk_id` into entities and relations. +- Storage: `Index.upsert_chunks()` stores chunk metadata in KV storage and dense/sparse embeddings in vector storage. +- Retrieval: `GraphRetriever.query_chunks()` resolves vector hits back into `Chunk` objects for `NaiveSearchEngine`. +- Settings: chunkers do not use global storage settings directly; `KnowledgeGraph` controls where chunk outputs are persisted. + +## Configuration + +- `SimpleChunker(max_chunk_size, overlap=0)`: character budget and optional character overlap. +- `SemanticTextChunker(model_name, max_chunk_size, device="cuda:0")`: sentence-transformer model and token budget. +- `SmartSemanticChunker(...)`: reranker model, tokenizer callables, device, `max_chunk_length`, and batching. + +## Dependencies + +Internal: + +- `ragu.chunker.types.Chunk` +- `ragu.utils.ragu_utils.compute_mdhash_id` + +External: + +- `razdel` +- `tqdm` +- `nltk` +- `numpy` +- optional `sentence_transformers` +- optional `smart_chunker` + +## Notes / Pitfalls + +- `Chunk.id` is content-derived. Duplicate chunk text across documents produces the same ID and is deduplicated by `KnowledgeGraph.build_from_docs()`. +- `doc_id` is currently derived from the whole document hash inside chunkers. +- `SimpleChunker.max_chunk_size` is measured in characters, not tokens. +- Semantic chunkers load local ML models and may require GPU-compatible configuration. diff --git a/ragu/common/README.md b/ragu/common/README.md new file mode 100644 index 0000000..15bff53 --- /dev/null +++ b/ragu/common/README.md @@ -0,0 +1,269 @@ +# Module: ragu.common + +## Role in RAGU Pipeline + +`ragu.common` provides cross-cutting infrastructure for the whole GraphRAG pipeline: global settings, prompt rendering, logging, batching, and cache helpers. It is not a pipeline stage by itself, but every stage depends on it. + +Pipeline position: + +```text +settings + prompts + utilities + -> chunking / extraction / graph building / storage / retrieval / generation +``` + +## Overview + +The module keeps shared behavior out of the domain packages. It centralizes the default storage directory, default filenames, runtime environment loading, prompt templates, and the base class used by LLM-driven modules. + +## Key Components + +### Settings + +Singleton instance of `GlobalSettings`. + +- Purpose: stores process-wide defaults. +- Important fields: `language`, `storage_folder`. +- Used by: storage initialization, prompts, builders, search engines, sparse embedders. + +```python +from ragu.common.global_parameters import Settings + +Settings.language = "english" +Settings.storage_folder = "./ragu_working_dir/demo" +Settings.init_storage_folder() + +print(Settings.storage_folder) +``` + +### Env + +Pydantic settings model. + +- Purpose: load model API configuration from environment variables or `.env`. +- Important fields: `llm_model_name`, `llm_base_url`, `llm_api_key`, optional embedder and reranker fields. + +```python +from ragu.common.env import Env + +env = Env( + llm_model_name="gpt-4o-mini", + llm_base_url="https://api.openai.com/v1", + llm_api_key="dummy-api-token", +) + +print(env.llm_model_name) +``` + +### RaguGenerativeModule + +Base class for modules that own prompts. + +- Purpose: load default prompts by name or accept custom `RAGUInstruction` objects. +- Important methods: `get_prompt`, `get_prompts`, `update_prompt`. +- Used by: extractors, summarizers, and search engines. + +```python +from ragu.common.base import RaguGenerativeModule +from ragu.common.prompts.messages import ChatMessages, UserMessage +from ragu.common.prompts.prompt_storage import RAGUInstruction + +module = RaguGenerativeModule(prompts=["naive_search"]) +module.update_prompt( + "naive_search", + RAGUInstruction( + messages=ChatMessages.from_messages([ + UserMessage(content="Answer in {{ language }} using this context:\n{{ context }}\n\nQuery: {{ query }}") + ]), + pydantic_model=str, + description="Custom concise answer prompt.", + ), +) + +print(module.get_prompt("naive_search").description) +``` + +### ChatMessages and Message Types + +Prompt-message abstraction from `ragu.common.prompts.messages`. + +- Purpose: represent system/user/assistant messages and convert them to OpenAI chat payloads. +- Important classes: `SystemMessage`, `UserMessage`, `AIMessage`, `ChatMessages`. + +```python +from ragu.common.prompts.messages import ChatMessages, SystemMessage, UserMessage + +messages = ChatMessages.from_messages([ + SystemMessage(content="Answer briefly."), + UserMessage(content="What is RAGU?"), +]) + +print(messages.to_openai()) +``` + +### render + +Jinja2 renderer for prompt messages. + +- Purpose: render one or many conversations from scalar and batch parameters. +- Important behavior: list/tuple parameters define batch size; all batch parameters must have the same length. + +```python +from ragu.common.prompts.messages import ChatMessages, UserMessage, render + +template = ChatMessages.from_messages([ + UserMessage(content="Question: {{ query }}") +]) + +rendered = render(template, query=["What is RAGU?", "What is GraphRAG?"]) +print([conversation.to_str() for conversation in rendered]) +``` + +### BatchGenerator + +Small batching helper used by rerankers and utility code. + +```python +from ragu.common.batch_generator import BatchGenerator + +generator = BatchGenerator([1, 2, 3, 4, 5], batch_size=2) +print(list(generator.get_batches())) +``` + +### get_cache + +Disk cache helper that returns a mutable mapping backed by `diskcache`. + +```python +from ragu.common.cache import get_cache + +cache = get_cache("./ragu_working_dir/cache") +cache["key"] = {"value": 1} +print(cache["key"]) +``` + +## Data Flow + +Input: runtime configuration, prompt names, prompt parameters. + +Output: storage paths, rendered `ChatMessages`, OpenAI-compatible message lists, cache mappings. + +Used by: + +- `ragu.triplet` extraction prompts +- `ragu.graph` summarization prompts and storage defaults +- `ragu.search_engine` answer-generation prompts +- `ragu.models` caching and API wrappers + +## Usage Examples + +### Example 1 - Minimal usage + +```python +from ragu.common.global_parameters import Settings + +Settings.language = "english" +Settings.storage_folder = "./ragu_working_dir/example" +Settings.init_storage_folder() + +print(Settings.storage_folder) +``` + +### Example 2 - Pipeline usage + +```python +from ragu.common.prompts.messages import ChatMessages, SystemMessage, UserMessage, render + +template = ChatMessages.from_messages([ + SystemMessage(content="Answer in {{ language }}."), + UserMessage(content="Question: {{ query }}\nContext: {{ context }}"), +]) + +rendered = render( + template, + language="english", + query=["What is RAGU?", "What is local search?"], + context=["GraphRAG system", "Entity-neighborhood retrieval"], +) + +openai_messages = [conversation.to_openai() for conversation in rendered] +print(openai_messages[0]) +``` + +### Example 3 - Change an instruction in a generative module + +```python +from ragu.common.base import RaguGenerativeModule +from ragu.common.prompts.messages import ChatMessages, UserMessage, render +from ragu.common.prompts.prompt_storage import RAGUInstruction + +module = RaguGenerativeModule(prompts=["local_search"]) +module.update_prompt( + "local_search", + RAGUInstruction( + messages=ChatMessages.from_messages([ + UserMessage( + content=( + "Use only the context below. " + "Answer in {{ language }}.\n\n" + "Context:\n{{ context }}\n\n" + "Question: {{ query }}" + ) + ) + ]), + pydantic_model=str, + description="Strict context-only local search prompt.", + ), +) + +instruction = module.get_prompt("local_search") +rendered = render( + instruction.messages, + language="english", + context="Python is a programming language.", + query="What is Python?", +)[0] + +print(rendered.to_openai()) +``` + +## Integration Points + +- LLMs: `ChatMessages.to_openai()` produces payloads accepted by `LLM.chat_completion`. +- Extraction and retrieval: `RaguGenerativeModule` loads named prompts from `DEFAULT_PROMPT_TEMPLATES`. +- Storage: `Settings.storage_folder` and `DEFAULT_FILENAMES` define default locations for KV, vector, and graph files. +- Configuration: `Env.from_env()` loads OpenAI-compatible model settings for application entrypoints. + +## Configuration + +Environment variables consumed by `Env`: + +- `llm_model_name`, `llm_base_url`, `llm_api_key` +- `embedder_base_url`, `embedder_api_key`, `embedder_model_name` +- `reranker_base_url`, `reranker_api_key`, `reranker_model_name` + +Global defaults: + +- `Settings.language = "english"` +- `Settings.storage_folder` defaults to `./ragu_working_dir/`. + +## Dependencies + +Internal: + +- `ragu.common.prompts` +- `ragu.common.logger` + +External: + +- `pydantic-settings` +- `jinja2` +- `diskcache` +- `loguru` +- OpenAI SDK message types + +## Notes / Pitfalls + +- `Settings` is global process state. Set `Settings.storage_folder` before constructing `KnowledgeGraph` or `Index`. +- Jinja rendering uses `StrictUndefined`; missing template variables raise errors. +- `render()` treats any list or tuple parameter as batched input. +- Default prompt names must exist in `DEFAULT_PROMPT_TEMPLATES`, otherwise `get_prompt()` can return `None` at construction time and fail later. diff --git a/ragu/graph/README.md b/ragu/graph/README.md new file mode 100644 index 0000000..2f6f37e --- /dev/null +++ b/ragu/graph/README.md @@ -0,0 +1,628 @@ +# Module: ragu.graph + +## Role in RAGU Pipeline + +`ragu.graph` is the central indexing layer. It receives chunks and extracted artifacts, builds a knowledge graph, clusters it into communities, stores graph/vector/KV state, and exposes retrieval helpers. + +Pipeline position: + +```text +Chunk -> Entity/Relation extraction -> graph build/summarize/cluster -> Index +Index -> GraphRetriever -> search engines +``` + +## Overview + +The module exists to keep graph construction and graph persistence independent from the model and storage implementations. `KnowledgeGraph` is the high-level facade. `InMemoryGraphBuilder` runs extraction, summarization, optional post-processing modules, and Leiden community detection. `Index` coordinates graph storage, vector storage, and JSON-like KV storage. + +## Key Components + +### KnowledgeGraph + +High-level facade for build, CRUD, reindexing, and storage access. + +- Purpose: orchestrates chunking, extraction, graph construction, vectorization, and persistence. +- Important methods: `build_from_docs`, `upsert_entities`, `upsert_relations`, `get_entities`, `get_relations`, `get_chunks`, `reindex_community`, `reindex_descriptions`, `reindex_graph`. +- Important parameters: `llm`, `embedder`, optional `sparse_embedder`, `chunker`, `artifact_extractor`, `builder_settings`, `storage_settings`. + +```python +from ragu.graph.knowledge_graph import KnowledgeGraph +from ragu.graph.graph_builder_pipeline import BuilderArguments +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.openai import CachedAsyncOpenAI + +client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token" +) +embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536 +) + +graph = KnowledgeGraph( + llm=None, + embedder=embedder, + builder_settings=BuilderArguments(build_only_vector_context=True), +) +print(graph.language) +``` + +### Entity + +Graph node dataclass. + +- Purpose: semantic node extracted from source chunks. +- Important fields: `entity_name`, `entity_type`, `description`, `source_chunk_id`, `documents_id`, `clusters`, `id`. +- ID behavior: defaults to `ent-...` hash of name and type. +- Extension point: `Entity` is the base graph model for RAGU entities and can be inherited to add domain-specific fields or methods. Custom entity classes should preserve the `Node` contract used by storage adapters. + +> NOTE: custom entity extraction is not supported in RAGU versions 0.0.1 and 0.0.2. +> Full support expected in 0.0.3. + +```python +from ragu.graph.types import Entity + +entity = Entity( + entity_name="Python", + entity_type="Language", + description="A programming language.", + source_chunk_id=["chunk-1"], +) + +print(entity.id, entity.to_text()) +``` + +```python +from dataclasses import dataclass + +from ragu.graph.types import Entity + + +@dataclass(slots=True) +class ProductEntity(Entity): + sku: str = "" + confidence: float = 1.0 + + +entity = ProductEntity( + entity_name="RAGU Pro", + entity_type="Product", + description="A domain-specific product entity.", + source_chunk_id=["chunk-1"], + sku="RAGU-PRO", + confidence=0.97, +) + +print(entity.sku, entity.to_text()) +``` + +### Relation + +Directed graph edge dataclass. + +- Purpose: semantic edge between two entities. +- Important fields: `subject_id`, `object_id`, `subject_name`, `object_name`, `relation_type`, `description`, `relation_strength`, `source_chunk_id`. +- ID behavior: defaults to `rel-...` hash of subject, object, and relation type. +- Extension point: `Relation` is the base graph model for RAGU relations and can be inherited to add domain-specific edge metadata. Custom relation classes should preserve the `Edge` contract used by storage adapters. + +> NOTE: custom relation extraction is not supported in RAGU versions 0.0.1 and 0.0.2. +> Full support expected in 0.0.3. +> +```python +from ragu.graph.types import Entity, Relation + +python = Entity( + entity_name="Python", + entity_type="Language", + description="A programming language.", + source_chunk_id=["chunk-1"] +) +guido = Entity( + entity_name="Guido van Rossum", + entity_type="Person", + description="Creator of Python.", + source_chunk_id=["chunk-1"] +) +relation = Relation( + subject_id=guido.id, + object_id=python.id, + subject_name=guido.entity_name, + object_name=python.entity_name, + relation_type="CREATED", + description="Guido van Rossum created Python.", +) + +print(relation.id, relation.to_text()) +``` + +```python +from dataclasses import dataclass + +from ragu.graph.types import Entity, Relation + + +@dataclass(slots=True) +class EvidenceRelation(Relation): + evidence_quote: str = "" + extractor_name: str = "" + + +python = Entity( + entity_name="Python", + entity_type="Language", + description="A programming language.", + source_chunk_id=["chunk-1"] +) + +# "Short" creation +guido = Entity( + "Guido van Rossum", + "Person", + "Creator of Python.", + ["chunk-1"] +) +relation = EvidenceRelation( + subject_id=guido.id, + object_id=python.id, + subject_name=guido.entity_name, + object_name=python.entity_name, + relation_type="CREATED", + description="Guido van Rossum created Python.", + evidence_quote="Python was created by Guido van Rossum.", + extractor_name="two_stage_llm", +) + +print(relation.evidence_quote) +``` + +### BuilderArguments + +Configuration for build behavior. + +- `use_llm_summarization`: summarize duplicate descriptions with LLM. +- `use_clustering`: cluster similar entities before summarization. +- `build_only_vector_context`: skip graph artifact extraction and store chunks only. +- `make_community_summary`: run community detection and summarization. +- `remove_isolated_nodes`: add `RemoveIsolatedNodes` post-processor. +- `vectorize_chunks`: stored on `KnowledgeGraph`; chunk vectorization currently happens in `Index.upsert_chunks`. + +#### Pipeline preset: chunk-vector index only + +```python +import asyncio + +from ragu import KnowledgeGraph, SimpleChunker +from ragu.graph.graph_builder_pipeline import BuilderArguments +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.openai import CachedAsyncOpenAI + + +async def main(): + client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", + ) + embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, + ) + graph = KnowledgeGraph( + llm=None, + embedder=embedder, + chunker=SimpleChunker(max_chunk_size=800, overlap=80), + builder_settings=BuilderArguments( + build_only_vector_context=True, + make_community_summary=False, + ), + ) + + await graph.build_from_docs(["RAGU can index chunks without extracting a graph."]) + print(await graph.index.chunks_kv_storage.all_keys()) + + +asyncio.run(main()) +``` + +This preset is for naive vector RAG. It stores chunks and chunk vectors, but skips entity/relation extraction, graph edges, communities, and community summaries. + +#### Pipeline preset: fast graph extraction without LLM summarization + +```python +import asyncio + +from ragu import KnowledgeGraph, SimpleChunker +from ragu.graph.graph_builder_pipeline import BuilderArguments +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.triplet import TwoStageArtifactsExtractorLLM + + +async def main(): + client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", + ) + llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") + embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, + ) + graph = KnowledgeGraph( + llm=llm, + embedder=embedder, + chunker=SimpleChunker(max_chunk_size=1200, overlap=100), + artifact_extractor=TwoStageArtifactsExtractorLLM(llm), + builder_settings=BuilderArguments( + use_llm_summarization=False, + use_clustering=False, + make_community_summary=False, + remove_isolated_nodes=True, + ), + ) + + await graph.build_from_docs(["Guido van Rossum created Python."]) + print(await graph.index.graph_backend.get_all_nodes()) + + +asyncio.run(main()) +``` + +This preset still extracts entities and relations with an LLM, but duplicate descriptions are merged without additional LLM summarization and no community summaries are generated. + +#### Pipeline preset: full GraphRAG with community summaries + +```python +import asyncio + +from ragu import KnowledgeGraph, SimpleChunker +from ragu.graph.graph_builder_pipeline import BuilderArguments +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.triplet import TwoStageArtifactsExtractorLLM + + +async def main(): + client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", + ) + llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") + embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, + ) + graph = KnowledgeGraph( + llm=llm, + embedder=embedder, + chunker=SimpleChunker(max_chunk_size=1200, overlap=100), + artifact_extractor=TwoStageArtifactsExtractorLLM( + llm, + do_entity_validation=True, + do_relation_validation=True, + ), + builder_settings=BuilderArguments( + use_llm_summarization=True, + use_clustering=False, + make_community_summary=True, + remove_isolated_nodes=True, + summarize_only_if_more_than=7, + max_cluster_size=128, + random_seed=42, + ), + ) + + await graph.build_from_docs(["Python was created by Guido van Rossum."]) + print(await graph.index.community_summary_kv_storage.all_keys()) + + +asyncio.run(main()) +``` + +This preset is the default GraphRAG path: extract artifacts, merge/summarize duplicate descriptions, remove isolated entities, detect Leiden communities, and write community summaries for global search. + +#### Pipeline preset: large-corpus entity clustering before summarization + +```python +import asyncio + +from ragu import KnowledgeGraph, SimpleChunker +from ragu.graph.graph_builder_pipeline import BuilderArguments +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.triplet import ArtifactsExtractorLLM + + +async def main(): + client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", + ) + llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") + embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, + ) + graph = KnowledgeGraph( + llm=llm, + embedder=embedder, + chunker=SimpleChunker(max_chunk_size=1500, overlap=150), + artifact_extractor=ArtifactsExtractorLLM(llm, do_validation=True), + builder_settings=BuilderArguments( + use_llm_summarization=True, + use_clustering=True, + cluster_only_if_more_than=500, + summarize_only_if_more_than=5, + make_community_summary=True, + max_cluster_size=256, + ), + ) + + await graph.build_from_docs(["A long corpus split into many chunks."]) + print(await graph.index.graph_backend.get_all_nodes()) + + +asyncio.run(main()) +``` + +This preset is intended for many repeated entity mentions. Entity descriptions are embedded and clustered before LLM summarization when the duplicate-description count crosses `cluster_only_if_more_than`. + +### InMemoryGraphBuilder + +In-memory graph build pipeline. + +- Purpose: calls artifact extractor, entity/relation summarizers, extra modules, community clustering, and community summarizer. +- Important methods: `extract_graph`, `cluster_graph`. + +```python +import asyncio + +from ragu.graph.graph_builder_pipeline import BuilderArguments, InMemoryGraphBuilder +from ragu.graph.types import Entity, Relation +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.openai import CachedAsyncOpenAI + + +async def main(): + client = CachedAsyncOpenAI(base_url="https://api.openai.com/v1", api_key="dummy-api-token") + embedder = EmbedderOpenAI(client=client, model_name="text-embedding-3-small", dim=1536) + builder = InMemoryGraphBuilder( + embedder=embedder, + build_parameters=BuilderArguments(build_only_vector_context=True), + ) + python = Entity("Python", "Language", "A programming language.", ["chunk-1"]) + guido = Entity("Guido van Rossum", "Person", "Creator of Python.", ["chunk-1"]) + relation = Relation(guido.id, python.id, guido.entity_name, python.entity_name, "CREATED", "Created Python.") + communities = await builder.cluster_graph([python, guido], [relation]) + print(communities) + + +asyncio.run(main()) +``` + +### Index + +Storage coordinator. + +- Purpose: keeps graph backend, vector DBs, chunk KV, community KV, and summary KV in sync. +- Important methods: `upsert_nodes`, `upsert_edges`, `upsert_chunks`, `delete_chunks`, `check_consistency`. +- Storage defaults: `NetworkXStorage`, `JsonKVStorage`, `NanoVectorDBStorage`. + +```python +import asyncio + +from ragu.graph.index import Index, StorageArguments +from ragu.graph.types import Entity, Relation +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.openai import CachedAsyncOpenAI + + +async def main(): + client = CachedAsyncOpenAI(base_url="https://api.openai.com/v1", api_key="dummy-api-token") + embedder = EmbedderOpenAI(client=client, model_name="text-embedding-3-small", dim=1536) + index = Index(arguments=StorageArguments(), embedder=embedder) + entity = Entity("Python", "Language", "A programming language.", ["chunk-1"]) + await index.upsert_nodes([entity]) + print(await index.get_nodes([entity.id])) + + +asyncio.run(main()) +``` + +### GraphRetriever + +Query-time vector helper. + +- Purpose: build dense/sparse query vectors and resolve vector hits to entities, relations, or chunks. +- Important methods: `query_entities`, `query_relations`, `query_chunks`. + +```python +import asyncio + +from ragu.graph.graph_retrieve_backend import GraphRetriever +from ragu.graph.knowledge_graph import KnowledgeGraph +from ragu.graph.graph_builder_pipeline import BuilderArguments +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.openai import CachedAsyncOpenAI + + +async def main(): + client = CachedAsyncOpenAI(base_url="https://api.openai.com/v1", api_key="dummy-api-token") + embedder = EmbedderOpenAI(client=client, model_name="text-embedding-3-small", dim=1536) + graph = KnowledgeGraph( + llm=None, + embedder=embedder, + builder_settings=BuilderArguments(build_only_vector_context=True), + ) + retriever = GraphRetriever(knowledge_graph=graph, embedder=embedder) + point = await retriever.build_query_vectors("Python") + print(point.dense_embedding is not None) + + +asyncio.run(main()) +``` + +## Data Flow + +Input: `list[str]` documents or explicit `Entity` and `Relation` objects. + +Output: + +- graph backend nodes and directed multigraph edges +- vector records for entities, relations, and chunks +- KV records for chunks, communities, and community summaries + +Used by: + +- `ragu.search_engine.LocalSearchEngine` +- `ragu.search_engine.GlobalSearchEngine` +- `ragu.search_engine.NaiveSearchEngine` +- `ragu.storage` adapters + +## Usage Examples + +### Example 1 - Minimal usage + +```python +import asyncio + +from ragu.graph.knowledge_graph import KnowledgeGraph +from ragu.graph.graph_builder_pipeline import BuilderArguments +from ragu.graph.types import Entity, Relation +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.openai import CachedAsyncOpenAI + + +async def main(): + client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", + ) + embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, + ) + graph = KnowledgeGraph( + llm=None, + embedder=embedder, + builder_settings=BuilderArguments(build_only_vector_context=True), + ) + + python = Entity("Python", "Language", "A programming language.", ["chunk-1"]) + guido = Entity("Guido van Rossum", "Person", "Creator of Python.", ["chunk-1"]) + relation = Relation( + subject_id=guido.id, + object_id=python.id, + subject_name=guido.entity_name, + object_name=python.entity_name, + relation_type="CREATED", + description="Guido van Rossum created Python.", + source_chunk_id=["chunk-1"], + ) + + await graph.upsert_entities([python, guido]) + await graph.upsert_relations([relation]) + + stored = await graph.get_entities([python.id, guido.id]) + print([entity.entity_name for entity in stored if entity]) + + +asyncio.run(main()) +``` + +### Example 2 - Pipeline usage + +```python +import asyncio + +from ragu import BuilderArguments, KnowledgeGraph, SimpleChunker +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.openai import CachedAsyncOpenAI + + +async def main(): + client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", + ) + embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, + ) + graph = KnowledgeGraph( + llm=None, + embedder=embedder, + chunker=SimpleChunker(max_chunk_size=100), + builder_settings=BuilderArguments(build_only_vector_context=True), + ) + + await graph.build_from_docs(["RAGU supports naive vector search over chunks."]) + chunk_ids = await graph.index.chunks_kv_storage.all_keys() + print(chunk_ids) + + +asyncio.run(main()) +``` + +## Integration Points + +- LLMs: used by artifact extraction, entity/relation summarization, and community summarization. +- Embedders: `Index` embeds entity text, relation text, and chunk content before vector upsert. +- Sparse embedders: when provided, `Index` stores sparse vectors and `GraphRetriever` builds sparse query vectors. +- Qdrant: configure `StorageArguments(vdb_storage_type=QdrantVectorDBStorage, vdb_storage_kwargs={...})`. +- Search engines: consume `KnowledgeGraph` plus embedders through `GraphRetriever`. + +## Configuration + +Key build settings: + +- `BuilderArguments(build_only_vector_context=True)`: chunk-only vector index, useful for `NaiveSearchEngine`. +- `BuilderArguments(make_community_summary=True)`: enables Leiden clustering plus LLM summaries. +- `BuilderArguments(remove_isolated_nodes=True)`: filters nodes without relations through `RemoveIsolatedNodes`. + +Key storage settings: + +```python +from ragu.graph.index import StorageArguments +from ragu.storage.vdb_storage_adapters.qdrant_vdb import QdrantVectorDBStorage + +storage = StorageArguments( + vdb_storage_type=QdrantVectorDBStorage, + vdb_storage_kwargs={"location": ":memory:", "sparse_type": "bm25"}, +) +``` + +## Dependencies + +Internal: + +- `ragu.chunker` +- `ragu.triplet` +- `ragu.models` +- `ragu.storage` +- `ragu.utils.token_truncation` + +External: + +- `networkx` +- `graspologic_native` +- `pandas` +- `numpy` + +## Notes / Pitfalls + +- Graph storage assumes directed multigraph semantics; edge identity is `(subject_id, object_id, relation_id)`. +- Edges cannot be inserted before their endpoint entities exist. +- `upsert_entities` and `upsert_relations` reject duplicate IDs in the same request, then merge with existing stored items. +- Community detection builds an undirected temporary graph for Leiden clustering, then stores communities separately in KV storage. +- `build_only_vector_context=True` requires an embedder but does not require an LLM or artifact extractor. diff --git a/ragu/search_engine/README.md b/ragu/search_engine/README.md new file mode 100644 index 0000000..d2302ab --- /dev/null +++ b/ragu/search_engine/README.md @@ -0,0 +1,399 @@ +# Module: ragu.search_engine + +## Role in RAGU Pipeline + +`ragu.search_engine` is the query layer. It consumes an already built `KnowledgeGraph`, retrieves context with one or more strategies, and optionally asks an LLM to synthesize an answer. + +Pipeline position: + +```text +KnowledgeGraph + query -> retrieval context -> LLM answer +``` + +## Overview + +The module separates retrieval strategy from indexing. All engines share `BaseEngine`, `a_search()` for retrieval-only calls, and `a_query()` for retrieval plus answer generation. + +## Key Components + +### BaseEngine + +Abstract base for query engines. + +- Purpose: stores LLM and context truncation settings. +- Important methods: `a_search`, `a_query`, sync wrappers `search`, `query`. + +```python +from ragu.search_engine.naive_search import NaiveSearchEngine + +print(issubclass(NaiveSearchEngine, object)) +``` + +### SearchEngineRetrieve + +Base dataclass for retrieval-only results. + +- Purpose: carries `query`, engine-specific `result`, and `metrics`. +- Important method: `to_text()`. + +```python +from ragu.search_engine.naive_search import NaiveSearchResult, NaiveSearchRetrieve + +retrieval = NaiveSearchRetrieve( + query="What is RAGU?", + result=NaiveSearchResult(), + metrics={"chunks": []}, +) + +print(retrieval.to_text()) +``` + +### SearchEngineResponse + +Generated answer container. + +- Purpose: carries `query`, `response`, `retrieval`, and optional `payload`. + +```python +from ragu.search_engine.base_engine import SearchEngineResponse +from ragu.search_engine.naive_search import NaiveSearchResult, NaiveSearchRetrieve + +retrieval = NaiveSearchRetrieve(query="What is RAGU?", result=NaiveSearchResult()) +response = SearchEngineResponse( + query="What is RAGU?", + response="RAGU is a GraphRAG engine.", + retrieval=retrieval, +) + +print(str(response)) +``` + +### NaiveSearchEngine + +Chunk-vector RAG. + +- Purpose: retrieve chunk vectors directly, optionally rerank, then answer. +- Best for: document QA when graph extraction is not needed. +- Uses: `GraphRetriever.query_chunks`. + +```python +from ragu import BuilderArguments, KnowledgeGraph, NaiveSearchEngine +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI + +client = CachedAsyncOpenAI(base_url="https://api.openai.com/v1", api_key="dummy-api-token") +llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") +embedder = EmbedderOpenAI(client=client, model_name="text-embedding-3-small", dim=1536) +graph = KnowledgeGraph( + llm=None, + embedder=embedder, + builder_settings=BuilderArguments(build_only_vector_context=True), +) +engine = NaiveSearchEngine(llm, graph, embedder) + +print(engine.language) +``` + +### LocalSearchEngine + +Graph-neighborhood RAG. + +- Purpose: retrieve relevant entities, then collect related relations, chunks, and community summaries. +- Best for: entity-centric questions and local factual neighborhoods. +- Uses: entity vector DB, graph edges, chunk KV, community summary KV. + +```python +from ragu import KnowledgeGraph, LocalSearchEngine +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI + +client = CachedAsyncOpenAI(base_url="https://api.openai.com/v1", api_key="dummy-api-token") +llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") +embedder = EmbedderOpenAI(client=client, model_name="text-embedding-3-small", dim=1536) +graph = KnowledgeGraph(llm=llm, embedder=embedder) +engine = LocalSearchEngine(llm, graph, embedder) + +print(engine.language) +``` + +### GlobalSearchEngine + +Community-summary RAG. + +- Purpose: evaluate all community summaries for query relevance and synthesize a global answer. +- Best for: broad questions that require corpus-level themes. +- Requires: community summaries built by `KnowledgeGraph`. + +```python +from ragu import GlobalSearchEngine, KnowledgeGraph +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI + +client = CachedAsyncOpenAI(base_url="https://api.openai.com/v1", api_key="dummy-api-token") +llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") +embedder = EmbedderOpenAI(client=client, model_name="text-embedding-3-small", dim=1536) +graph = KnowledgeGraph(llm=llm, embedder=embedder) +engine = GlobalSearchEngine(llm, graph) + +print(engine.language) +``` + +### MixSearchEngine + +Ensemble engine. + +- Purpose: run child engines and synthesize combined context or combined answers. +- Important parameter: `ensemble_responses`. + +```python +from ragu import BuilderArguments, KnowledgeGraph, MixSearchEngine, NaiveSearchEngine +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI + +client = CachedAsyncOpenAI(base_url="https://api.openai.com/v1", api_key="dummy-api-token") +llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") +embedder = EmbedderOpenAI(client=client, model_name="text-embedding-3-small", dim=1536) +graph = KnowledgeGraph( + llm=None, + embedder=embedder, + builder_settings=BuilderArguments(build_only_vector_context=True), +) +naive = NaiveSearchEngine(llm, graph, embedder) +mix = MixSearchEngine(llm, engines=[naive]) + +print(len(mix.engines)) +``` + +### QueryPlanEngine + +Prompt-based query planning engine exported by the package. It extends the same generation infrastructure and is intended for decomposed query workflows. + +```python +from ragu import BuilderArguments, KnowledgeGraph, NaiveSearchEngine, QueryPlanEngine +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI + +client = CachedAsyncOpenAI(base_url="https://api.openai.com/v1", api_key="dummy-api-token") +llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") +embedder = EmbedderOpenAI(client=client, model_name="text-embedding-3-small", dim=1536) +graph = KnowledgeGraph( + llm=None, + embedder=embedder, + builder_settings=BuilderArguments(build_only_vector_context=True), +) +engine = NaiveSearchEngine(llm, graph, embedder) +planner = QueryPlanEngine(engine) + +print(planner.get_prompt("query_decomposition").description) +``` + +## Data Flow + +Input: query string, `KnowledgeGraph`, dense embedder, optional sparse embedder and reranker. + +Output: + +- `SearchEngineRetrieve` from `a_search` +- `SearchEngineResponse` from `a_query` + +Used by: applications that need GraphRAG answers, retrieval diagnostics, or mixed retrieval strategies. + +## Usage Examples + +### Example 1 - Minimal usage + +```python +import asyncio + +from ragu import BuilderArguments, KnowledgeGraph, NaiveSearchEngine +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI + + +async def main(): + client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", + ) + llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") + embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, + ) + graph = KnowledgeGraph( + llm=None, + embedder=embedder, + builder_settings=BuilderArguments(build_only_vector_context=True), + ) + await graph.build_from_docs(["Python is a programming language."]) + + engine = NaiveSearchEngine(llm, graph, embedder) + retrieval = await engine.a_search("What is Python?", top_k=1) + print(retrieval.result.chunks[0].content) + + +asyncio.run(main()) +``` + +### Example 2 - Pipeline usage + +```python +import asyncio + +from ragu import BuilderArguments, GlobalSearchEngine, KnowledgeGraph, LocalSearchEngine, MixSearchEngine +from ragu.graph.types import CommunitySummary, Entity, Relation +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI + + +async def main(): + client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", + ) + llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") + embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, + ) + graph = KnowledgeGraph( + llm=llm, + embedder=embedder, + builder_settings=BuilderArguments(make_community_summary=False), + ) + python = Entity("Python", "Language", "A programming language.", ["chunk-1"]) + guido = Entity("Guido van Rossum", "Person", "Creator of Python.", ["chunk-1"]) + relation = Relation( + subject_id=guido.id, + object_id=python.id, + subject_name=guido.entity_name, + object_name=python.entity_name, + relation_type="CREATED", + description="Guido van Rossum created Python.", + source_chunk_id=["chunk-1"], + ) + await graph.upsert_entities([python, guido]) + await graph.upsert_relations([relation]) + await graph.upsert_summaries([CommunitySummary(id="com-1", summary="Python has a creator.")]) + + local = LocalSearchEngine(llm, graph, embedder) + global_ = GlobalSearchEngine(llm, graph) + mix = MixSearchEngine(llm, engines=[local, global_]) + + response = await mix.a_query("Summarize the main people and organizations.") + print(response.response) + + +asyncio.run(main()) +``` + +### Example 3 - Override a search engine instruction + +```python +import asyncio + +from ragu import BuilderArguments, KnowledgeGraph, NaiveSearchEngine +from ragu.common.prompts.messages import ChatMessages, UserMessage +from ragu.common.prompts.prompt_storage import RAGUInstruction +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI + + +async def main(): + client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", + ) + llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") + embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, + ) + graph = KnowledgeGraph( + llm=None, + embedder=embedder, + builder_settings=BuilderArguments(build_only_vector_context=True), + ) + engine = NaiveSearchEngine(llm, graph, embedder) + engine.update_prompt( + "naive_search", + RAGUInstruction( + messages=ChatMessages.from_messages([ + UserMessage( + content=( + "You are answering from retrieved chunks only.\n" + "Language: {{ language }}\n" + "Question: {{ query }}\n" + "Chunks:\n{{ context }}" + ) + ) + ]), + pydantic_model=str, + description="Chunk-grounded answer prompt.", + ), + ) + + print(engine.get_prompt("naive_search").description) + + +asyncio.run(main()) +``` + +## Integration Points + +- LLMs: every `a_query()` renders a prompt and calls `llm.chat_completion`. +- Embedders: local and naive search encode query text through `GraphRetriever`. +- Sparse embedders: local and naive search can issue hybrid dense+sparse vector queries when storage supports sparse vectors. +- Qdrant: hybrid search is handled by vector storage, including Qdrant prefetch/fusion in `QdrantVectorDBStorage`. +- Other modules: search engines read graph artifacts through `KnowledgeGraph.index`. + +## Configuration + +Shared engine parameters: + +- `max_context_length`: token budget for prompt context. +- `tokenizer_backend`: `"tiktoken"` or `"local"`. +- `tokenizer_model`: tokenizer model name. +- `language`: prompt language, defaulting to `Settings.language`. + +Retrieval parameters: + +- `top_k`: initial result count for local and naive search. +- `rerank_top_k`: final chunk count for `NaiveSearchEngine` when a reranker is configured. +- `use_summary` and `use_chunks`: toggles for `LocalSearchEngine.a_query`. +- `allow_partial_failures`: controls `MixSearchEngine` child-engine failures. + +## Dependencies + +Internal: + +- `ragu.graph.KnowledgeGraph` +- `ragu.graph.GraphRetriever` +- `ragu.models` +- `ragu.common.prompts` +- `ragu.utils.token_truncation` + +External: + +- `jinja2` +- `pydantic` +- `typing_extensions` + +## Notes / Pitfalls + +- `GlobalSearchEngine` needs community summaries; build with `make_community_summary=True` or call `reindex_community()`. +- `LocalSearchEngine` starts from entity vector search, so it needs entity vectors, not just chunk vectors. +- `NaiveSearchEngine` works with `build_only_vector_context=True`. +- `MixSearchEngine` requires at least one child engine. +- `a_search()` is useful for debugging retrieval before involving the LLM. diff --git a/ragu/storage/README.md b/ragu/storage/README.md new file mode 100644 index 0000000..160faf2 --- /dev/null +++ b/ragu/storage/README.md @@ -0,0 +1,600 @@ +# Module: ragu.storage + +## Role in RAGU Pipeline + +`ragu.storage` persists the outputs of graph building and supplies vector search for retrieval. It contains abstract contracts plus concrete graph, KV, and vector storage adapters. + +Pipeline position: + +```text +Entity/Relation/Chunk/Community -> Index -> graph storage + KV storage + vector storage +query vector -> vector storage -> EmbeddingHit -> graph/KV resolution +``` + +## Overview + +The module exists to keep `KnowledgeGraph` independent from a specific backend. RAGU can use local NetworkX/GML and JSON/Nano vector files for development, or Qdrant for dense and hybrid retrieval. + +## Key Components + +### Node + +Base graph node protocol from `ragu.storage.types`. + +- Purpose: defines the minimum shape graph storage adapters must support for node-like objects. +- Required fields by convention: + - `id: str` + - `source_chunk_id: list[str]` + - `clusters: list[ClusterInfo]` +- Important methods: + - `to_dict()`: serialize the node payload. + - `to_text()`: produce text used by vectorization or fallback display. +- Common child class: `ragu.graph.types.Entity`. +- Model extension reminder: `Entity` itself is designed to be inherited for richer domain entities. Storage adapters should operate on the configured child class, not on `Entity` specifically. +- Adapter requirement: graph storages must not hard-code `Entity`. They receive `node_cls` from `Index` and must be able to store and reconstruct any dataclass-like child class of `Node` that follows the required fields. + +```python +from ragu.graph.types import Entity +from ragu.storage.types import Node + +entity = Entity("Python", "Language", "A programming language.", ["chunk-1"]) + +print(isinstance(entity, Node)) +print(entity.to_dict()) +print(entity.to_text()) +``` + +### Edge + +Base graph edge protocol from `ragu.storage.types`. + +- Purpose: defines the minimum shape graph storage adapters must support for edge-like objects. +- Required fields by convention: + - `id: str` + - `subject_id: str` + - `object_id: str` + - `source_chunk_id: list[str]` +- Important methods: + - `to_dict()`: serialize the edge payload. + - `to_text()`: produce text used by vectorization or fallback display. +- Common child class: `ragu.graph.types.Relation`. +- Model extension reminder: `Relation` itself is designed to be inherited for richer domain relations. Storage adapters should operate on the configured child class, not on `Relation` specifically. +- Adapter requirement: graph storages must not hard-code `Relation`. They receive `edge_cls` from `Index` and must be able to store and reconstruct any dataclass-like child class of `Edge` that follows the required fields. + +```python +from ragu.graph.types import Entity, Relation +from ragu.storage.types import Edge + +python = Entity("Python", "Language", "A programming language.", ["chunk-1"]) +guido = Entity("Guido van Rossum", "Person", "Creator of Python.", ["chunk-1"]) +relation = Relation( + subject_id=guido.id, + object_id=python.id, + subject_name=guido.entity_name, + object_name=python.entity_name, + relation_type="CREATED", + description="Guido van Rossum created Python.", +) + +print(isinstance(relation, Edge)) +print(relation.to_dict()) +print(relation.to_text()) +``` + +### BaseStorage + +Common lifecycle contract for every storage backend. + +- Purpose: gives `Index` a uniform way to notify storage backends before indexing, after indexing, and after query work. +- Used by: graph, KV, and vector storage base classes. +- Important hooks: + - `index_start_callback()`: optional setup before an indexing batch. + - `index_done_callback()`: flush, persist, or finalize newly indexed data. + - `query_done_callback()`: optional cleanup after query-time reads. +- Implementation expectation: adapters may no-op these hooks, but they must expose them so orchestration code can treat all storage backends consistently. + +```python +from ragu.storage.base_storage import BaseStorage + +print(BaseStorage.__abstractmethods__) +``` + +### BaseGraphStorage + +Directed multigraph storage contract. + +- Purpose: store nodes and edges with edge specs `(subject_id, object_id, relation_id)`. +- Generic parameters: + - `NodeT`: a subclass of `ragu.storage.types.Node`. + - `EdgeT`: a subclass of `ragu.storage.types.Edge`. +- Important read methods: + - `get_nodes(node_ids)`: ordered node lookup with `None` for misses. + - `get_edges(edge_specs)`: ordered edge lookup by `(subject_id, object_id, relation_id)`. + - `get_all_nodes()`, `get_all_edges()`: full graph scans. + - `get_all_edges_for_nodes(node_ids)`: incident-edge lookup for local search and cascade deletion. + - `edges_degrees(edge_specs)`: degree signal for relation ranking. +- Important write methods: + - `upsert_nodes(nodes)`, `delete_nodes(node_ids)`. + - `upsert_edges(edges)`, `delete_edges(edge_specs)`. +- Invariant: RAGU treats graph storage as a directed multigraph; the relation ID is part of edge identity. +- Implementation expectation: an adapter must preserve dataclass payloads well enough to reconstruct the configured `node_cls` and `edge_cls`. +- Subclass requirement: storage adapters must operate with any child class of `Node` and `Edge`, not only the built-in `Entity` and `Relation` classes. + +```python +from ragu.storage.base_storage import BaseGraphStorage, EdgeSpec +from ragu.storage.types import Edge, Node + +edge_spec: EdgeSpec = ("subject-id", "object-id", "relation-id") +print(BaseGraphStorage[Node, Edge].__abstractmethods__) +print(edge_spec) +``` + +### BaseKVStorage + +Key-value storage contract. + +- Purpose: store chunks, communities, and summaries. +- Generic parameter: `T`, the value type stored under string IDs. +- Important read methods: + - `all_keys()`: list all stored keys. + - `get_by_id(id)`: fetch one value. + - `get_by_ids(ids, fields=None)`: ordered batch lookup with optional field projection. + - `filter_keys(data)`: return keys from the input list that are missing in storage. +- Important write methods: + - `upsert(data)`: insert or replace values by key. + - `delete(ids)`: delete keys. + - `drop()`: clear backend contents. +- Implementation expectation: missing keys should resolve to `None`, and batch lookups should preserve input order. + +```python +from typing import Any + +from ragu.storage.base_storage import BaseKVStorage + +print(BaseKVStorage[dict[str, Any]].__abstractmethods__) +``` + +### BaseVectorStorage + +Vector storage contract. + +- Purpose: store dense and optional sparse vectors. +- Input value type: `ragu.storage.types.Point`. +- Sparse vector type: `ragu.storage.types.SparseEmbedding`. +- Query output type: `list[ragu.storage.types.EmbeddingHit]`. +- Important read methods: + - `query(point, **kwargs)`: nearest-neighbor or hybrid lookup for one query point. + - `get_all_ids()`: list vector record IDs. + - `get_points_by_ids(ids)`: ordered point lookup with `None` for misses. + - `get_payloads_by_ids(ids)`: ordered metadata lookup with `None` for misses. +- Important write methods: + - `upsert(data)`: insert or replace vector records. + - `delete(ids)`: remove vector records. +- Implementation expectation: adapters should accept dense-only, sparse-only, or hybrid `Point` objects if the backend mode supports them; unsupported modes should fail clearly or document degraded behavior. + +```python +from ragu.storage.base_storage import BaseVectorStorage +from ragu.storage.types import EmbeddingHit, Point + +print(BaseVectorStorage.__abstractmethods__) +print(Point) +print(EmbeddingHit) +``` + +### SparseEmbedding + +Sparse vector payload from `ragu.storage.types`. + +- Purpose: represent lexical/sparse embeddings from BM25, BM42, SPLADE, or custom sparse encoders. +- Fields: + - `indices: list[int]` + - `values: list[float]` +- Invariant: `indices` and `values` must have the same length. +- Used by: `Point.sparse_embedding`, Qdrant sparse vectors, hybrid retrieval. + +```python +from ragu.storage.types import SparseEmbedding + +sparse = SparseEmbedding(indices=[10, 42], values=[0.7, 1.2]) +print(sparse.indices) +print(sparse.values) +``` + +### Point + +Vector database record from `ragu.storage.types`. + +- Purpose: carry a vector record into or out of vector storage. +- Fields: + - `id: str` + - `dense_embedding: DenseEmbedding | None` + - `sparse_embedding: SparseEmbedding | None` + - `metadata: dict[str, Any]` +- Invariant: at least one of `dense_embedding` or `sparse_embedding` must be present. +- Used by: `BaseVectorStorage.upsert`, `BaseVectorStorage.query`, and vector adapter lookup methods. + +```python +import numpy as np + +from ragu.storage.types import Point, SparseEmbedding + +point = Point( + id="chunk-1", + dense_embedding=np.array([0.1, 0.2, 0.3]), + sparse_embedding=SparseEmbedding(indices=[3], values=[0.9]), + metadata={"content": "RAGU stores vectors."}, +) + +print(point.id, point.metadata) +``` + +### EmbeddingHit + +Vector search result from `ragu.storage.types`. + +- Purpose: return ranked matches from vector storage. +- Fields: + - `id: str`: matched record ID. + - `distance: float`: backend score or distance. + - `metadata: dict[str, Any]`: payload returned by the vector backend. +- Used by: `GraphRetriever` to resolve vector hits back to nodes, edges, or chunks. + +```python +from ragu.storage.types import EmbeddingHit + +hit = EmbeddingHit( + id="chunk-1", + distance=0.93, + metadata={"doc_id": "doc-1"}, +) + +print(hit.id, hit.distance, hit.metadata) +``` + +### NetworkXStorage + +Local graph backend. + +- Purpose: store graph data in a NetworkX directed multigraph and persist to GML. +- Used by default through `StorageArguments`. + +```python +import tempfile + +from ragu.graph.types import Entity, Relation +from ragu.storage.graph_storage_adapters.networkx_adapter import NetworkXStorage + +with tempfile.TemporaryDirectory() as directory: + storage = NetworkXStorage(f"{directory}/kg.gml", node_cls=Entity, edge_cls=Relation) + print(storage) +``` + +### MemgraphStorage + +Memgraph-backed graph adapter. + +- Purpose: store graph nodes and edges in an external Memgraph database. + +```python +from ragu.graph.types import Entity, Relation +from ragu.storage.graph_storage_adapters.memgraph_adapter import MemgraphStorage + +storage = MemgraphStorage( + uri="bolt://localhost:7687", + username="memgraph", + password="dummy-password", + node_cls=Entity, + edge_cls=Relation, +) +print(storage) +``` + +### JsonKVStorage + +JSON-backed KV adapter. + +- Purpose: local persistent dictionaries for chunks, communities, and summaries. + +```python +import asyncio +import tempfile + +from ragu.storage.kv_storage_adapters.json_storage import JsonKVStorage + + +async def main(): + with tempfile.TemporaryDirectory() as directory: + storage = JsonKVStorage(storage_folder=directory, filename="data.json") + await storage.upsert({"id": {"value": 1}}) + await storage.index_done_callback() + print(await storage.all_keys()) + + +asyncio.run(main()) +``` + +### NanoVectorDBStorage + +Default local vector DB adapter. + +- Purpose: lightweight dense vector storage for local development and tests. + +```python +import tempfile + +from ragu.storage.vdb_storage_adapters.nano_vdb import NanoVectorDBStorage + +with tempfile.TemporaryDirectory() as directory: + storage = NanoVectorDBStorage( + embedding_dim=3, + storage_folder=directory, + filename="vectors.json", + ) + print(storage.embedding_dim) +``` + +### QdrantVectorDBStorage + +Qdrant-backed vector adapter. + +- Purpose: dense-only or dense+sparse hybrid retrieval. +- Important parameters: `embedding_dim`, `collection_name`, `location`, `url`, `sparse_type`. +- Sparse modes: `bm25`, `bm42`, `splade`, `custom`. + +```python +from ragu.storage.vdb_storage_adapters.qdrant_vdb import QdrantVectorDBStorage + +storage = QdrantVectorDBStorage( + embedding_dim=1536, + location=":memory:", + collection_name="ragu_demo", +) + +print(storage.collection_name) +``` + +### Vector Payload Summary + +Compact summary of the vector payload dataclasses. + +- `Point`: ID plus dense and/or sparse vector and metadata. +- `SparseEmbedding`: aligned `indices` and `values`. +- `EmbeddingHit`: vector query hit with `id`, `distance`, and metadata. + +```python +import numpy as np + +from ragu.storage.types import EmbeddingHit, Point, SparseEmbedding + +sparse = SparseEmbedding(indices=[1, 42], values=[0.5, 1.0]) +point = Point(id="p1", dense_embedding=np.array([1.0, 0.0, 0.0]), sparse_embedding=sparse) +hit = EmbeddingHit(id=point.id, distance=0.99, metadata={"kind": "entity"}) + +print(hit) +``` + +## Data Flow + +Input: + +- `Entity` and `Relation` objects from graph building +- `Chunk` objects from chunking +- `Community` and `CommunitySummary` from clustering +- `Point` objects from `Index` + +Output: + +- stored graph nodes and multigraph edges +- stored KV records +- vector query hits used by `GraphRetriever` + +Used by: + +- `ragu.graph.Index` +- `ragu.graph.KnowledgeGraph` +- `ragu.search_engine` + +## Usage Examples + +### Example 1 - Minimal usage + +```python +import asyncio +import tempfile + +from ragu.graph.types import Entity, Relation +from ragu.storage.graph_storage_adapters.networkx_adapter import NetworkXStorage + + +async def main(): + with tempfile.TemporaryDirectory() as directory: + storage = NetworkXStorage( + filename=f"{directory}/knowledge_graph.gml", + node_cls=Entity, + edge_cls=Relation, + ) + + python = Entity("Python", "Language", "A programming language.", ["chunk-1"]) + guido = Entity("Guido van Rossum", "Person", "Creator of Python.", ["chunk-1"]) + created = Relation( + subject_id=guido.id, + object_id=python.id, + subject_name=guido.entity_name, + object_name=python.entity_name, + relation_type="CREATED", + description="Guido van Rossum created Python.", + source_chunk_id=["chunk-1"], + ) + + await storage.upsert_nodes([python, guido]) + await storage.upsert_edges([created]) + + nodes = await storage.get_nodes([python.id, guido.id]) + edges = await storage.get_edges([(guido.id, python.id, created.id)]) + degrees = await storage.edges_degrees([(guido.id, python.id, created.id)]) + + print(nodes) + print(edges) + print(degrees) + + +asyncio.run(main()) +``` + +### Example 2 - Vector DB usage + +```python +import asyncio +import numpy as np + +from ragu.storage.types import Point +from ragu.storage.vdb_storage_adapters.qdrant_vdb import QdrantVectorDBStorage + + +async def main(): + store = QdrantVectorDBStorage( + embedding_dim=3, + location=":memory:", + collection_name="readme_vectors", + ) + + await store.upsert([ + Point(id="doc-1", dense_embedding=np.array([1.0, 0.0, 0.0]), metadata={"text": "RAGU"}), + ]) + + hits = await store.query( + Point(id="query", dense_embedding=np.array([1.0, 0.0, 0.0])), + top_k=1, + ) + print(hits[0].id) + + +asyncio.run(main()) +``` + +### Example 3 - Vector DB payload lookup and delete + +```python +import asyncio +import numpy as np + +from ragu.storage.types import Point +from ragu.storage.vdb_storage_adapters.qdrant_vdb import QdrantVectorDBStorage + + +async def main(): + storage = QdrantVectorDBStorage( + embedding_dim=3, + location=":memory:", + collection_name="readme_payload_vectors", + ) + await storage.upsert([ + Point( + id="chunk-1", + dense_embedding=np.array([0.1, 0.2, 0.3]), + metadata={"content": "RAGU stores chunk vectors.", "doc_id": "doc-1"}, + ) + ]) + + payloads = await storage.get_payloads_by_ids(["chunk-1"]) + points = await storage.get_points_by_ids(["chunk-1"]) + print(payloads) + print(points) + + await storage.delete(["chunk-1"]) + print(await storage.get_payloads_by_ids(["chunk-1"])) + + +asyncio.run(main()) +``` + +### Example 4 - Pipeline usage + +```python +import asyncio + +from ragu import BuilderArguments, KnowledgeGraph, StorageArguments +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.storage.vdb_storage_adapters.qdrant_vdb import QdrantVectorDBStorage + + +async def main(): + client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", + ) + embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, + ) + graph = KnowledgeGraph( + llm=None, + embedder=embedder, + builder_settings=BuilderArguments(build_only_vector_context=True), + storage_settings=StorageArguments( + vdb_storage_type=QdrantVectorDBStorage, + vdb_storage_kwargs={"location": ":memory:"}, + ), + ) + await graph.build_from_docs(["RAGU can use Qdrant for chunk vectors."]) + print(await graph.index.chunks_kv_storage.all_keys()) + + +asyncio.run(main()) +``` + +## Integration Points + +- Embedders: `Index` creates dense embeddings before vector upsert. +- Sparse embedders: `Index` stores sparse document vectors; `GraphRetriever` sends sparse query vectors. +- Qdrant: hybrid mode stores one dense vector field named `"dense"` and one sparse vector field named by `sparse_type`. +- Graph module: `StorageArguments` selects storage implementations. + +## Configuration + +Default storage is controlled by `ragu.graph.index.StorageArguments`: + +- `graph_backend_storage=NetworkXStorage` +- `kv_storage_type=JsonKVStorage` +- `vdb_storage_type=NanoVectorDBStorage` + +Qdrant deployment modes: + +- local on-disk: omit remote arguments and pass `storage_folder`/`filename` +- in-memory: `location=":memory:"` +- remote: pass `url`, `host`, `port`, `grpc_port`, and/or `api_key` + +Hybrid Qdrant storage additionally needs a matching sparse embedder: + +```python +from ragu.models.sparse_embedder import BM25 + +sparse_embedder = BM25() +vdb_kwargs = {"location": ":memory:", "sparse_type": "bm25"} +``` + +## Dependencies + +Internal: + +- `ragu.storage.types` +- `ragu.common.global_parameters.Settings` +- `ragu.utils.ragu_utils` + +External: + +- `networkx` +- `qdrant-client` +- `numpy` +- `pydantic` +- optional Memgraph client dependencies + +## Notes / Pitfalls + +- `Point` must contain at least one dense or sparse embedding. +- `SparseEmbedding.indices` and `SparseEmbedding.values` must have the same length. +- Qdrant validates existing collection schema; mismatched vector dimensions or sparse configuration raise `ValueError`. +- For BM25 and BM42, Qdrant sparse vectors use the `IDF` modifier. +- Deleting chunks through `Index.delete_chunks()` cascades to entities and relations sourced from those chunks. diff --git a/ragu/storage/graph_storage_adapters/README.md b/ragu/storage/graph_storage_adapters/README.md new file mode 100644 index 0000000..9e9d52d --- /dev/null +++ b/ragu/storage/graph_storage_adapters/README.md @@ -0,0 +1,159 @@ +# Module: ragu.storage.graph_storage_adapters + +## Role in RAGU Pipeline + +This package provides graph backend implementations for `Index`. Graph storage persists entities and relations after extraction and supports local-search neighborhood traversal. + +Pipeline position: + +```text +Entity/Relation -> BaseGraphStorage adapter -> graph backend -> LocalSearchEngine +``` + +## Overview + +Graph adapters implement `BaseGraphStorage` for different backends while preserving RAGU's directed multigraph contract. + +## Key Components + +### NetworkXStorage + +- Purpose: default local graph backend. +- Persistence: reads and writes GML files. +- Important parameters: `filename`, `node_cls`, `edge_cls`. + +## Data Flow + +Input: `Entity` nodes and `Relation` edges. + +Output: stored nodes, stored multigraph edges, edge-degree values, incident edge lists. + +Used by: + +- `ragu.graph.Index` +- `ragu.graph.KnowledgeGraph` +- `ragu.search_engine.LocalSearchEngine` + +## Usage Examples + +### Example 1 - Minimal usage + +```python +import asyncio + +from ragu.graph.types import Entity, Relation +from ragu.storage.graph_storage_adapters.networkx_adapter import NetworkXStorage + + +async def main(): + storage = NetworkXStorage( + filename="knowledge_graph.gml", + node_cls=Entity, + edge_cls=Relation, + ) + python = Entity("Python", "Language", "A programming language.", ["chunk-1"]) + guido = Entity("Guido van Rossum", "Person", "Creator of Python.", ["chunk-1"]) + relation = Relation( + subject_id=guido.id, + object_id=python.id, + subject_name=guido.entity_name, + object_name=python.entity_name, + relation_type="CREATED", + description="Guido van Rossum created Python.", + source_chunk_id=["chunk-1"], + ) + + await storage.upsert_nodes([python, guido]) + await storage.upsert_edges([relation]) + + print(await storage.get_nodes([python.id, guido.id])) + print(await storage.get_edges([(guido.id, python.id, relation.id)])) + print(await storage.get_all_edges_for_nodes([python.id])) + + +asyncio.run(main()) +``` + +### Example 2 - Delete graph records + +```python +import asyncio + +from ragu.graph.types import Entity, Relation +from ragu.storage.graph_storage_adapters.networkx_adapter import NetworkXStorage + + +async def main(): + storage = NetworkXStorage( + filename="knowledge_graph.gml", + node_cls=Entity, + edge_cls=Relation, + ) + python = Entity("Python", "Language", "A programming language.", ["chunk-1"]) + guido = Entity("Guido van Rossum", "Person", "Creator of Python.", ["chunk-1"]) + relation = Relation( + guido.id, + python.id, + guido.entity_name, + python.entity_name, + "CREATED", + "Guido van Rossum created Python.", + ) + + await storage.upsert_nodes([python, guido]) + await storage.upsert_edges([relation]) + await storage.delete_edges([(guido.id, python.id, relation.id)]) + await storage.delete_nodes([python.id]) + + print(await storage.get_nodes([python.id, guido.id])) + + +asyncio.run(main()) +``` + +### Example 3 - Pipeline usage + +```python +from ragu import BuilderArguments, KnowledgeGraph, StorageArguments +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.storage.graph_storage_adapters.networkx_adapter import NetworkXStorage + + +client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", +) +embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, +) + +graph = KnowledgeGraph( + llm=None, + embedder=embedder, + builder_settings=BuilderArguments(build_only_vector_context=True), + storage_settings=StorageArguments(graph_backend_storage=NetworkXStorage), +) +``` + +## Integration Points + +- `Index` calls graph adapters for CRUD and cascade deletion. +- `GraphRetriever` resolves relation vector hits through graph edge specs. +- Local search uses graph edges around retrieved entities. + +## Configuration + +`StorageArguments.graph_storage_kwargs` is merged with the default `knowledge_graph.gml` filename under `Settings.storage_folder`. + +## Dependencies + +- `networkx` + +## Notes / Pitfalls + +- RAGU edge identity is `(subject_id, object_id, relation_id)`. +- `NetworkXStorage.index_done_callback()` writes GML to disk. +- Deleting a node removes connected graph edges. diff --git a/ragu/storage/kv_storage_adapters/README.md b/ragu/storage/kv_storage_adapters/README.md new file mode 100644 index 0000000..1b9c924 --- /dev/null +++ b/ragu/storage/kv_storage_adapters/README.md @@ -0,0 +1,108 @@ +# Module: ragu.storage.kv_storage_adapters + +## Role in RAGU Pipeline + +This package provides key-value storage for chunks, community metadata, and community summaries. + +Pipeline position: + +```text +Chunk/Community/CommunitySummary -> KV storage -> retrieval context resolution +``` + +## Overview + +KV adapters store JSON-like payloads that do not belong in the graph backend or vector database. The default implementation is local and file-backed. + +## Key Components + +### JsonKVStorage + +- Purpose: persistent dictionary stored as a JSON file. +- Important methods: `all_keys`, `get_by_id`, `get_by_ids`, `filter_keys`, `upsert`, `delete`, `drop`. +- Important parameters: `storage_folder`, `filename`. + +## Data Flow + +Input: mappings from IDs to chunk/community/summary payloads. + +Output: ordered lookup results with `None` for missing IDs. + +Used by: + +- `Index.chunks_kv_storage` +- `Index.community_kv_storage` +- `Index.community_summary_kv_storage` +- `GlobalSearchEngine` + +## Usage Examples + +### Example 1 - Minimal usage + +```python +import asyncio +import tempfile + +from ragu.storage.kv_storage_adapters.json_storage import JsonKVStorage + + +async def main(): + with tempfile.TemporaryDirectory() as directory: + storage = JsonKVStorage(storage_folder=directory, filename="chunks.json") + await storage.upsert({"chunk-1": {"content": "RAGU", "doc_id": "doc-1"}}) + await storage.index_done_callback() + print(await storage.get_by_id("chunk-1")) + + +asyncio.run(main()) +``` + +### Example 2 - Pipeline usage + +```python +from ragu import BuilderArguments, KnowledgeGraph, StorageArguments +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.storage.kv_storage_adapters.json_storage import JsonKVStorage + + +client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", +) +embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, +) + +graph = KnowledgeGraph( + llm=None, + embedder=embedder, + builder_settings=BuilderArguments(build_only_vector_context=True), + storage_settings=StorageArguments(kv_storage_type=JsonKVStorage), +) +``` + +## Integration Points + +- Chunk KV records are resolved after vector chunk search. +- Community summaries are read by `GlobalSearchEngine`. +- Community metadata links cluster IDs to entity and relation IDs. + +## Configuration + +`StorageArguments` provides separate kwargs for chunks, communities, and summaries: + +- `chunks_kv_storage_kwargs` +- `communities_kv_storage_kwargs` +- `summary_kv_storage_kwargs` + +## Dependencies + +- Python `json` + +## Notes / Pitfalls + +- `upsert()` updates memory; `index_done_callback()` persists to disk. +- `drop()` clears in-memory data and should be followed by `index_done_callback()` when persistence matters. diff --git a/ragu/storage/vdb_storage_adapters/README.md b/ragu/storage/vdb_storage_adapters/README.md new file mode 100644 index 0000000..4a0d83d --- /dev/null +++ b/ragu/storage/vdb_storage_adapters/README.md @@ -0,0 +1,202 @@ +# Module: ragu.storage.vdb_storage_adapters + +## Role in RAGU Pipeline + +This package stores dense and optional sparse vectors for entities, relations, and chunks. Search engines depend on these adapters for similarity retrieval. + +Pipeline position: + +```text +text -> Embedder/SparseEmbedder -> Point -> vector DB -> EmbeddingHit +``` + +## Overview + +Vector adapters implement `BaseVectorStorage`. RAGU ships a lightweight local adapter and a Qdrant adapter for production-style dense and hybrid retrieval. + +## Key Components + +### NanoVectorDBStorage + +- Purpose: default local dense vector storage. +- Used for: development, tests, small examples. + +### QdrantVectorDBStorage + +- Purpose: Qdrant-backed dense-only or hybrid dense+sparse storage. +- Important parameters: `embedding_dim`, `collection_name`, `location`, `url`, `sparse_type`. +- Sparse modes: `bm25`, `bm42`, `splade`, `custom`. + +## Data Flow + +Input: `Point` objects with dense and/or sparse vectors. + +Output: ranked `EmbeddingHit` results. + +Used by: + +- `Index.nodes_vector_db` +- `Index.edges_vector_db` +- `Index.chunks_vector_db` +- `GraphRetriever` + +## Usage Examples + +### Example 1 - Minimal usage + +```python +import asyncio +import numpy as np + +from ragu.storage.types import Point +from ragu.storage.vdb_storage_adapters.qdrant_vdb import QdrantVectorDBStorage + + +async def main(): + storage = QdrantVectorDBStorage( + embedding_dim=3, + location=":memory:", + collection_name="example_vectors", + ) + await storage.upsert([ + Point(id="entity-1", dense_embedding=np.array([1.0, 0.0, 0.0])), + ]) + hits = await storage.query( + Point(id="query", dense_embedding=np.array([1.0, 0.0, 0.0])), + top_k=1, + ) + print(hits[0].id) + + +asyncio.run(main()) +``` + +### Example 2 - Point lookup and delete + +```python +import asyncio +import numpy as np + +from ragu.storage.types import Point +from ragu.storage.vdb_storage_adapters.qdrant_vdb import QdrantVectorDBStorage + + +async def main(): + storage = QdrantVectorDBStorage( + embedding_dim=3, + location=":memory:", + collection_name="point_crud_vectors", + ) + await storage.upsert([ + Point( + id="entity-1", + dense_embedding=np.array([1.0, 0.0, 0.0]), + metadata={"entity_name": "Python"}, + ) + ]) + + print(await storage.get_points_by_ids(["entity-1"])) + print(await storage.get_payloads_by_ids(["entity-1"])) + + await storage.delete(["entity-1"]) + print(await storage.get_points_by_ids(["entity-1"])) + + +asyncio.run(main()) +``` + +### Example 3 - Hybrid dense + sparse point + +```python +import asyncio +import numpy as np + +from ragu.storage.types import Point, SparseEmbedding +from ragu.storage.vdb_storage_adapters.qdrant_vdb import QdrantVectorDBStorage + + +async def main(): + storage = QdrantVectorDBStorage( + embedding_dim=3, + location=":memory:", + collection_name="hybrid_vectors", + sparse_type="bm25", + ) + await storage.upsert([ + Point( + id="chunk-1", + dense_embedding=np.array([0.1, 0.2, 0.3]), + sparse_embedding=SparseEmbedding(indices=[10, 25], values=[0.8, 0.4]), + metadata={"content": "Hybrid retrieval uses dense and sparse vectors."}, + ) + ]) + + hits = await storage.query( + Point( + dense_embedding=np.array([0.1, 0.2, 0.3]), + sparse_embedding=SparseEmbedding(indices=[10], values=[0.8]), + ), + top_k=1, + ) + print(hits) + + +asyncio.run(main()) +``` + +### Example 4 - Pipeline usage + +```python +from ragu import BuilderArguments, KnowledgeGraph, StorageArguments +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.storage.vdb_storage_adapters.qdrant_vdb import QdrantVectorDBStorage + + +client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", +) +embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, +) + +graph = KnowledgeGraph( + llm=None, + embedder=embedder, + builder_settings=BuilderArguments(build_only_vector_context=True), + storage_settings=StorageArguments( + vdb_storage_type=QdrantVectorDBStorage, + vdb_storage_kwargs={"location": ":memory:"}, + ), +) +``` + +## Integration Points + +- `Index` writes vectors for graph artifacts and chunks. +- `GraphRetriever` creates query `Point` objects and calls `query()`. +- Sparse embedders must be paired with a vector adapter configured for sparse vectors. + +## Configuration + +Qdrant modes: + +- dense only: `sparse_type=None` +- BM25 hybrid: `sparse_type="bm25"` +- BM42 hybrid: `sparse_type="bm42"` +- SPLADE hybrid: `sparse_type="splade"` + +## Dependencies + +- `numpy` +- `qdrant-client` +- local NanoVectorDB dependencies + +## Notes / Pitfalls + +- Dense vector dimensions must match `Embedder.dim`. +- Existing Qdrant collections are schema-validated on first use. +- Qdrant hybrid queries use reciprocal-rank fusion. diff --git a/ragu/triplet/README.md b/ragu/triplet/README.md new file mode 100644 index 0000000..380b2ec --- /dev/null +++ b/ragu/triplet/README.md @@ -0,0 +1,241 @@ +# Module: ragu.triplet + +## Role in RAGU Pipeline + +`ragu.triplet` is the extraction layer. It converts chunks into graph artifacts: entities and relations. These artifacts are then summarized, clustered, and stored by `ragu.graph`. + +Pipeline position: + +```text +List[Chunk] -> artifact extractor -> List[Entity], List[Relation] -> graph builder +``` + +## Overview + +The module exists to isolate LLM prompting and structured artifact conversion from graph storage. Extractors use prompts and Pydantic schemas to get structured output, then convert that output into `Entity` and `Relation` dataclasses with stable IDs and source chunk references. + +## Key Components + +### BaseArtifactExtractor + +Abstract extractor interface. + +- Purpose: standardize `extract(chunks) -> (entities, relations)`. +- Important behavior: instances are callable as async functions. +- Used by: `InMemoryGraphBuilder.extract_graph`. + +```python +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.triplet import ArtifactsExtractorLLM + +client = CachedAsyncOpenAI(base_url="https://api.openai.com/v1", api_key="dummy-api-token") +llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") +extractor = ArtifactsExtractorLLM(llm) + +print(extractor.get_prompt("artifact_extraction").description) +``` + +### ArtifactsExtractorLLM + +Single-pass LLM extractor. + +- Purpose: extract entities and relations from each chunk in one structured call. +- Optional validation: `do_validation=True` runs an additional artifact validation prompt. +- Important parameters: `llm`, `language`, `entity_types`, `relation_types`. + +```python +import asyncio + +from ragu.chunker.types import Chunk +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.triplet import ArtifactsExtractorLLM + + +async def main(): + client = CachedAsyncOpenAI(base_url="https://api.openai.com/v1", api_key="dummy-api-token") + llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") + extractor = ArtifactsExtractorLLM(llm, do_validation=False) + chunks = [Chunk("Python was created by Guido van Rossum.", 0, "doc-1")] + entities, relations = await extractor.extract(chunks) + print(entities, relations) + + +asyncio.run(main()) +``` + +### TwoStageArtifactsExtractorLLM + +Two-stage LLM extractor. + +- Purpose: extract entities first, then extract relations constrained by the entity list. +- Optional validation: `do_entity_validation`, `do_relation_validation`. +- Best for: reducing unresolved relation endpoints. + +```python +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.triplet import TwoStageArtifactsExtractorLLM + +client = CachedAsyncOpenAI(base_url="https://api.openai.com/v1", api_key="dummy-api-token") +llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") +extractor = TwoStageArtifactsExtractorLLM( + llm, + do_entity_validation=True, + do_relation_validation=True, +) + +print(extractor.get_prompt("entity_extraction").pydantic_model) +``` + +### RaguLmArtifactExtractor + +Extractor adapter for RAGU-LM style artifact extraction. + +- Purpose: uses chunk context and RAGU-LM prompts to produce graph artifacts. + +```python +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.triplet import RaguLmArtifactExtractor + +client = CachedAsyncOpenAI(base_url="https://api.openai.com/v1", api_key="dummy-api-token") +llm = LLMOpenAI(client=client, model_name="ragu-lm") +extractor = RaguLmArtifactExtractor(llm, temperature=0.0, top_p=0.95) + +print(extractor.temperature) +``` + +### NEREL Types + +`ragu.triplet.types` defines default Russian NEREL-oriented entity and relation type lists used as prompt hints. + +```python +from ragu.triplet.types import NEREL_ENTITY_TYPES, NEREL_RELATION_TYPES + +print(NEREL_ENTITY_TYPES[:3]) +print(NEREL_RELATION_TYPES[:3]) +``` + +## Data Flow + +Input: `list[Chunk]`. + +Output: + +- `list[Entity]` with `source_chunk_id=[chunk.id]` +- `list[Relation]` with endpoint IDs resolved against entities extracted from the same chunk + +Used by: + +- `ragu.graph.InMemoryGraphBuilder` +- `ragu.graph.KnowledgeGraph.build_from_docs` + +## Usage Examples + +### Example 1 - Minimal usage + +```python +import asyncio + +from ragu.chunker.types import Chunk +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.triplet import ArtifactsExtractorLLM + + +async def main(): + client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", + ) + llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") + extractor = ArtifactsExtractorLLM(llm, entity_types=["Language"], relation_types=[]) + chunks = [Chunk(content="Python is a programming language.", chunk_order_idx=0, doc_id="doc-1")] + entities, relations = await extractor.extract(chunks) + print(entities[0].entity_name, relations) + + +asyncio.run(main()) +``` + +### Example 2 - Pipeline usage + +```python +import asyncio + +from ragu import BuilderArguments, KnowledgeGraph, SimpleChunker +from ragu.models.embedder import EmbedderOpenAI +from ragu.models.llm import LLMOpenAI +from ragu.models.openai import CachedAsyncOpenAI +from ragu.triplet import TwoStageArtifactsExtractorLLM + + +async def main(): + client = CachedAsyncOpenAI( + base_url="https://api.openai.com/v1", + api_key="dummy-api-token", + ) + llm = LLMOpenAI(client=client, model_name="gpt-4o-mini") + embedder = EmbedderOpenAI( + client=client, + model_name="text-embedding-3-small", + dim=1536, + ) + graph = KnowledgeGraph( + llm=llm, + embedder=embedder, + chunker=SimpleChunker(max_chunk_size=1000, overlap=100), + artifact_extractor=TwoStageArtifactsExtractorLLM(llm), + builder_settings=BuilderArguments(make_community_summary=False), + ) + await graph.build_from_docs(["Python is a programming language."]) + print(await graph.index.graph_backend.get_all_nodes()) + + +asyncio.run(main()) +``` + +## Integration Points + +- LLMs: extractors call `llm.batch_chat_completion` with prompt-rendered OpenAI messages. +- Prompt layer: extractors inherit `RaguGenerativeModule` and use `RAGUInstruction`. +- Graph layer: outputs are consumed by `EntitySummarizer`, `RelationSummarizer`, additional builder modules, and `Index`. +- Storage: artifact IDs become graph node/edge IDs and vector record IDs. + +## Configuration + +`ArtifactsExtractorLLM`: + +- `do_validation=False` +- `language=Settings.language` +- `entity_types=NEREL_ENTITY_TYPES` +- `relation_types=NEREL_RELATION_TYPES` + +`TwoStageArtifactsExtractorLLM`: + +- `do_entity_validation`: disabled unless truthy. +- `do_relation_validation`: disabled unless truthy. +- prompt type hints can be overridden with `entity_types` and `relation_types`. + +## Dependencies + +Internal: + +- `ragu.chunker.types.Chunk` +- `ragu.common.prompts` +- `ragu.graph.types` +- `ragu.models.llm` + +External: + +- `pydantic` +- `typing_extensions` + +## Notes / Pitfalls + +- Relations are skipped when their source or target entity name cannot be resolved in the same chunk's extracted entity list. +- Entity IDs are generated from entity name and type, so repeated mentions of the same entity merge later in `KnowledgeGraph`. +- The single-pass extractor may produce relation endpoints that do not exactly match extracted entity names; the two-stage extractor is stricter. +- The `multi_stage_artifacts_extractor.py` file currently contains commented-out experimental code and is not exported by `ragu.triplet`. diff --git a/ragu/utils/README.md b/ragu/utils/README.md new file mode 100644 index 0000000..d80c8a0 --- /dev/null +++ b/ragu/utils/README.md @@ -0,0 +1,213 @@ +# Module: ragu.utils + +## Role in RAGU Pipeline + +`ragu.utils` contains low-level helpers used across indexing, storage, testing, token truncation, hashing, serialization, and text normalization. It supports every pipeline stage but is not a domain stage itself. + +Pipeline position: + +```text +shared utilities -> chunking / extraction / graph / storage / retrieval / tests +``` + +## Overview + +The module exists to keep reusable mechanics out of the GraphRAG domain modules. The most important production helpers are deterministic ID generation, token truncation, disk cache access, object serialization, async context composition, and sparse-text normalization. + +## Key Components + +### compute_mdhash_id + +Creates deterministic MD5-based IDs. + +- Purpose: stable IDs for chunks, entities, relations, communities, and points. +- Important parameter: `prefix`. + +```python +from ragu.utils.ragu_utils import compute_mdhash_id + +entity_id = compute_mdhash_id("Python - Language", prefix="ent-") +print(entity_id) +``` + +### TokenTruncation + +Callable token-budget truncator. + +- Purpose: trim prompt contexts for LLM and embedding calls. +- Important parameters: `model_id`, `tokenizer_type`, `max_tokens`. +- Used by: `KnowledgeGraph`, `BaseEngine`. + +```python +from ragu.utils.token_truncation import TokenTruncation + +truncate = TokenTruncation( + model_id="gpt-4o", + tokenizer_type="tiktoken", + max_tokens=16, +) + +print(truncate("RAGU builds and queries knowledge graphs.")) +``` + +### read_text_from_files + +Reads text files from a directory. + +- Purpose: load document corpora for examples or indexing jobs. +- Important parameter: `file_extensions`. + +```python +from ragu.utils.ragu_utils import read_text_from_files + +documents = read_text_from_files("./examples/data/ru", file_extensions={".txt"}) +print(len(documents)) +``` + +### serialize and serialized_size + +Dataclass/object serialization helpers. + +- Purpose: convert graph and vector payloads into JSON-like dictionaries. +- Used by: storage payload construction. + +```python +from ragu.graph.types import Entity +from ragu.utils.ragu_utils import serialize, serialized_size + +entity = Entity("Python", "Language", "A programming language.", ["chunk-1"]) +payload = serialize(entity) + +print(payload) +print(serialized_size(payload)) +``` + +### split_on_batches_by_size + +Splits objects into batches constrained by serialized size. + +- Purpose: keep vector DB upserts under payload limits. +- Used by: Qdrant vector storage. + +```python +from ragu.utils.ragu_utils import split_on_batches_by_size + +items = [{"id": "a", "text": "short"}, {"id": "b", "text": "also short"}] +batches = list(split_on_batches_by_size(items, max_size_in_bytes=1024)) + +print(batches) +``` + +### BaseNormalizer and PymorphyNormalizer + +Text normalization for sparse retrieval. + +- Purpose: normalize/stem text before BM25/SPLADE style sparse embedding. +- Used by: sparse embedders. + +```python +from ragu.utils.text_normalize import PymorphyNormalizer + +normalizer = PymorphyNormalizer() +print(normalizer.normalize_batch(["кошки бегали"])) +``` + +### OpenAIMockServer + +Testing server for OpenAI-compatible endpoints. + +- Purpose: deterministic tests for LLM wrappers and structured output behavior. + +```python +from ragu.utils.testing.openai_mock_server import OpenAIMockServer + +server = OpenAIMockServer(("127.0.0.1", 0)) +print(server.server_port) +server.server_close() +``` + +## Data Flow + +Input: arbitrary strings, dataclasses, token contexts, file paths, async callables. + +Output: stable IDs, truncated strings, serialized payloads, text batches, normalized text, test server responses. + +Used by: + +- `ragu.chunker` for chunk/document IDs +- `ragu.graph` for artifact IDs and token truncation +- `ragu.storage` for serialization and batch sizing +- `ragu.models` for caching and debug wrappers +- `tests` for mock OpenAI services + +## Usage Examples + +### Example 1 - Minimal usage + +```python +from ragu.utils.ragu_utils import compute_mdhash_id + +entity_id = compute_mdhash_id("Python - Language", prefix="ent-") +print(entity_id) +``` + +### Example 2 - Pipeline usage + +```python +from ragu.common.global_parameters import Settings +from ragu.utils.ragu_utils import read_text_from_files +from ragu.utils.token_truncation import TokenTruncation + +Settings.storage_folder = "./ragu_working_dir/docs" + +documents = read_text_from_files("./examples/data/ru", file_extensions={".txt"}) +truncate = TokenTruncation( + model_id="gpt-4o", + tokenizer_type="tiktoken", + max_tokens=1000, +) + +short_documents = [truncate(document) for document in documents] +print(len(short_documents)) +``` + +## Integration Points + +- Chunking and graph IDs: ID hashes are generated by `compute_mdhash_id`. +- LLM and embedding limits: `TokenTruncation` is configured in `KnowledgeGraph` and search engines. +- Qdrant: `split_on_batches_by_size` prevents oversized payloads. +- Sparse embedding: normalizers can be passed into `BM25` and `SPLADE`. +- Testing: `OpenAIMockServer` backs model wrapper tests. + +## Configuration + +`TokenTruncation`: + +- `tokenizer_type="tiktoken"` for OpenAI tokenizer behavior. +- `tokenizer_type="local"` for locally available tokenizers. +- `max_tokens` is the hard budget returned by the callable truncator. + +`read_text_from_files`: + +- `file_extensions=None` reads all files. +- pass a collection like `{".txt", ".md"}` to restrict corpus loading. + +## Dependencies + +Internal: + +- `ragu.common.logger` + +External: + +- `diskcache` +- `numpy` +- tokenizer libraries used by `TokenTruncation` +- optional `pymorphy2` or compatible packages for morphology normalization + +## Notes / Pitfalls + +- `compute_mdhash_id` is deterministic; identical content and prefix produce identical IDs. +- Token truncation can change retrieval and answer quality if budgets are too small. +- `serialize()` is used for storage payloads; custom dataclasses should remain JSON-compatible. +- The testing utilities are for local tests, not production serving. From c1f104d7cb843d00a125c603bfa3d415b013904a Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Sun, 26 Apr 2026 03:33:57 +0700 Subject: [PATCH 37/42] Update types --- ragu/graph/index.py | 11 ++++++----- ragu/storage/types.py | 2 -- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ragu/graph/index.py b/ragu/graph/index.py index b23569a..dbfdcb1 100644 --- a/ragu/graph/index.py +++ b/ragu/graph/index.py @@ -20,7 +20,7 @@ ) from ragu.storage.graph_storage_adapters.networkx_adapter import NetworkXStorage from ragu.storage.kv_storage_adapters.json_storage import JsonKVStorage -from ragu.storage.types import Node, Edge, Point +from ragu.storage.types import Point from ragu.storage.vdb_storage_adapters.nano_vdb import NanoVectorDBStorage from ragu.utils.token_truncation import TokenTruncation @@ -100,8 +100,8 @@ def __str__(self) -> str: return self.to_text() -NodeT = TypeVar("NodeT", bound=Node) -EdgeT = TypeVar("EdgeT", bound=Edge) +NodeT = TypeVar("NodeT", bound=Entity) +EdgeT = TypeVar("EdgeT", bound=Relation) class Index(Generic[NodeT, EdgeT]): """ @@ -727,8 +727,9 @@ async def get_communities(self, community_ids: List[str]) -> List[Optional[Commu # TODO: remove full scan here all_edges = await self.graph_backend.get_all_edges() edges = [edge for edge in all_edges if edge and edge.id in relation_id_set] - entities = cast(List[Entity], [entity for entity in nodes if entity]) - relations = cast(List[Relation], [relation for relation in edges if relation]) + + entities: List[NodeT] = [entity for entity in nodes if entity is not None] + relations: List[EdgeT] = [relation for relation in edges if relation is not None] communities.append(Community( id=community_id, diff --git a/ragu/storage/types.py b/ragu/storage/types.py index 4858c19..5887a1b 100644 --- a/ragu/storage/types.py +++ b/ragu/storage/types.py @@ -22,8 +22,6 @@ class Node: """ id: str - source_chunk_id: List[str] - clusters: List[ClusterInfo] def to_dict(self) -> Dict[str, Any]: """ From 60ebc365524d4b7d4ec786cb0c70f9ea43e64a4c Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Sun, 26 Apr 2026 22:54:00 +0700 Subject: [PATCH 38/42] Add project tree --- ragu/README.md | 118 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 ragu/README.md diff --git a/ragu/README.md b/ragu/README.md new file mode 100644 index 0000000..e3d1272 --- /dev/null +++ b/ragu/README.md @@ -0,0 +1,118 @@ +# RAGU Package Tree + +```text +ragu/ +├── __init__.py +├── README.md +├── chunker/ +│ ├── __init__.py +│ ├── base_chunker.py +│ ├── chunkers.py +│ ├── types.py +│ └── README.md +├── common/ +│ ├── __init__.py +│ ├── base.py +│ ├── batch_generator.py +│ ├── cache.py +│ ├── env.py +│ ├── global_parameters.py +│ ├── logger.py +│ └── prompts/ +│ ├── __init__.py +│ ├── default_models.py +│ ├── default_templates.py +│ ├── messages.py +│ ├── prompt_storage.py +│ └── README.md +├── graph/ +│ ├── __init__.py +│ ├── artifacts_summarizer.py +│ ├── builder_modules.py +│ ├── community_summarizer.py +│ ├── graph_builder_pipeline.py +│ ├── graph_retrieve_backend.py +│ ├── index.py +│ ├── knowledge_graph.py +│ ├── types.py +│ └── README.md +├── models/ +│ ├── __init__.py +│ ├── caching.py +│ ├── embedder.py +│ ├── llm.py +│ ├── openai.py +│ ├── scorer.py +│ └── sparse_embedder.py +├── search_engine/ +│ ├── __init__.py +│ ├── base_engine.py +│ ├── global_search.py +│ ├── local_search.py +│ ├── mix_search.py +│ ├── naive_search.py +│ ├── query_plan.py +│ ├── search_functional.py +│ └── README.md +├── storage/ +│ ├── __init__.py +│ ├── base_storage.py +│ ├── types.py +│ ├── README.md +│ ├── graph_storage_adapters/ +│ │ ├── README.md +│ │ ├── memgraph_adapter.py +│ │ └── networkx_adapter.py +│ ├── kv_storage_adapters/ +│ │ ├── README.md +│ │ └── json_storage.py +│ └── vdb_storage_adapters/ +│ ├── README.md +│ ├── nano_vdb.py +│ └── qdrant_vdb.py +├── triplet/ +│ ├── __init__.py +│ ├── base_artifact_extractor.py +│ ├── llm_artifact_extractor.py +│ ├── multi_stage_artifacts_extractor.py +│ ├── ragu_lm_artifact_extractor.py +│ ├── two_stage_extractor.py +│ ├── types.py +│ └── README.md +└── utils/ + ├── __init__.py + ├── ragu_utils.py + ├── text_normalize.py + ├── token_truncation.py + ├── README.md + └── testing/ + ├── __init__.py + ├── openai_mock_server.py + └── README.md +``` + +## What the folders contain + +`chunker/` +Chunking logic and chunk data types. + +`common/` +Shared settings, prompt helpers, cache utilities, logging, and base classes. + +`graph/` +Graph construction, summarization, indexing, and the `KnowledgeGraph` facade. + +`models/` +LLM, embedder, scoring, caching, and OpenAI client adapters. + +`search_engine/` +Retrieval engines. + +`storage/` +Graph, key-value, and vector storage contracts plus concrete adapters. + +`triplet/` +Entity and relation extraction modules. + +`utils/` +General utilities and testing helpers. From 09d0d11e7a0ba1c36c3713391d4fe0375bb62d75 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 27 Apr 2026 00:05:53 +0700 Subject: [PATCH 39/42] Add graph reindexing --- ragu/graph/knowledge_graph.py | 235 ++++++++++++++++++++++++++++++---- 1 file changed, 209 insertions(+), 26 deletions(-) diff --git a/ragu/graph/knowledge_graph.py b/ragu/graph/knowledge_graph.py index 125a20b..eedb24c 100644 --- a/ragu/graph/knowledge_graph.py +++ b/ragu/graph/knowledge_graph.py @@ -1,8 +1,18 @@ from __future__ import annotations import re -from collections import defaultdict -from typing import Dict, Iterable, List, Optional, Set, Literal +from collections import Counter, defaultdict +from typing import ( + Dict, + Iterable, + List, + Optional, + Set, + Literal, + Tuple +) + +import pandas as pd from ragu.chunker.base_chunker import BaseChunker from ragu.chunker.types import Chunk @@ -25,11 +35,8 @@ from ragu.utils.token_truncation import TokenTruncation -def _duplicate_ids(items: List[Entity] | List[Relation]) -> List[str]: - counts: Dict[str, int] = defaultdict(int) - for item in items: - assert item.id - counts[item.id] += 1 +def _duplicate_ids(items: Iterable[Entity | Relation | CommunitySummary | Chunk | Community]) -> List[str]: + counts = Counter(item.id for item in items) return [item_id for item_id, count in counts.items() if count > 1] @@ -64,6 +71,7 @@ def _unique_description_fragments(descriptions: Iterable[str]) -> List[str]: return unique_parts +# TODO: add an ability to pass merge policy into KG def default_merge_entities_policy(entities: List[Entity]) -> Entity: """ Default merge policy for entities (class Entity). @@ -128,12 +136,13 @@ def default_merge_entities_policy(entities: List[Entity]) -> Entity: clusters=deduplicated_clusters, ) +# TODO: add an ability to pass merge policy into KG def default_merge_relations_policy(relations: List[Relation]) -> Relation: """ Default merge policy for relations (class Relation). Default policy = concatenate descriptions, merge source_chunk_ids, docs ids and clusters. - :param entities: Relations to merge. + :param relations: Relations to merge. :return: New single relation. """ if len(relations) == 0: @@ -177,7 +186,7 @@ class KnowledgeGraph: """ High-level facade for building, storing, and querying a knowledge graph. - :param client: LLM client used by extraction and summarization modules. + :param llm: LLM client used by extraction and summarization modules. :param embedder: Embedder used for vector storage and clustering/similarity steps. :param chunker: Optional chunker used to split input documents. :param artifact_extractor: Optional extractor used to generate entities/relations from chunks. @@ -483,6 +492,55 @@ async def get_communities(self, community_ids: List[str]) -> List[Community | No """ return await self.index.get_communities(community_ids) + async def upsert_communities(self, communities: List[Community]) -> "KnowledgeGraph": + """ + Add or replace one or more communities. + + :param communities: Communities to upsert. + :return: Self for method chaining. + """ + duplicate_ids = _duplicate_ids(communities) + if duplicate_ids: + raise ValueError(f"Cannot upsert duplicated community IDs in one request: {duplicate_ids}") + + await self.index.upsert_communities(communities) + return self + + async def update_communities(self, communities: List[Community]) -> "KnowledgeGraph": + """ + Replace one or more existing communities by ID. + + :param communities: Communities to replace. + :return: Self for method chaining. + :raises ValueError: If duplicate IDs are provided or a community is missing. + """ + duplicate_ids = _duplicate_ids(communities) + if duplicate_ids: + raise ValueError(f"Cannot update duplicated community IDs in one request: {duplicate_ids}") + + community_ids = [community.id for community in communities] + existing_communities = await self.index.community_kv_storage.get_by_ids(community_ids) + missing_ids = [ + community_id + for community_id, existing in zip(community_ids, existing_communities) + if existing is None + ] + if missing_ids: + raise ValueError(f"Cannot update non-existent communities: {missing_ids}") + + await self.index.upsert_communities(communities) + return self + + async def delete_communities(self, community_ids: List[str]) -> "KnowledgeGraph": + """ + Delete communities and their summaries. + + :param community_ids: IDs of the communities to delete. + :return: Self for method chaining. + """ + await self.index.delete_communities(community_ids) + return self + async def upsert_summaries(self, summaries: List[CommunitySummary]) -> "KnowledgeGraph": """ Add or replace community summaries. @@ -490,6 +548,35 @@ async def upsert_summaries(self, summaries: List[CommunitySummary]) -> "Knowledg :param summaries: Summaries to upsert. :return: Self for method chaining. """ + duplicate_ids = _duplicate_ids(summaries) + if duplicate_ids: + raise ValueError(f"Cannot upsert duplicated summary IDs in one request: {duplicate_ids}") + + await self.index.upsert_summaries(summaries) + return self + + async def update_summaries(self, summaries: List[CommunitySummary]) -> "KnowledgeGraph": + """ + Replace one or more existing community summaries by ID. + + :param summaries: Summaries to replace. + :return: Self for method chaining. + :raises ValueError: If duplicate IDs are provided or a summary is missing. + """ + duplicate_ids = _duplicate_ids(summaries) + if duplicate_ids: + raise ValueError(f"Cannot update duplicated summary IDs in one request: {duplicate_ids}") + + summary_ids = [summary.id for summary in summaries] + existing_summaries = await self.index.community_summary_kv_storage.get_by_ids(summary_ids) + missing_ids = [ + summary_id + for summary_id, existing in zip(summary_ids, existing_summaries) + if existing is None + ] + if missing_ids: + raise ValueError(f"Cannot update non-existent summaries: {missing_ids}") + await self.index.upsert_summaries(summaries) return self @@ -552,18 +639,19 @@ async def _deduplicate_chunks_by_id(self, chunks: List[Chunk]) -> List[Chunk]: return unique_chunks async def reindex_community(self) -> "KnowledgeGraph": + """ + Run clusterization and community summarization in knowledge graph. + """ if not self.pipeline.community_summarizer: raise ValueError() - await self.index.community_kv_storage.drop() - await self.index.community_summary_kv_storage.drop() - entities = await self.index.graph_backend.get_all_nodes() relations = await self.index.graph_backend.get_all_edges() entities = list( map( lambda node: Entity( + id=node.id, entity_name=node.entity_name, description=node.description, entity_type=node.entity_type, @@ -575,29 +663,124 @@ async def reindex_community(self) -> "KnowledgeGraph": ) communities = await self.pipeline.cluster_graph(entities=entities, relations=relations) - summaries = await self.pipeline.community_summarizer.summarize(communities=communities) + summaries = ( + await self.pipeline.community_summarizer.summarize(communities=communities) # type: ignore + if communities + else [] + ) - await self.index.upsert_communities(communities) + await self.update_entities(entities) + + await self.index.community_kv_storage.drop() + await self.index.community_summary_kv_storage.drop() + + await self.upsert_communities(communities) await self.upsert_summaries(summaries) return self - async def reindex_descriptions(self) -> "KnowledgeGraph": - assert self.pipeline.entity_summarizer is not None + # TODO: add an ability to summarize any of Entity/Relation (or Node/Edge) child types. + async def reindex_descriptions( + self, + summarize_only_more_than: Optional[int] = None, + ) -> "KnowledgeGraph": + """ + Summarize existing entity and relation descriptions that exceed a sentence threshold. + + The method reuses the configured entity/relation summarizers by building + summarizer-compatible DataFrames for only the long-description items. + Items at or below the threshold are left unchanged. + + :param summarize_only_more_than: Summarize descriptions with more than + this many sentences. Defaults to the graph builder setting. + :return: Self for method chaining. + """ + entity_summarizer = self.pipeline.entity_summarizer + relation_summarizer = self.pipeline.relation_summarizer + if entity_summarizer is None or relation_summarizer is None: + raise ValueError("Description reindexing requires entity and relation summarizers.") + + if not entity_summarizer.use_llm_summarization or not relation_summarizer.use_llm_summarization: # type: ignore + raise ValueError("Description reindexing requires LLM summarization to be enabled.") + threshold = ( + summarize_only_more_than + if summarize_only_more_than is not None + else self.builder_settings.summarize_only_if_more_than + ) + + def _description_sentence_count(description: str) -> int: + text = (description or "").strip() + if not text: + return 0 + + # TODO: replace with sentenizer + parts = re.split(r"\n+|(?<=[.!?])\s+", text) + return sum(1 for part in parts if re.sub(r"\s+", " ", part).strip()) + all_entities = await self.index.graph_backend.get_all_nodes() - entities = await self.pipeline.entity_summarizer.run(entities=all_entities) + entities_to_summarize = [ + entity for entity in all_entities + if _description_sentence_count(entity.description) > threshold + ] all_relations = await self.index.graph_backend.get_all_edges() - relations = await self.pipeline.relation_summarizer.run(relations=all_relations) + relations_to_summarize = [ + relation for relation in all_relations + if _description_sentence_count(relation.description) > threshold + ] + + summarized_entities: List[Entity] = [] + if entities_to_summarize: + entity_rows = [ + { + "id": entity.id, + "entity_name": entity.entity_name, + "entity_type": entity.entity_type, + "description": [entity.description], + "source_chunk_id": entity.source_chunk_id, + "documents_id": entity.documents_id, + "clusters": entity.clusters, + "duplicate_count": entity_summarizer.summarize_only_if_more_than + 1, # type: ignore + } + for entity in entities_to_summarize + ] + summarized_entities = await entity_summarizer.summarize_entities(pd.DataFrame(entity_rows)) # type: ignore + + summarized_relations: List[Relation] = [] + if relations_to_summarize: + relation_rows = [ + { + "id": relation.id, + "subject_id": relation.subject_id, + "object_id": relation.object_id, + "subject_name": relation.subject_name, + "object_name": relation.object_name, + "relation_type": relation.relation_type, + "description": relation.description, + "relation_strength": relation.relation_strength, + "source_chunk_id": relation.source_chunk_id, + "duplicate_count": relation_summarizer.summarize_only_if_more_than + 1, # type: ignore + } + for relation in relations_to_summarize + ] + summarized_relations = await relation_summarizer.summarize_relations(pd.DataFrame(relation_rows)) # type: ignore + + if not summarized_entities and not summarized_relations: + return self + + if summarized_entities: + await self.update_entities(summarized_entities) + if summarized_relations: + await self.update_relations(summarized_relations) - await self.index.graph_backend.index_start_callback() - await self.index.graph_backend.delete_nodes([e.id for e in all_entities]) - await self.index.graph_backend.delete_edges([(r.subject_id, r.object_id, r.id) for r in all_relations]) - await self.index.graph_backend.upsert_nodes(entities) - await self.index.graph_backend.upsert_edges(relations) - await self.index.graph_backend.index_done_callback() + return self async def reindex_graph(self) -> "KnowledgeGraph": + """ + Reindex item descriptions and clusters. Useful after upserting a new graph into an existing one. + + Reindexing = summarizing item descriptions + detecting communities and generating their summaries. + """ return await (await self.reindex_descriptions()).reindex_community() async def _reindex_cluster_ids( @@ -689,8 +872,8 @@ async def _reindex_cluster_ids( seen_memberships.add(membership_key) remapped_memberships.append({ - "level": level, - "cluster_id": global_cluster_id, + "level": int(level), + "cluster_id": int(global_cluster_id), }) entity.clusters = remapped_memberships From a04d7574750230887a5a2c249108c91720939abd Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 27 Apr 2026 00:08:02 +0700 Subject: [PATCH 40/42] Update tests --- tests/README.md | 88 +- tests/chunker/test_chunkers.py | 9 +- tests/embedder/test_sparse_embedders.py | 205 +++++ tests/graph/test_community_summarizer.py | 53 ++ tests/graph/test_graph_loading.py | 8 +- tests/graph/test_index_crud.py | 755 ++++++++++++++-- tests/graph/test_knowledge_graph_merge.py | 830 ++++++++++++++++++ tests/graph/test_merge_logic.py | 220 +++-- tests/search_engine/conftest.py | 8 +- .../test_global_search_engine.py | 29 +- .../search_engine/test_local_search_engine.py | 71 +- tests/search_engine/test_mix_search_engine.py | 55 +- .../search_engine/test_naive_search_engine.py | 59 +- tests/storage/conftest.py | 86 ++ tests/storage/qdrant_testkit.py | 357 ++++++++ .../storage/test_backend_batch_operations.py | 14 +- tests/storage/test_qdrant_vdb_sparse_modes.py | 279 ++++++ tests/storage/test_qdrant_vdb_storage.py | 343 ++++++++ tests/storage/test_vdb_contract.py | 152 ++++ 19 files changed, 3307 insertions(+), 314 deletions(-) create mode 100644 tests/embedder/test_sparse_embedders.py create mode 100644 tests/graph/test_community_summarizer.py create mode 100644 tests/graph/test_knowledge_graph_merge.py create mode 100644 tests/storage/conftest.py create mode 100644 tests/storage/qdrant_testkit.py create mode 100644 tests/storage/test_qdrant_vdb_sparse_modes.py create mode 100644 tests/storage/test_qdrant_vdb_storage.py create mode 100644 tests/storage/test_vdb_contract.py diff --git a/tests/README.md b/tests/README.md index ba438ca..3a3393a 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,49 +1,73 @@ # Tests -> Tests were made with Claude Code support. +The test suite is pytest-based and is organized by package area. The default +pytest configuration discovers tests under `tests/` only; root-level ad hoc +scripts such as `test.py` are not part of the normal suite. ## Structure -``` +```text tests/ -├── conftest.py # Shared fixtures (mock embedder, Index factory, etc.) +├── conftest.py # Shared text fixtures and event loop setup ├── chunker/ -│ └── test_chunkers.py # SimpleChunker and SmartChunker splitting logic +│ └── test_chunkers.py # Simple, semantic, and smart chunker behavior ├── common/ -│ ├── test_batch_generator.py # Batch iteration utility -│ ├── test_cache.py # LRU / async cache helpers -│ └── test_env.py # Environment and settings resolution +│ ├── test_batch_generator.py # Batch iteration utility +│ ├── test_cache.py # Disk/cache helpers +│ └── test_env.py # Environment and settings resolution ├── embedder/ -│ └── test_embedders.py # OpenAI and SentenceTransformers embedder wrappers +│ ├── test_embedders.py # Dense embedder wrappers +│ └── test_sparse_embedders.py # BM25/BM42 sparse embedder wrappers ├── graph/ -│ ├── test_artifacts_summarizer.py # Entity and relation summarization / dedup -│ ├── test_builder_modules.py # GraphBuilderModule plugin system -│ ├── test_graph_loading.py # Persistence round-trip for KnowledgeGraph -│ └── test_graph_types.py # Entity / Relation dataclass behaviour +│ ├── test_artifacts_summarizer.py # Entity/relation summarization and deduplication +│ ├── test_builder_modules.py # Graph builder modules +│ ├── test_community_summarizer.py # Community report generation +│ ├── test_graph_loading.py # Loading a serialized graph from storage +│ ├── test_graph_types.py # Entity, relation, and community dataclasses +│ ├── test_index_crud.py # Index CRUD, cascades, vectors, and consistency checks +│ ├── test_knowledge_graph_merge.py # KnowledgeGraph high-level merge/update behavior +│ └── test_merge_logic.py # Pure entity/relation merge helpers +├── llm/ +│ └── test_cached_openai.py # Cached OpenAI client against a local mock server ├── rerank/ -│ ├── test_api_rerankers.py # API-based reranker clients -│ ├── test_base_reranker.py # BaseReranker interface contract -│ └── test_local_rerankers.py # Local model rerankers +│ ├── test_api_rerankers.py # API-based reranker wrappers +│ ├── test_base_reranker.py # Base reranker behavior +│ └── test_local_rerankers.py # Local reranker batching and ordering ├── search_engine/ -│ ├── conftest.py # Fixtures that load kg_for_test/ into a real KnowledgeGraph -│ ├── test_global_search_engine.py # GlobalSearchEngine (community summaries) -│ ├── test_local_search_engine.py # LocalSearchEngine (entity embeddings) -│ └── test_naive_search_engine.py # NaiveSearchEngine (chunk vector search + rerank) +│ ├── conftest.py # Real KnowledgeGraph fixture backed by kg_for_test/ +│ ├── test_global_search_engine.py # Global search behavior +│ ├── test_local_search_engine.py # Local search behavior +│ ├── test_mix_search_engine.py # Multi-engine orchestration +│ └── test_naive_search_engine.py # Chunk/vector based search ├── storage/ -│ ├── test_backend_batch_operations.py # Batch upsert / delete across all backends -│ ├── test_index_crud.py # Index-level CRUD for entities, relations, chunks -│ ├── test_json_storage.py # JsonKVStorage adapter -│ ├── test_merge_logic.py # Entity / relation merge-on-conflict logic -│ ├── test_nano_vdb_storage.py # NanoVectorDBStorage adapter -│ └── test_networkx_adapter.py # NetworkXStorage graph backend +│ ├── conftest.py # Shared vector DB backend contract cases +│ ├── qdrant_testkit.py # In-memory fake Qdrant implementation +│ ├── test_backend_batch_operations.py # Batch graph/KV/vector storage operations +│ ├── test_json_storage.py # JSON KV storage adapter +│ ├── test_memgraph_adapter.py # Optional Memgraph integration tests +│ ├── test_networkx_adapter.py # NetworkX graph storage adapter +│ ├── test_qdrant_vdb_sparse_modes.py # Qdrant sparse/hybrid configuration behavior +│ ├── test_qdrant_vdb_storage.py # Qdrant vector storage edge cases +│ └── test_vdb_contract.py # Shared VDB contract across Nano/Qdrant backends +├── triplet/ +│ └── test_llm_exception_handling.py # LLM extractor fail-fast behavior ├── utils/ -│ ├── test_ragu_utils.py # Hash IDs, file readers, misc helpers -│ └── test_token_truncation.py # Token-aware text truncation -└── kg_for_test/ # Pre-built knowledge graph fixtures (GML, JSON, VDB) +│ ├── test_ragu_utils.py # Hash IDs, async context helpers, file readers +│ └── test_token_truncation.py # Token-aware text truncation +└── kg_for_test/ # Serialized graph fixture for integration-level tests ``` -## Fixtures +## Fixtures And State + +- Tests that need temporary RAGU storage should use `monkeypatch.setattr(Settings, "storage_folder", ...)` so global state is restored after each test. +- `tests/search_engine/conftest.py` loads `tests/kg_for_test/` into a real `KnowledgeGraph` for search-engine integration-style tests. +- `tests/storage/conftest.py` parametrizes shared vector DB contract tests across NanoVDB and fake-Qdrant backends. +- Memgraph tests are marked `integration` and skip unless `MEMGRAPH_URI` and `MEMGRAPH_PASSWORD` are configured. -- `tests/conftest.py` — project-wide fixtures such as mock embedders and temporary `Index` instances. -- `tests/search_engine/conftest.py` — loads `kg_for_test/` files into a real `KnowledgeGraph` (`real_kg`) used by search engine tests. -- `tests/kg_for_test/` — serialized graph, KV, and vector-DB files that provide a deterministic dataset for integration-level tests. +## Useful Commands + +```bash +pytest -q --no-cov +pytest -q --no-cov -m "not slow and not integration" +pytest -q --cov-report=term-missing:skip-covered +``` diff --git a/tests/chunker/test_chunkers.py b/tests/chunker/test_chunkers.py index 9b1d100..c4f0cb6 100644 --- a/tests/chunker/test_chunkers.py +++ b/tests/chunker/test_chunkers.py @@ -52,13 +52,10 @@ def test_overlap_functionality(self, sample_text_medium): chunks = chunker.split(sample_text_medium) if len(chunks) > 1: - # Check that there's some overlap between consecutive chunks + # Check that consecutive chunks carry the configured overlap forward. for i in range(len(chunks) - 1): - chunk1_end = chunks[i].content[-overlap:] if len(chunks[i].content) >= overlap else chunks[i].content - chunk2_start = chunks[i + 1].content[:overlap] - # At least some characters should be similar - assert len(chunk1_end) > 0 - assert len(chunk2_start) > 0 + expected_overlap = chunks[i].content[-overlap:].strip() + assert chunks[i + 1].content.startswith(expected_overlap) def test_empty_string(self): chunker = SimpleChunker(max_chunk_size=100, overlap=0) diff --git a/tests/embedder/test_sparse_embedders.py b/tests/embedder/test_sparse_embedders.py new file mode 100644 index 0000000..806dfda --- /dev/null +++ b/tests/embedder/test_sparse_embedders.py @@ -0,0 +1,205 @@ +import numpy as np +import pytest + +from ragu.models.sparse_embedder import BM25, BM42 + + +class _FakeFastEmbedSparseEmbedding: + def __init__(self, indices, values): + self.indices = np.array(indices) + self.values = np.array(values) + + +class _FakeFastEmbedBM25: + def __init__( + self, + model_name: str, + cache_dir: str | None = None, + k: float = 1.2, + b: float = 0.75, + avg_len: float = 256.0, + language: str = "english", + token_max_length: int = 40, + disable_stemmer: bool = False, + specific_model_path: str | None = None, + **kwargs, + ): + self.model_name = model_name + self.cache_dir = cache_dir + self.k = k + self.b = b + self.avg_len = avg_len + self.language = language + self.token_max_length = token_max_length + self.disable_stemmer = disable_stemmer + self.specific_model_path = specific_model_path + self.kwargs = kwargs + self.embed_calls = [] + self.query_calls = [] + + def embed(self, texts, **kwargs): + self.embed_calls.append((texts, kwargs)) + return [ + _FakeFastEmbedSparseEmbedding(indices=[idx, idx + 10], values=[1.0, 0.5]) + for idx, _ in enumerate(texts, start=1) + ] + + def query_embed(self, texts, **kwargs): + self.query_calls.append((texts, kwargs)) + return [ + _FakeFastEmbedSparseEmbedding(indices=[idx + 100], values=[1.0]) + for idx, _ in enumerate(texts, start=1) + ] + + +class _FakeFastEmbedBM42: + def __init__( + self, + model_name: str, + cache_dir: str | None = None, + threads: int | None = None, + providers=None, + alpha: float = 0.5, + cuda="auto", + device_ids: list[int] | None = None, + lazy_load: bool = False, + specific_model_path: str | None = None, + **kwargs, + ): + self.model_name = model_name + self.cache_dir = cache_dir + self.threads = threads + self.providers = providers + self.alpha = alpha + self.cuda = cuda + self.device_ids = device_ids + self.lazy_load = lazy_load + self.specific_model_path = specific_model_path + self.kwargs = kwargs + self.embed_calls = [] + self.query_calls = [] + + def embed(self, texts, **kwargs): + self.embed_calls.append((texts, kwargs)) + return [ + _FakeFastEmbedSparseEmbedding(indices=[idx + 200], values=[0.7]) + for idx, _ in enumerate(texts, start=1) + ] + + def query_embed(self, texts, **kwargs): + self.query_calls.append((texts, kwargs)) + return [ + _FakeFastEmbedSparseEmbedding(indices=[idx + 300], values=[1.0]) + for idx, _ in enumerate(texts, start=1) + ] + + +class _FakeNormalizer: + def normalize_batch(self, texts: list[str]) -> list[str]: + return [f"norm::{text}" for text in texts] + + +def test_bm25_embed_document_uses_normalizer_and_forwards_init_options(monkeypatch): + monkeypatch.setattr("ragu.models.sparse_embedder.FastEmbedBM25", _FakeFastEmbedBM25) + + bm25 = BM25( + model_name="Qdrant/bm25", + cache_dir="/tmp/cache", + k=1.7, + b=0.25, + avg_len=512.0, + language="russian", + token_max_length=64, + disable_stemmer=True, + specific_model_path="/tmp/model", + normalizer=_FakeNormalizer(), + local_files_only=True, + ) + + embeddings = bm25.embed_document(["hello", "world"]) + + assert [embedding.indices for embedding in embeddings] == [[1, 11], [2, 12]] + assert bm25._model.cache_dir == "/tmp/cache" + assert bm25._model.k == 1.7 + assert bm25._model.b == 0.25 + assert bm25._model.avg_len == 512.0 + assert bm25._model.language == "russian" + assert bm25._model.token_max_length == 64 + assert bm25._model.specific_model_path == "/tmp/model" + assert bm25._model.kwargs == {"local_files_only": True} + assert bm25._model.embed_calls == [(["norm::hello", "norm::world"], {})] + + +def test_bm25_embed_query_uses_query_encoder(monkeypatch): + monkeypatch.setattr("ragu.models.sparse_embedder.FastEmbedBM25", _FakeFastEmbedBM25) + bm25 = BM25(disable_stemmer=True, normalizer=_FakeNormalizer()) + + embeddings = bm25.embed_query(["question"]) + + assert [embedding.indices for embedding in embeddings] == [[101]] + assert bm25._model.query_calls == [(["norm::question"], {})] + + +def test_bm25_rejects_custom_normalizer_with_fastembed_stemmer_enabled(): + with pytest.raises(ValueError, match="disable_stemmer"): + BM25(normalizer=_FakeNormalizer()) + + +def test_bm42_embed_document_forwards_batch_options(monkeypatch): + monkeypatch.setattr("ragu.models.sparse_embedder.FastEmbedBM42", _FakeFastEmbedBM42) + + bm42 = BM42( + cache_dir="/tmp/cache", + alpha=0.7, + batch_size=16, + parallel=3, + threads=2, + providers=["CPUExecutionProvider"], + cuda=False, + device_ids=[0], + lazy_load=True, + specific_model_path="/tmp/bm42", + local_files_only=True, + ) + + embeddings = bm42.embed_document(["alpha", "beta"]) + + assert [embedding.indices for embedding in embeddings] == [[201], [202]] + assert bm42._model.cache_dir == "/tmp/cache" + assert bm42._model.alpha == 0.7 + assert bm42._model.threads == 2 + assert bm42._model.providers == ["CPUExecutionProvider"] + assert bm42._model.cuda is False + assert bm42._model.device_ids == [0] + assert bm42._model.lazy_load is True + assert bm42._model.specific_model_path == "/tmp/bm42" + assert bm42._model.kwargs == {"local_files_only": True} + assert bm42._model.embed_calls == [(["alpha", "beta"], {"batch_size": 16, "parallel": 3})] + + +def test_bm42_embed_query_uses_query_encoder(monkeypatch): + monkeypatch.setattr("ragu.models.sparse_embedder.FastEmbedBM42", _FakeFastEmbedBM42) + bm42 = BM42() + + embeddings = bm42.embed_query(["question"]) + + assert [embedding.indices for embedding in embeddings] == [[301]] + assert bm42._model.query_calls == [(["question"], {})] + + +def test_bm42_rejects_custom_normalizer(): + with pytest.raises(ValueError, match="does not support a custom normalizer"): + BM42(normalizer=_FakeNormalizer()) + + +def test_sparse_embedders_return_empty_lists_for_empty_inputs(monkeypatch): + monkeypatch.setattr("ragu.models.sparse_embedder.FastEmbedBM25", _FakeFastEmbedBM25) + monkeypatch.setattr("ragu.models.sparse_embedder.FastEmbedBM42", _FakeFastEmbedBM42) + + bm25 = BM25(disable_stemmer=True, normalizer=_FakeNormalizer()) + bm42 = BM42() + + assert bm25.embed_document([]) == [] + assert bm25.embed_query([]) == [] + assert bm42.embed_document([]) == [] + assert bm42.embed_query([]) == [] diff --git a/tests/graph/test_community_summarizer.py b/tests/graph/test_community_summarizer.py new file mode 100644 index 0000000..67d9d29 --- /dev/null +++ b/tests/graph/test_community_summarizer.py @@ -0,0 +1,53 @@ +from types import SimpleNamespace +from unittest.mock import AsyncMock + +import pytest + +import ragu.graph.community_summarizer as community_module +from ragu.common.prompts import ChatMessages, UserMessage +from ragu.common.prompts.default_models import CommunityReportModel +from ragu.graph.community_summarizer import CommunitySummarizer +from ragu.graph.types import Community, Entity, Relation + + +@pytest.mark.asyncio +async def test_community_summarizer_llm_exception_propagates(monkeypatch): + llm = AsyncMock() + llm.batch_chat_completion = AsyncMock(side_effect=RuntimeError("boom")) + summarizer = CommunitySummarizer(llm=llm) + + monkeypatch.setattr( + summarizer, + "get_prompt", + lambda _: SimpleNamespace( + messages=[UserMessage(content="{{ community }}")], + pydantic_model=CommunityReportModel, + ), + ) + monkeypatch.setattr( + community_module, + "render", + lambda messages, **kwargs: [ChatMessages.from_messages([UserMessage(content="prompt")])], + ) + + entity = Entity( + entity_name="Alice", + entity_type="Person", + description="A software engineer", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ) + relation = Relation( + subject_id=entity.id, + object_id=entity.id, + subject_name="Alice", + object_name="Alice", + relation_type="SELF", + description="Self reference", + source_chunk_id=["chunk-1"], + ) + community = Community(level=1, cluster_id=1, entities=[entity], relations=[relation]) + + with pytest.raises(RuntimeError, match="boom"): + await summarizer.summarize([community]) diff --git a/tests/graph/test_graph_loading.py b/tests/graph/test_graph_loading.py index 6b933a6..c0608b4 100644 --- a/tests/graph/test_graph_loading.py +++ b/tests/graph/test_graph_loading.py @@ -40,11 +40,11 @@ def example_graph_path(self): return "tests/kg_for_test" @pytest.fixture - def setup_storage_folder(self, example_graph_path): + def setup_storage_folder(self, example_graph_path, monkeypatch): """ Set up storage folder before test. """ - Settings.storage_folder = example_graph_path + monkeypatch.setattr(Settings, "storage_folder", example_graph_path) return example_graph_path @pytest.fixture @@ -152,7 +152,7 @@ async def test_loaded_graph_has_relations( graph_backend = kg.index.graph_backend assert graph_backend is not None - num_edges = len(await graph_backend.get_all_nodes()) + num_edges = len(await graph_backend.get_all_edges()) assert num_edges > 0, "Loaded graph should contain relations" @pytest.mark.asyncio @@ -172,7 +172,7 @@ async def test_loaded_graph_entity_vdb( builder_settings=no_llm_builder_settings, ) - entity_vdb = kg.index.entity_vector_db + entity_vdb = kg.index.nodes_vector_db assert entity_vdb is not None assert hasattr(entity_vdb, '_client') diff --git a/tests/graph/test_index_crud.py b/tests/graph/test_index_crud.py index abe4be6..4141a8d 100644 --- a/tests/graph/test_index_crud.py +++ b/tests/graph/test_index_crud.py @@ -3,11 +3,13 @@ """ import pytest -from ragu.graph.types import Entity, Relation +from ragu.graph.types import Community, Entity, Relation from ragu.chunker.types import Chunk -from ragu.graph.index import Index, StorageArguments +from ragu.graph.graph_retrieve_backend import GraphRetriever +from ragu.graph.index import ConsistencyIssue, ConsistencyReport, Index, StorageArguments from ragu.models.embedder import Embedder -from unittest.mock import AsyncMock +from ragu.storage.types import EmbeddingHit, Point, SparseEmbedding +from unittest.mock import AsyncMock, Mock @pytest.fixture @@ -16,18 +18,51 @@ def mock_embedder(): embedder = AsyncMock(spec=Embedder) embedder.dim = 128 embedder.embed_text = AsyncMock(return_value=[0.1] * 128) + embedder.batch_embed_text = AsyncMock( + side_effect=lambda texts, **kwargs: [[0.1] * 128 for _ in texts] + ) return embedder @pytest.fixture -def index(tmp_path, mock_embedder): +def index(tmp_path, monkeypatch, mock_embedder): """Create an Index instance with temporary storage.""" from ragu.common.global_parameters import Settings - Settings.storage_folder = str(tmp_path / "storage") + monkeypatch.setattr(Settings, "storage_folder", str(tmp_path / "storage")) storage_args = StorageArguments() return Index(embedder=mock_embedder, arguments=storage_args) +@pytest.fixture +def mock_sparse_embedder(): + sparse_embedder = Mock() + sparse_embedder.embed_document.return_value = [ + SparseEmbedding(indices=[1, 2], values=[0.7, 0.3]), + SparseEmbedding(indices=[3, 4], values=[0.6, 0.4]), + ] + sparse_embedder.embed_query.return_value = [ + SparseEmbedding(indices=[9], values=[1.0]), + ] + return sparse_embedder + + +@pytest.fixture +def sparse_index(tmp_path, monkeypatch, mock_embedder, mock_sparse_embedder): + from ragu.common.global_parameters import Settings + monkeypatch.setattr(Settings, "storage_folder", str(tmp_path / "storage")) + storage_args = StorageArguments() + return Index(embedder=mock_embedder, arguments=storage_args, sparse_embedder=mock_sparse_embedder) + + +@pytest.fixture +def sparse_retriever(sparse_index, mock_embedder, mock_sparse_embedder): + return GraphRetriever( + Mock(index=sparse_index), + mock_embedder, + mock_sparse_embedder, + ) + + @pytest.fixture def sample_entities(): """Sample entities for testing.""" @@ -76,18 +111,117 @@ async def test_insert_entities(index, sample_entities): """ Test inserting entities. """ - await index.insert_entities(sample_entities) + await index.upsert_nodes(sample_entities) # Verify entities exist - retrieved = await index.get_entities(["ent-1", "ent-2"]) + retrieved = await index.get_nodes(["ent-1", "ent-2"]) assert len(retrieved) == 2 assert all(e is not None for e in retrieved) + assert [e.id for e in retrieved if e is not None] == ["ent-1", "ent-2"] + assert [e.entity_name for e in retrieved if e is not None] == ["Alice", "Bob"] + assert [e.description for e in retrieved if e is not None] == [ + "A software engineer", + "A data scientist", + ] + + vector_points = await index.nodes_vector_db.get_points_by_ids(["ent-1", "ent-2"]) + assert [point.id for point in vector_points if point is not None] == ["ent-1", "ent-2"] + assert [point.metadata["entity_name"] for point in vector_points if point is not None] == ["Alice", "Bob"] + + +@pytest.mark.asyncio +async def test_insert_entities_does_not_touch_graph_when_vectorization_fails(index, mock_embedder, sample_entities): + mock_embedder.batch_embed_text = AsyncMock( + side_effect=RuntimeError("embed failed") + ) + + with pytest.raises(RuntimeError, match="embed failed"): + await index.upsert_nodes(sample_entities) + + retrieved = await index.get_nodes(["ent-1", "ent-2"]) + assert retrieved == [None, None] + + +@pytest.mark.asyncio +async def test_build_query_vectors_returns_dense_and_sparse_payload( + sparse_retriever, + mock_embedder, + mock_sparse_embedder, +): + point = await sparse_retriever.build_query_vectors("alpha beta") + + assert point.dense_embedding == [0.1] * 128 + assert point.sparse_embedding is not None + assert point.sparse_embedding.indices == [9] + mock_embedder.embed_text.assert_awaited_once_with("alpha beta") + mock_sparse_embedder.embed_query.assert_called_once_with(["alpha beta"]) + + +@pytest.mark.asyncio +async def test_insert_entities_passes_sparse_embeddings_to_vector_db(sparse_index, sample_entities, mock_sparse_embedder): + sparse_index.nodes_vector_db.upsert = AsyncMock() + + await sparse_index.upsert_nodes(sample_entities) + + sparse_index.nodes_vector_db.upsert.assert_awaited_once() + args, _ = sparse_index.nodes_vector_db.upsert.await_args + points = args[0] + assert [point.sparse_embedding.indices for point in points if point.sparse_embedding is not None] == [[1, 2], [3, 4]] + mock_sparse_embedder.embed_document.assert_called_once() + + +@pytest.mark.asyncio +async def test_query_entities_passes_sparse_query_to_vector_db(sparse_index, sparse_retriever, sample_entities, mock_sparse_embedder): + await sparse_index.upsert_nodes(sample_entities) + sparse_index.nodes_vector_db.query = AsyncMock( + return_value=[EmbeddingHit(id="ent-1", distance=0.9)] + ) + + entities, hits = await sparse_retriever.query_entities("alice engineer", top_k=3) + + assert [entity.id for entity in entities] == ["ent-1"] + assert [hit.id for hit in hits] == ["ent-1"] + args, kwargs = sparse_index.nodes_vector_db.query.await_args + point = args[0] + assert point.sparse_embedding is not None + assert point.sparse_embedding.indices == [9] + assert kwargs["top_k"] == 3 + mock_sparse_embedder.embed_query.assert_called_once_with(["alice engineer"]) @pytest.mark.asyncio -async def test_upsert_entities_with_merge(index): +async def test_query_chunk_hits_passes_sparse_query_to_vector_db(sparse_index, sparse_retriever, mock_sparse_embedder): + sparse_index.chunks_vector_db.query = AsyncMock( + return_value=[EmbeddingHit(id="chunk-1", distance=0.8, metadata={"doc_id": "doc-1"})] + ) + sparse_index.chunks_kv_storage.get_by_ids = AsyncMock( + return_value=[ + { + "content": "chunk one", + "chunk_order_idx": 0, + "doc_id": "doc-1", + "num_tokens": 3, + } + ] + ) + + chunks, hits = await sparse_retriever.query_chunks("hybrid chunk query", top_k=2) + + assert [chunk.id for chunk in chunks] == ["chunk-1"] + assert [hit.id for hit in hits] == ["chunk-1"] + _, kwargs = sparse_index.chunks_vector_db.query.await_args + point = kwargs["point"] + assert point.sparse_embedding is not None + assert point.sparse_embedding.indices == [9] + assert kwargs["top_k"] == 2 + sparse_index.chunks_kv_storage.get_by_ids.assert_awaited_once_with(["chunk-1"]) + mock_sparse_embedder.embed_query.assert_called_once_with(["hybrid chunk query"]) + + +@pytest.mark.asyncio +async def test_insert_entities_replaces_existing_payload(index): """ - Test upserting duplicate entities merges them. + Test storage-level insert replaces existing entity payload by ID. """ entity1 = Entity( entity_name="Alice", @@ -107,20 +241,20 @@ async def test_upsert_entities_with_merge(index): ) # First insert - await index.insert_entities([entity1]) + await index.upsert_nodes([entity1]) # Second insert with duplicate - await index.insert_entities([entity2]) + await index.upsert_nodes([entity2]) - # Should have merged - only one Alice entity - retrieved = await index.get_entities([entity1.id]) + # Should still have one Alice entity, using the latest payload. + retrieved = await index.get_nodes([entity1.id]) non_null = [e for e in retrieved if e is not None] assert len(non_null) == 1 - # Merged entity should have both descriptions - merged = non_null[0] - assert "First description" in merged.description - assert "Second description" in merged.description + stored = non_null[0] + assert stored.description == "Second description" + assert stored.source_chunk_id == ["chunk-2"] + assert stored.documents_id == ["doc-2"] @pytest.mark.asyncio @@ -128,13 +262,24 @@ async def test_insert_relations(index, sample_entities, sample_relations): """ Test inserting relations. """ - await index.insert_entities(sample_entities) - await index.insert_relations(sample_relations) + await index.upsert_nodes(sample_entities) + await index.upsert_edges(sample_relations) relation = sample_relations[0] - retrieved = await index.get_relations([(relation.subject_id, relation.object_id, relation.id)]) + retrieved = await index.get_edges([(relation.subject_id, relation.object_id, relation.id)]) assert len(retrieved) == 1 assert retrieved[0] is not None + assert retrieved[0].id == relation.id + assert retrieved[0].subject_id == "ent-1" + assert retrieved[0].object_id == "ent-2" + assert retrieved[0].relation_type == "KNOWS" + assert retrieved[0].description == "Alice knows Bob" + + vector_points = await index.edges_vector_db.get_points_by_ids([relation.id]) + assert vector_points[0] is not None + assert vector_points[0].id == relation.id + assert vector_points[0].metadata["subject_id"] == "ent-1" + assert vector_points[0].metadata["object_id"] == "ent-2" @pytest.mark.asyncio @@ -142,8 +287,28 @@ async def test_upsert_relations_validates_entities(index, sample_relations): """ Test that upserting relations validates entity existence. """ - with pytest.raises(ValueError, match="non-existent entities"): - await index.insert_relations(sample_relations) + with pytest.raises(ValueError, match="non-existent nodes"): + await index.upsert_edges(sample_relations) + + +@pytest.mark.asyncio +async def test_insert_relations_does_not_touch_graph_when_vectorization_fails( + index, + mock_embedder, + sample_entities, + sample_relations, +): + await index.upsert_nodes(sample_entities) + mock_embedder.batch_embed_text = AsyncMock( + side_effect=RuntimeError("embed failed") + ) + + with pytest.raises(RuntimeError, match="embed failed"): + await index.upsert_edges(sample_relations) + + relation = sample_relations[0] + retrieved = await index.get_edges([(relation.subject_id, relation.object_id, relation.id)]) + assert retrieved == [None] @pytest.mark.asyncio @@ -151,10 +316,10 @@ async def test_delete_entities(index, sample_entities): """ Test deleting entities. """ - await index.insert_entities(sample_entities) - await index.delete_entities(["ent-1"]) + await index.upsert_nodes(sample_entities) + await index.delete_nodes(["ent-1"]) - retrieved = await index.get_entities(["ent-1", "ent-2"]) + retrieved = await index.get_nodes(["ent-1", "ent-2"]) assert retrieved[0] is None assert retrieved[1] is not None @@ -164,13 +329,13 @@ async def test_delete_entities_cascade(index, sample_entities, sample_relations) """ Test that deleting entities cascades to relations. """ - await index.insert_entities(sample_entities) - await index.insert_relations(sample_relations) + await index.upsert_nodes(sample_entities) + await index.upsert_edges(sample_relations) - await index.delete_entities(["ent-1"]) + await index.delete_nodes(["ent-1"]) relation = sample_relations[0] - retrieved_relations = await index.get_relations([(relation.subject_id, relation.object_id, relation.id)]) + retrieved_relations = await index.get_edges([(relation.subject_id, relation.object_id, relation.id)]) assert retrieved_relations[0] is None @@ -179,22 +344,98 @@ async def test_delete_relations(index, sample_entities, sample_relations): """ Test deleting relations. """ - await index.insert_entities(sample_entities) - await index.insert_relations(sample_relations) + await index.upsert_nodes(sample_entities) + await index.upsert_edges(sample_relations) relation = sample_relations[0] - await index.delete_relations([(relation.subject_id, relation.object_id, relation.id)]) + await index.delete_edges([(relation.subject_id, relation.object_id, relation.id)]) - retrieved = await index.get_relations([(relation.subject_id, relation.object_id, relation.id)]) + retrieved = await index.get_edges([(relation.subject_id, relation.object_id, relation.id)]) assert retrieved[0] is None - entities = await index.get_entities(["ent-1", "ent-2"]) + entities = await index.get_nodes(["ent-1", "ent-2"]) assert all(e is not None for e in entities) +@pytest.mark.asyncio +async def test_get_relations_preserves_stored_relation_id(index, sample_entities): + await index.upsert_nodes(sample_entities) + + relation = Relation( + id="rel-custom", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Alice knows Bob", + relation_strength=1.0, + source_chunk_id=["chunk-1"], + ) + + await index.upsert_edges([relation]) + + retrieved = await index.get_edges([("ent-1", "ent-2", "rel-custom")]) + assert retrieved[0] is not None + assert retrieved[0].id == "rel-custom" + + +@pytest.mark.asyncio +async def test_delete_relations_removes_relation_vector(index, sample_entities): + await index.upsert_nodes(sample_entities) + + relation = Relation( + id="rel-custom", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Alice knows Bob", + relation_strength=1.0, + source_chunk_id=["chunk-1"], + ) + + await index.upsert_edges([relation]) + before_delete = await index.edges_vector_db.query(Point(dense_embedding=[0.1] * 128), top_k=10) + assert any(hit.id == "rel-custom" for hit in before_delete) + + await index.delete_edges([("ent-1", "ent-2", "rel-custom")]) + + after_delete = await index.edges_vector_db.query(Point(dense_embedding=[0.1] * 128), top_k=10) + assert all(hit.id != "rel-custom" for hit in after_delete) + + +@pytest.mark.asyncio +async def test_query_relations_returns_vector_hits(sparse_retriever, sparse_index, sample_entities, sample_relations): + await sparse_index.upsert_nodes(sample_entities) + await sparse_index.upsert_edges(sample_relations) + + relations, hits = await sparse_retriever.query_relations("Alice knows Bob", top_k=5) + + assert [relation.id for relation in relations] == ["rel-1"] + assert [hit.id for hit in hits] == ["rel-1"] + + +@pytest.mark.asyncio +async def test_query_relations_skips_hits_without_endpoint_metadata(index, sample_entities, sample_relations, mock_embedder): + await index.upsert_nodes(sample_entities) + await index.upsert_edges(sample_relations) + + retriever = GraphRetriever(Mock(index=index), mock_embedder) + index.edges_vector_db.query = AsyncMock( + return_value=[EmbeddingHit(id="rel-1", distance=0.9, metadata={"content": "Alice knows Bob"})] + ) + + relations, hits = await retriever.query_relations("Alice knows Bob", top_k=5) + + assert relations == [] + assert hits == [] + + @pytest.mark.asyncio async def test_upsert_relations_keeps_non_duplicate_when_duplicates_exist(index, sample_entities): - await index.insert_entities(sample_entities) + await index.upsert_nodes(sample_entities) rel_existing = Relation( id="rel-dup", @@ -207,7 +448,7 @@ async def test_upsert_relations_keeps_non_duplicate_when_duplicates_exist(index, relation_strength=1.0, source_chunk_id=["chunk-1"], ) - await index.insert_relations([rel_existing]) + await index.upsert_edges([rel_existing]) rel_duplicate_update = Relation( id="rel-dup", @@ -232,9 +473,9 @@ async def test_upsert_relations_keeps_non_duplicate_when_duplicates_exist(index, source_chunk_id=["chunk-2"], ) - await index.insert_relations([rel_duplicate_update, rel_unique_new]) + await index.upsert_edges([rel_duplicate_update, rel_unique_new]) - got = await index.get_relations( + got = await index.get_edges( [ ("ent-1", "ent-2", "rel-dup"), ("ent-2", "ent-1", "rel-new"), @@ -242,6 +483,11 @@ async def test_upsert_relations_keeps_non_duplicate_when_duplicates_exist(index, ) assert got[0] is not None assert got[1] is not None + assert got[0].description == "new" + assert got[0].source_chunk_id == ["chunk-2"] + assert got[1].description == "fresh" + assert got[1].subject_id == "ent-2" + assert got[1].object_id == "ent-1" @pytest.mark.asyncio @@ -265,10 +511,10 @@ async def test_update_entities_replaces_existing_payload(index): clusters=[{"level": 2, "cluster_id": "2"}], ) - await index.insert_entities([original]) - await index.update_entities([updated]) + await index.upsert_nodes([original]) + await index.update_nodes([updated]) - got = await index.get_entities([original.id]) + got = await index.get_nodes([original.id]) assert got[0] is not None assert got[0].description == "new description" assert got[0].entity_name == "Alice Updated" @@ -289,8 +535,8 @@ async def test_update_entities_fails_for_missing_id(index): clusters=[], ) - with pytest.raises(ValueError, match="non-existent entities"): - await index.update_entities([missing]) + with pytest.raises(ValueError, match="non-existent nodes"): + await index.update_nodes([missing]) @pytest.mark.asyncio @@ -300,7 +546,7 @@ async def test_update_relations_replaces_existing_payload(index): Entity(id="ent-b", entity_name="B", entity_type="Node", description="B", source_chunk_id=["chunk-b"]), Entity(id="ent-c", entity_name="C", entity_type="Node", description="C", source_chunk_id=["chunk-c"]), ] - await index.insert_entities(entities) + await index.upsert_nodes(entities) original = Relation( id="rel-update", @@ -315,32 +561,30 @@ async def test_update_relations_replaces_existing_payload(index): ) updated = Relation( id="rel-update", - subject_id="ent-b", - object_id="ent-c", - subject_name="B", - object_name="C", + subject_id="ent-a", + object_id="ent-b", + subject_name="A", + object_name="B", relation_type="LINKS", description="new relation", relation_strength=7.0, source_chunk_id=["chunk-new"], ) - await index.insert_relations([original]) - await index.update_relations([updated]) + await index.upsert_edges([original]) + await index.update_edges([updated]) - old_edge = await index.get_relations([("ent-a", "ent-b", "rel-update")]) - new_edge = await index.get_relations([("ent-b", "ent-c", "rel-update")]) + updated_edge = await index.get_edges([("ent-a", "ent-b", "rel-update")]) - assert old_edge[0] is None - assert new_edge[0] is not None - assert new_edge[0].description == "new relation" - assert new_edge[0].relation_strength == 7.0 - assert new_edge[0].source_chunk_id == ["chunk-new"] + assert updated_edge[0] is not None + assert updated_edge[0].description == "new relation" + assert updated_edge[0].relation_strength == 7.0 + assert updated_edge[0].source_chunk_id == ["chunk-new"] @pytest.mark.asyncio async def test_update_relations_fails_for_missing_id(index, sample_entities): - await index.insert_entities(sample_entities) + await index.upsert_nodes(sample_entities) missing_relation = Relation( id="rel-missing", @@ -354,8 +598,8 @@ async def test_update_relations_fails_for_missing_id(index, sample_entities): source_chunk_id=["chunk-x"], ) - with pytest.raises(ValueError, match="non-existent relations"): - await index.update_relations([missing_relation]) + with pytest.raises(ValueError, match="non-existent edges"): + await index.update_edges([missing_relation]) @pytest.mark.asyncio @@ -373,6 +617,15 @@ async def test_upsert_chunks(index): retrieved = await index.get_chunks([chunk1.id, chunk2.id]) assert len(retrieved) == 2 assert all(c is not None for c in retrieved) + assert [chunk.id for chunk in retrieved if chunk is not None] == [chunk1.id, chunk2.id] + assert [chunk.content for chunk in retrieved if chunk is not None] == ["Some text", "More text"] + assert [chunk.chunk_order_idx for chunk in retrieved if chunk is not None] == [0, 1] + assert [chunk.doc_id for chunk in retrieved if chunk is not None] == ["doc-1", "doc-1"] + + vector_points = await index.chunks_vector_db.get_points_by_ids([chunk1.id, chunk2.id]) + assert [point.id for point in vector_points if point is not None] == [chunk1.id, chunk2.id] + assert [point.metadata["content"] for point in vector_points if point is not None] == ["Some text", "More text"] + assert [point.metadata["doc_id"] for point in vector_points if point is not None] == ["doc-1", "doc-1"] @pytest.mark.asyncio @@ -439,8 +692,8 @@ async def test_delete_chunks_cascade(index): ), ] - await index.insert_entities(entities) - await index.insert_relations(relations) + await index.upsert_nodes(entities) + await index.upsert_edges(relations) await index.upsert_chunks([chunk1, chunk2, chunk3]) await index.delete_chunks([chunk1.id]) @@ -450,12 +703,12 @@ async def test_delete_chunks_cascade(index): assert chunks_after_delete[1] is not None assert chunks_after_delete[2] is not None - entities_after_delete = await index.get_entities(["ent-1", "ent-2", "ent-3"]) + entities_after_delete = await index.get_nodes(["ent-1", "ent-2", "ent-3"]) assert entities_after_delete[0] is None assert entities_after_delete[1] is not None assert entities_after_delete[2] is not None - relations_after_delete = await index.get_relations( + relations_after_delete = await index.get_edges( [ ("ent-1", "ent-2", "rel-1"), ("ent-2", "ent-3", "rel-2"), @@ -463,3 +716,377 @@ async def test_delete_chunks_cascade(index): ) assert relations_after_delete[0] is None assert relations_after_delete[1] is not None + + +@pytest.mark.asyncio +async def test_delete_chunks_removes_relations_tied_only_to_deleted_chunk(index): + chunk_entity_a = Chunk(content="Chunk entity A", chunk_order_idx=0, doc_id="doc-1") + chunk_entity_b = Chunk(content="Chunk entity B", chunk_order_idx=1, doc_id="doc-1") + chunk_relation = Chunk(content="Chunk relation only", chunk_order_idx=2, doc_id="doc-1") + + entities = [ + Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=[chunk_entity_a.id], + documents_id=["doc-1"], + clusters=[], + ), + Entity( + id="ent-2", + entity_name="Bob", + entity_type="Person", + description="Bob", + source_chunk_id=[chunk_entity_b.id], + documents_id=["doc-1"], + clusters=[], + ), + ] + relation = Relation( + id="rel-1", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Alice knows Bob", + relation_strength=1.0, + source_chunk_id=[chunk_relation.id], + ) + + await index.upsert_nodes(entities) + await index.upsert_edges([relation]) + await index.upsert_chunks([chunk_entity_a, chunk_entity_b, chunk_relation]) + + await index.delete_chunks([chunk_relation.id]) + + entities_after_delete = await index.get_nodes(["ent-1", "ent-2"]) + assert entities_after_delete[0] is not None + assert entities_after_delete[1] is not None + + relations_after_delete = await index.get_edges([("ent-1", "ent-2", "rel-1")]) + assert relations_after_delete[0] is None + + +@pytest.mark.asyncio +async def test_check_consistency_reports_clean_graph(index): + chunk = Chunk(content="Chunk one text", chunk_order_idx=0, doc_id="doc-1") + await index.upsert_chunks([chunk]) + await index.upsert_nodes([ + Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=[chunk.id], + documents_id=["doc-1"], + clusters=[], + ), + Entity( + id="ent-2", + entity_name="Bob", + entity_type="Person", + description="Bob", + source_chunk_id=[chunk.id], + documents_id=["doc-1"], + clusters=[], + ), + ]) + await index.upsert_edges([ + Relation( + id="rel-1", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Alice knows Bob", + relation_strength=1.0, + source_chunk_id=[chunk.id], + ) + ]) + await index.upsert_communities([ + Community( + id="com-1", + level=0, + cluster_id=1, + entities=[ + Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=[chunk.id], + ) + ], + relations=[ + Relation( + id="rel-1", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Alice knows Bob", + source_chunk_id=[chunk.id], + ) + ], + ) + ]) + + report = await index.check_consistency() + + assert report.is_consistent + assert report.errors == [] + + +@pytest.mark.asyncio +async def test_check_consistency_reports_missing_relation_endpoint(index): + await index.upsert_chunks([Chunk(content="Chunk one text", chunk_order_idx=0, doc_id="doc-1")]) + await index.upsert_nodes([ + Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=[], + documents_id=["doc-1"], + clusters=[], + ) + ]) + + bad_relation = Relation( + id="rel-1", + subject_id="ent-1", + object_id="ent-missing", + subject_name="Alice", + object_name="Ghost", + relation_type="KNOWS", + description="Alice knows Ghost", + relation_strength=1.0, + source_chunk_id=[], + ) + index.graph_backend.get_all_edges = AsyncMock(return_value=[bad_relation]) + + report = await index.check_consistency() + relation_issue = next(issue for issue in report.errors if issue.check == "relation_endpoints") + + assert not report.is_consistent + assert any(issue.check == "relation_endpoints" for issue in report.errors) + assert relation_issue.details["missing_entity_ids"] == ["ent-missing"] + assert relation_issue.details["relation_ids_with_empty_endpoints"] == ["rel-1"] + + +@pytest.mark.asyncio +async def test_check_consistency_reports_missing_chunk_and_community_references(index): + await index.graph_backend.upsert_nodes([ + Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=["chunk-missing"], + documents_id=["doc-1"], + clusters=[], + ) + ]) + await index.graph_backend.index_done_callback() + await index.community_kv_storage.upsert({ + "com-1": { + "level": 0, + "cluster_id": 1, + "entity_ids": ["ent-1", "ent-missing"], + "relation_ids": ["rel-missing"], + } + }) + await index.community_kv_storage.index_done_callback() + + report = await index.check_consistency() + + assert not report.is_consistent + assert any(issue.check == "source_chunk_references" for issue in report.errors) + assert any(issue.check == "community_references" for issue in report.errors) + + +@pytest.mark.asyncio +async def test_check_consistency_reports_relation_vector_endpoint_issues(index): + await index.upsert_chunks([Chunk(content="Chunk one text", chunk_order_idx=0, doc_id="doc-1")]) + await index.upsert_nodes([ + Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=[], + documents_id=["doc-1"], + clusters=[], + ), + Entity( + id="ent-2", + entity_name="Bob", + entity_type="Person", + description="Bob", + source_chunk_id=[], + documents_id=["doc-1"], + clusters=[], + ), + ]) + await index.upsert_edges([ + Relation( + id="rel-1", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Alice knows Bob", + relation_strength=1.0, + source_chunk_id=[], + ) + ]) + await index.edges_vector_db.upsert([ + Point( + id="rel-bad", + dense_embedding=[0.1] * 128, + metadata={"content": "broken relation"}, + ) + ]) + await index.edges_vector_db.index_done_callback() + + report = await index.check_consistency() + + assert not report.is_consistent + assert any(issue.check == "relation_vector_endpoints" for issue in report.errors) + + +@pytest.mark.asyncio +async def test_check_consistency_reports_entity_and_chunk_vector_endpoint_issues(index): + chunk = Chunk(content="Chunk one text", chunk_order_idx=0, doc_id="doc-1") + await index.upsert_chunks([chunk]) + await index.upsert_nodes([ + Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=[chunk.id], + documents_id=["doc-1"], + clusters=[], + ) + ]) + existing_relations = await index.graph_backend.get_all_edges() + index.graph_backend.get_all_edges = AsyncMock(return_value=[ + *existing_relations, + Relation( + id="rel-bad-endpoint", + subject_id="ent-bad", + object_id="ent-1", + subject_name="Ghost", + object_name="Alice", + relation_type="KNOWS", + description="Ghost knows Alice", + relation_strength=1.0, + source_chunk_id=[], + ) + ]) + + await index.nodes_vector_db.upsert([ + Point( + id="ent-bad", + dense_embedding=[0.1] * 128, + metadata={"content": "broken entity"}, + ) + ]) + await index.nodes_vector_db.index_done_callback() + await index.chunks_vector_db.upsert([ + Point( + id="chunk-bad", + dense_embedding=[0.1] * 128, + metadata={"content": "broken chunk", "doc_id": "doc-1"}, + ) + ]) + await index.chunks_vector_db.index_done_callback() + + report = await index.check_consistency() + entity_vector_issue = next(issue for issue in report.errors if issue.check == "entity_vector_endpoints") + relation_issue = next(issue for issue in report.errors if issue.check == "relation_endpoints") + + assert not report.is_consistent + assert any(issue.check == "relation_endpoints" for issue in report.errors) + assert any(issue.check == "entity_vector_endpoints" for issue in report.errors) + assert any(issue.check == "chunk_vector_endpoints" for issue in report.errors) + assert relation_issue.details["missing_entity_ids"] == ["ent-bad"] + assert relation_issue.details["relation_ids_with_empty_endpoints"] == ["rel-bad-endpoint"] + assert entity_vector_issue.details["orphan_vector_ids"] == ["ent-bad"] + + +@pytest.mark.asyncio +async def test_check_consistency_reports_missing_vector_representations(index): + chunk = Chunk(content="Chunk one text", chunk_order_idx=0, doc_id="doc-1") + await index.upsert_chunks([chunk]) + await index.upsert_nodes([ + Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=[chunk.id], + documents_id=["doc-1"], + clusters=[], + ), + Entity( + id="ent-2", + entity_name="Bob", + entity_type="Person", + description="Bob", + source_chunk_id=[chunk.id], + documents_id=["doc-1"], + clusters=[], + ), + ]) + await index.upsert_edges([ + Relation( + id="rel-1", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Alice knows Bob", + relation_strength=1.0, + source_chunk_id=[chunk.id], + ) + ]) + + await index.nodes_vector_db.delete(["ent-1"]) + await index.nodes_vector_db.index_done_callback() + await index.edges_vector_db.delete(["rel-1"]) + await index.edges_vector_db.index_done_callback() + await index.chunks_vector_db.delete([chunk.id]) + await index.chunks_vector_db.index_done_callback() + + report = await index.check_consistency() + + assert not report.is_consistent + assert any(issue.check == "entity_vector_representations" for issue in report.errors) + assert any(issue.check == "relation_vector_representations" for issue in report.errors) + assert any(issue.check == "chunk_vector_representations" for issue in report.errors) + + +def test_consistency_report_str(): + report = ConsistencyReport(errors=[ + ConsistencyIssue( + check="relation_endpoints", + message="Relations reference entity endpoints that do not exist in the graph.", + details={"missing_entity_ids": ["ent-missing"]}, + ) + ]) + + rendered = str(report) + + assert "Graph consistency: FAILED" in rendered + assert "Issues found: 1" in rendered + assert "- relation_endpoints: Relations reference entity endpoints that do not exist in the graph." in rendered + assert "missing_entity_ids: ent-missing" in rendered diff --git a/tests/graph/test_knowledge_graph_merge.py b/tests/graph/test_knowledge_graph_merge.py new file mode 100644 index 0000000..6205005 --- /dev/null +++ b/tests/graph/test_knowledge_graph_merge.py @@ -0,0 +1,830 @@ +""" +Tests for KnowledgeGraph high-level merge behavior. +""" + +from unittest.mock import AsyncMock + +import pytest + +from ragu.chunker.types import Chunk +from ragu.common.global_parameters import Settings +from ragu.common.prompts.default_models import EntityDescriptionModel, RelationDescriptionModel +from ragu.graph.graph_builder_pipeline import BuilderArguments +from ragu.graph.knowledge_graph import KnowledgeGraph +from ragu.graph.types import Community, CommunitySummary, Entity, Relation +from ragu.models.embedder import Embedder + + +@pytest.fixture +def mock_embedder(): + embedder = AsyncMock(spec=Embedder) + embedder.dim = 128 + embedder.embed_text = AsyncMock(return_value=[0.1] * 128) + embedder.batch_embed_text = AsyncMock( + side_effect=lambda texts, **kwargs: [[0.1] * 128 for _ in texts] + ) + return embedder + + +@pytest.fixture +def builder_settings(): + return BuilderArguments( + use_llm_summarization=False, + make_community_summary=False, + remove_isolated_nodes=False, + ) + + +@pytest.fixture +def kg(tmp_path, monkeypatch, mock_embedder, builder_settings): + monkeypatch.setattr(Settings, "storage_folder", str(tmp_path / "storage")) + return KnowledgeGraph( + llm=None, + embedder=mock_embedder, + builder_settings=builder_settings, + ) + + +@pytest.mark.asyncio +async def test_insert_entities_merges_with_existing_entity(kg): + original = Entity( + entity_name="Alice", + entity_type="Person", + description="First description", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ) + incoming = Entity( + entity_name="Alice", + entity_type="Person", + description="Second description", + source_chunk_id=["chunk-2"], + documents_id=["doc-2"], + clusters=[], + ) + + await kg.upsert_entities([original]) + await kg.upsert_entities([incoming]) + + stored = await kg.index.get_nodes([original.id]) + + assert stored[0] is not None + assert "First description" in stored[0].description + assert "Second description" in stored[0].description + assert set(stored[0].source_chunk_id) == {"chunk-1", "chunk-2"} + assert set(stored[0].documents_id) == {"doc-1", "doc-2"} + + +@pytest.mark.asyncio +async def test_reindex_descriptions_summarizes_only_long_descriptions(tmp_path, monkeypatch, mock_embedder): + monkeypatch.setattr(Settings, "storage_folder", str(tmp_path / "storage")) + llm = AsyncMock() + + async def batch_chat_completion(conversations, output_schema, **kwargs): + if output_schema is EntityDescriptionModel: + return [ + EntityDescriptionModel(entity_name="Alice", description="Summarized Alice"), + ] + if output_schema is RelationDescriptionModel: + return [ + RelationDescriptionModel( + subject_name="Alice", + object_name="Bob", + description="Summarized relation", + ), + ] + raise AssertionError(f"Unexpected output schema: {output_schema}") + + llm.batch_chat_completion = AsyncMock(side_effect=batch_chat_completion) + kg = KnowledgeGraph( + llm=llm, + embedder=mock_embedder, + builder_settings=BuilderArguments( + use_llm_summarization=True, + make_community_summary=False, + remove_isolated_nodes=False, + ), + ) + + long_entity = Entity( + id="ent-alice", + entity_name="Alice", + entity_type="Person", + description="Alice is an engineer. She works on graphs.", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[{"level": 1, "cluster_id": 7}], + ) + short_entity = Entity( + id="ent-bob", + entity_name="Bob", + entity_type="Person", + description="Bob is a scientist.", + source_chunk_id=["chunk-2"], + documents_id=["doc-2"], + ) + long_relation = Relation( + id="rel-alice-bob", + subject_id=long_entity.id, + object_id=short_entity.id, + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Alice knows Bob. They work together.", + relation_strength=0.8, + source_chunk_id=["chunk-1", "chunk-2"], + ) + short_relation = Relation( + id="rel-bob-alice", + subject_id=short_entity.id, + object_id=long_entity.id, + subject_name="Bob", + object_name="Alice", + relation_type="KNOWS", + description="Bob knows Alice.", + relation_strength=0.4, + source_chunk_id=["chunk-2"], + ) + + await kg.upsert_entities([long_entity, short_entity]) + await kg.upsert_relations([long_relation, short_relation]) + + result = await kg.reindex_descriptions(summarize_only_more_than=1) + + assert result is kg + stored_entities = await kg.index.get_nodes([long_entity.id, short_entity.id]) + assert stored_entities[0] is not None + assert stored_entities[1] is not None + assert stored_entities[0].description == "Summarized Alice" + assert stored_entities[0].clusters == [{"level": 1, "cluster_id": 7}] + assert stored_entities[1].description == "Bob is a scientist." + + stored_relations = await kg.index.get_edges([ + (long_relation.subject_id, long_relation.object_id, long_relation.id), + (short_relation.subject_id, short_relation.object_id, short_relation.id), + ]) + assert stored_relations[0] is not None + assert stored_relations[1] is not None + assert stored_relations[0].description == "Summarized relation" + assert stored_relations[0].relation_strength == pytest.approx(0.8) + assert stored_relations[1].description == "Bob knows Alice." + + entity_payloads = await kg.index.nodes_vector_db.get_payloads_by_ids([ + long_entity.id, + short_entity.id, + ]) + relation_payloads = await kg.index.edges_vector_db.get_payloads_by_ids([ + long_relation.id, + short_relation.id, + ]) + + assert entity_payloads[0]["description"] == "Summarized Alice" + assert entity_payloads[0]["clusters"] == [{"level": 1, "cluster_id": 7}] + assert entity_payloads[1]["description"] == "Bob is a scientist." + assert relation_payloads[0]["description"] == "Summarized relation" + assert relation_payloads[1]["description"] == "Bob knows Alice." + + assert llm.batch_chat_completion.await_count == 2 + + +@pytest.mark.asyncio +async def test_reindex_community_replaces_stale_records_and_persists_clusters_to_vectors(kg): + alice = Entity( + id="ent-alice", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[{"level": 99, "cluster_id": 99}], + ) + bob = Entity( + id="ent-bob", + entity_name="Bob", + entity_type="Person", + description="Bob", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ) + relation = Relation( + id="rel-alice-bob", + subject_id=alice.id, + object_id=bob.id, + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Alice knows Bob.", + source_chunk_id=["chunk-1"], + ) + + async def summarize(communities): + return [ + CommunitySummary(id=community.id, summary=f"Summary for {community.id}") + for community in communities + ] + + kg.pipeline.community_summarizer.summarize = AsyncMock(side_effect=summarize) + + await kg.upsert_entities([alice, bob]) + await kg.upsert_relations([relation]) + await kg.upsert_communities([ + Community( + id="com-stale", + level=0, + cluster_id=99, + entities=[alice], + relations=[], + ) + ]) + await kg.upsert_summaries([ + CommunitySummary(id="com-stale", summary="Stale summary") + ]) + + await kg.reindex_community() + + stored_entities = await kg.get_entities([alice.id, bob.id]) + stale_communities = await kg.get_communities(["com-stale"]) + stale_summaries = await kg.get_summaries(["com-stale"]) + + assert stored_entities[0] is not None + assert stored_entities[1] is not None + assert stored_entities[0].clusters + assert stored_entities[1].clusters + assert stored_entities[0].clusters != [{"level": 99, "cluster_id": 99}] + assert stale_communities[0] is None + assert stale_summaries[0] is None + + community_id = Community( + level=stored_entities[0].clusters[0]["level"], + cluster_id=stored_entities[0].clusters[0]["cluster_id"], + entities=[], + relations=[], + ).id + communities = await kg.get_communities([community_id]) + summaries = await kg.get_summaries([community_id]) + + assert communities[0] is not None + assert summaries[0] is not None + assert summaries[0].id == communities[0].id + assert {entity.id for entity in communities[0].entities} == {alice.id, bob.id} + assert {item.id for item in communities[0].relations} == {relation.id} + + entity_payloads = await kg.index.nodes_vector_db.get_payloads_by_ids([ + alice.id, + bob.id, + ]) + assert entity_payloads[0]["clusters"] == stored_entities[0].clusters + assert entity_payloads[1]["clusters"] == stored_entities[1].clusters + + +@pytest.mark.asyncio +async def test_reindex_community_keeps_existing_communities_when_summarization_fails(kg): + alice = Entity( + id="ent-alice", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ) + bob = Entity( + id="ent-bob", + entity_name="Bob", + entity_type="Person", + description="Bob", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ) + relation = Relation( + id="rel-alice-bob", + subject_id=alice.id, + object_id=bob.id, + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Alice knows Bob.", + source_chunk_id=["chunk-1"], + ) + + await kg.upsert_entities([alice, bob]) + await kg.upsert_relations([relation]) + await kg.index.upsert_communities([ + Community( + id="com-existing", + level=0, + cluster_id=7, + entities=[alice, bob], + relations=[relation], + ) + ]) + await kg.upsert_summaries([ + CommunitySummary(id="com-existing", summary="Existing summary") + ]) + + kg.pipeline.community_summarizer.summarize = AsyncMock(side_effect=RuntimeError("LLM failed")) + + with pytest.raises(RuntimeError, match="LLM failed"): + await kg.reindex_community() + + communities = await kg.get_communities(["com-existing"]) + summaries = await kg.get_summaries(["com-existing"]) + + assert communities[0] is not None + assert summaries[0] is not None + assert summaries[0].summary == "Existing summary" + + +@pytest.mark.asyncio +async def test_insert_relations_merges_with_existing_relation(kg): + await kg.upsert_entities([ + Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ), + Entity( + id="ent-2", + entity_name="Bob", + entity_type="Person", + description="Bob", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ), + ]) + + original = Relation( + id="rel-1", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Friends", + relation_strength=1.0, + source_chunk_id=["chunk-1"], + ) + incoming = Relation( + id="rel-1", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Work together", + relation_strength=0.4, + source_chunk_id=["chunk-2"], + ) + + await kg.upsert_relations([original]) + await kg.upsert_relations([incoming]) + + stored = await kg.index.get_edges([("ent-1", "ent-2", "rel-1")]) + + assert stored[0] is not None + assert "Friends" in stored[0].description + assert "Work together" in stored[0].description + assert stored[0].relation_strength == pytest.approx(0.7) + assert set(stored[0].source_chunk_id) == {"chunk-1", "chunk-2"} + + +@pytest.mark.asyncio +async def test_build_from_docs_uses_knowledge_graph_merge_path(kg): + original = Entity( + entity_name="Alice", + entity_type="Person", + description="Stored description", + source_chunk_id=["chunk-old"], + documents_id=["doc-old"], + clusters=[], + ) + await kg.upsert_entities([original]) + + extracted = Entity( + entity_name="Alice", + entity_type="Person", + description="Extracted description", + source_chunk_id=["chunk-new"], + documents_id=["doc-new"], + clusters=[], + ) + + async def fake_extract_graph(chunks): + return [extracted], [], [], [], chunks + + kg.pipeline.extract_graph = fake_extract_graph + + await kg.build_from_docs(["Alice is mentioned in the new document."]) + + stored = await kg.index.get_nodes([original.id]) + + assert stored[0] is not None + assert "Stored description" in stored[0].description + assert "Extracted description" in stored[0].description + assert set(stored[0].source_chunk_id) == {"chunk-old", "chunk-new"} + assert set(stored[0].documents_id) == {"doc-old", "doc-new"} + + +@pytest.mark.asyncio +async def test_insert_entities_rejects_duplicate_ids_in_one_request(kg): + entity = Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ) + + with pytest.raises(ValueError, match="duplicated entity IDs"): + await kg.upsert_entities([entity, Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice duplicate", + source_chunk_id=["chunk-2"], + documents_id=["doc-2"], + clusters=[], + )]) + + +@pytest.mark.asyncio +async def test_update_entities_rejects_duplicate_ids_in_one_request(kg): + entity = Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ) + await kg.upsert_entities([entity]) + + with pytest.raises(ValueError, match="duplicated entity IDs"): + await kg.update_entities([entity, Entity( + id="ent-1", + entity_name="Alice Updated", + entity_type="Person", + description="Alice updated", + source_chunk_id=["chunk-2"], + documents_id=["doc-2"], + clusters=[], + )]) + + +@pytest.mark.asyncio +async def test_insert_relations_rejects_duplicate_ids_in_one_request(kg): + await kg.upsert_entities([ + Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ), + Entity( + id="ent-2", + entity_name="Bob", + entity_type="Person", + description="Bob", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ), + ]) + + relation = Relation( + id="rel-1", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Friends", + relation_strength=1.0, + source_chunk_id=["chunk-1"], + ) + + with pytest.raises(ValueError, match="duplicated relation IDs"): + await kg.upsert_relations([relation, Relation( + id="rel-1", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Coworkers", + relation_strength=0.5, + source_chunk_id=["chunk-2"], + )]) + + +@pytest.mark.asyncio +async def test_update_relations_rejects_duplicate_ids_in_one_request(kg): + await kg.upsert_entities([ + Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ), + Entity( + id="ent-2", + entity_name="Bob", + entity_type="Person", + description="Bob", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ), + ]) + relation = Relation( + id="rel-1", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Friends", + relation_strength=1.0, + source_chunk_id=["chunk-1"], + ) + await kg.upsert_relations([relation]) + + with pytest.raises(ValueError, match="duplicated relation IDs"): + await kg.update_relations([relation, Relation( + id="rel-1", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Updated relation", + relation_strength=0.7, + source_chunk_id=["chunk-2"], + )]) + + +@pytest.mark.asyncio +async def test_get_entities_is_batched(kg): + await kg.upsert_entities([ + Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ), + Entity( + id="ent-2", + entity_name="Bob", + entity_type="Person", + description="Bob", + source_chunk_id=["chunk-2"], + documents_id=["doc-1"], + clusters=[], + ), + ]) + + entities = await kg.get_entities(["ent-1", "ent-missing", "ent-2"]) + + assert [entity.id if entity is not None else None for entity in entities] == ["ent-1", None, "ent-2"] + + +@pytest.mark.asyncio +async def test_get_entities_supports_single_item_batch(kg): + entity = Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ) + await kg.upsert_entities([entity]) + + fetched = await kg.get_entities(["ent-1"]) + + assert fetched[0] is not None + assert fetched[0].id == "ent-1" + + +@pytest.mark.asyncio +async def test_get_relations_chunks_communities_are_batched(kg): + await kg.upsert_entities([ + Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ), + Entity( + id="ent-2", + entity_name="Bob", + entity_type="Person", + description="Bob", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ), + ]) + relation = Relation( + id="rel-1", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Friends", + relation_strength=1.0, + source_chunk_id=["chunk-1"], + ) + await kg.upsert_relations([relation]) + await kg.index.upsert_chunks([ + kg_chunk := Chunk( + content="Chunk one text", + chunk_order_idx=0, + doc_id="doc-1", + ) + ]) + await kg.index.upsert_communities([ + Community( + id="com-1", + level=0, + cluster_id=1, + entities=[ + Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=["chunk-1"], + ) + ], + relations=[relation], + ) + ]) + + relations = await kg.get_relations([("ent-1", "ent-2", "rel-1")]) + chunks = await kg.get_chunks([kg_chunk.id]) + communities = await kg.get_communities(["com-1"]) + relation = await kg.get_relations([("ent-1", "ent-2", "rel-1")]) + chunk = await kg.get_chunks([kg_chunk.id]) + community = await kg.get_communities(["com-1"]) + + assert relations[0] is not None + assert chunks[0] is not None + assert communities[0] is not None + assert relation[0] is not None + assert chunk[0] is not None + assert community[0] is not None + + +@pytest.mark.asyncio +async def test_community_crud_operations_are_exposed_on_knowledge_graph(kg): + alice = Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Alice", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ) + bob = Entity( + id="ent-2", + entity_name="Bob", + entity_type="Person", + description="Bob", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ) + relation = Relation( + id="rel-1", + subject_id=alice.id, + object_id=bob.id, + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Alice knows Bob.", + source_chunk_id=["chunk-1"], + ) + community = Community( + id="com-1", + level=0, + cluster_id=1, + entities=[alice, bob], + relations=[relation], + ) + updated_community = Community( + id="com-1", + level=1, + cluster_id=2, + entities=[alice], + relations=[], + ) + + await kg.upsert_entities([alice, bob]) + await kg.upsert_relations([relation]) + + assert await kg.upsert_communities([community]) is kg + + stored = await kg.get_communities(["com-1"]) + assert stored[0] is not None + assert stored[0].level == 0 + assert {entity.id for entity in stored[0].entities} == {"ent-1", "ent-2"} + assert {item.id for item in stored[0].relations} == {"rel-1"} + + assert await kg.update_communities([updated_community]) is kg + + stored = await kg.get_communities(["com-1"]) + assert stored[0] is not None + assert stored[0].level == 1 + assert stored[0].cluster_id == 2 + assert [entity.id for entity in stored[0].entities] == ["ent-1"] + assert stored[0].relations == [] + + with pytest.raises(ValueError, match="non-existent communities"): + await kg.update_communities([ + Community(id="com-missing", level=0, cluster_id=9, entities=[], relations=[]) + ]) + + await kg.upsert_summaries([CommunitySummary(id="com-1", summary="Community summary")]) + assert await kg.delete_communities(["com-1"]) is kg + + stored = await kg.get_communities(["com-1"]) + summaries = await kg.get_summaries(["com-1"]) + assert stored[0] is None + assert summaries[0] is None + + +@pytest.mark.asyncio +async def test_get_summaries_is_batched(kg): + await kg.upsert_summaries([ + CommunitySummary(id="sum-1", summary="Summary one"), + CommunitySummary(id="sum-2", summary="Summary two"), + ]) + + summaries = await kg.get_summaries(["sum-1", "sum-missing", "sum-2"]) + + assert [summary.id if summary is not None else None for summary in summaries] == ["sum-1", None, "sum-2"] + + +@pytest.mark.asyncio +async def test_get_summaries_supports_single_item_batch(kg): + await kg.upsert_summaries([CommunitySummary(id="sum-1", summary="Summary one")]) + + summary = await kg.get_summaries(["sum-1"]) + + assert summary[0] is not None + assert summary[0].id == "sum-1" + + +@pytest.mark.asyncio +async def test_summary_crud_operations_are_exposed_on_knowledge_graph(kg): + summary = CommunitySummary(id="sum-1", summary="Initial summary") + updated_summary = CommunitySummary(id="sum-1", summary="Updated summary") + + assert await kg.upsert_summaries([summary]) is kg + + stored = await kg.get_summaries(["sum-1"]) + assert stored[0] is not None + assert stored[0].summary == "Initial summary" + + assert await kg.update_summaries([updated_summary]) is kg + + stored = await kg.get_summaries(["sum-1"]) + assert stored[0] is not None + assert stored[0].summary == "Updated summary" + + with pytest.raises(ValueError, match="non-existent summaries"): + await kg.update_summaries([ + CommunitySummary(id="sum-missing", summary="Missing summary") + ]) + + assert await kg.delete_summaries(["sum-1"]) is kg + + stored = await kg.get_summaries(["sum-1"]) + assert stored[0] is None diff --git a/tests/graph/test_merge_logic.py b/tests/graph/test_merge_logic.py index cae2570..3991dd4 100644 --- a/tests/graph/test_merge_logic.py +++ b/tests/graph/test_merge_logic.py @@ -1,12 +1,14 @@ """ -Tests for entity and relation merge logic. +Tests for high-level entity and relation merge policies. """ +from unittest.mock import AsyncMock + import pytest + +from ragu.graph.knowledge_graph import default_merge_entities_policy, default_merge_relations_policy from ragu.graph.types import Entity, Relation -from ragu.graph.index import Index, StorageArguments from ragu.models.embedder import Embedder -from unittest.mock import AsyncMock @pytest.fixture @@ -18,15 +20,7 @@ def mock_embedder(): return embedder -@pytest.fixture -def index(tmp_path, mock_embedder): - """Create an Index instance.""" - storage_args = StorageArguments() - index = Index(embedder=mock_embedder, arguments=storage_args) - return index - - -def test_merge_entities_no_duplicates(index): +def test_merge_entities_no_duplicates(): """Test merging when there are no duplicates.""" entity1 = Entity( id="ent-1", @@ -37,29 +31,14 @@ def test_merge_entities_no_duplicates(index): documents_id=["doc-1"], clusters=[], ) - entity2 = Entity( - id="ent-2", - entity_name="Bob", - entity_type="Person", - description="Description 2", - source_chunk_id=["chunk-2"], - documents_id=["doc-2"], - clusters=[], - ) - groups = { - ("Alice", "Person"): [entity1], - ("Bob", "Person"): [entity2], - } + merged = default_merge_entities_policy([entity1]) - merged = index._merge_entities(groups) + assert merged.id == "ent-1" + assert merged.description == "Description 1" - assert len(merged) == 2 - assert merged[0].id == "ent-1" - assert merged[1].id == "ent-2" - -def test_merge_entities_with_duplicates(index): +def test_merge_entities_with_duplicates(): """Test merging entities with duplicates.""" entity1 = Entity( id="ent-1", @@ -71,7 +50,7 @@ def test_merge_entities_with_duplicates(index): clusters=[{"level": 0, "cluster_id": 1}], ) entity2 = Entity( - id="ent-2", + id="ent-1", entity_name="Alice", entity_type="Person", description="Works at Acme Corp", @@ -80,14 +59,7 @@ def test_merge_entities_with_duplicates(index): clusters=[], ) - groups = { - ("Alice", "Person"): [entity1, entity2], - } - - merged = index._merge_entities(groups) - - assert len(merged) == 1 - merged_entity = merged[0] + merged_entity = default_merge_entities_policy([entity1, entity2]) # Should use primary ID (from entity with most chunks) assert merged_entity.id == "ent-1" @@ -103,7 +75,7 @@ def test_merge_entities_with_duplicates(index): assert set(merged_entity.documents_id) == {"doc-1", "doc-2"} -def test_merge_entities_sorts_by_richness(index): +def test_merge_entities_sorts_by_richness(): """Test that merge uses entity with most chunks as primary.""" entity1 = Entity( id="ent-1", @@ -115,7 +87,7 @@ def test_merge_entities_sorts_by_richness(index): clusters=[], ) entity2 = Entity( - id="ent-2", + id="ent-1", entity_name="Alice", entity_type="Person", description="Description 2", @@ -124,17 +96,13 @@ def test_merge_entities_sorts_by_richness(index): clusters=[], ) - groups = { - ("Alice", "Person"): [entity1, entity2], - } - - merged = index._merge_entities(groups) + merged = default_merge_entities_policy([entity1, entity2]) - # Should use ent-2 as primary (has more chunks) - assert merged[0].id == "ent-2" + # Should keep the shared ID while using the richer payload as primary. + assert merged.id == "ent-1" -def test_merge_relations_no_duplicates(index): +def test_merge_relations_no_duplicates(): """Test merging relations with no duplicates.""" rel1 = Relation( id="rel-1", @@ -147,29 +115,14 @@ def test_merge_relations_no_duplicates(index): relation_strength=1.0, source_chunk_id=["chunk-1"], ) - rel2 = Relation( - id="rel-2", - subject_id="ent-2", - object_id="ent-3", - subject_name="Bob", - object_name="Charlie", - relation_type="KNOWS", - description="Bob knows Charlie", - relation_strength=1.0, - source_chunk_id=["chunk-2"], - ) - - groups = { - ("ent-1", "ent-2", "KNOWS"): [rel1], - ("ent-2", "ent-3", "KNOWS"): [rel2], - } - merged = index._merge_relations(groups) + merged = default_merge_relations_policy([rel1]) - assert len(merged) == 2 + assert merged.id == "rel-1" + assert merged.description == "Alice knows Bob" -def test_merge_relations_with_duplicates(index): +def test_merge_relations_with_duplicates(): """Test merging duplicate relations.""" rel1 = Relation( id="rel-1", @@ -183,7 +136,7 @@ def test_merge_relations_with_duplicates(index): source_chunk_id=["chunk-1", "chunk-2"], ) rel2 = Relation( - id="rel-2", + id="rel-1", subject_id="ent-1", object_id="ent-2", subject_name="Alice", @@ -194,14 +147,7 @@ def test_merge_relations_with_duplicates(index): source_chunk_id=["chunk-3"], ) - groups = { - ("ent-1", "ent-2", "WORKS_WITH"): [rel1, rel2], - } - - merged = index._merge_relations(groups) - - assert len(merged) == 1 - merged_rel = merged[0] + merged_rel = default_merge_relations_policy([rel1, rel2]) # Should use primary ID assert merged_rel.id == "rel-1" @@ -217,7 +163,7 @@ def test_merge_relations_with_duplicates(index): assert set(merged_rel.source_chunk_id) == {"chunk-1", "chunk-2", "chunk-3"} -def test_merge_entities_deduplicates_descriptions(index): +def test_merge_entities_deduplicates_descriptions(): """Test that duplicate descriptions are not repeated.""" entity1 = Entity( id="ent-1", @@ -229,26 +175,21 @@ def test_merge_entities_deduplicates_descriptions(index): clusters=[], ) entity2 = Entity( - id="ent-2", + id="ent-1", entity_name="Alice", entity_type="Person", - description="Software engineer", # Same description + description="Software engineer", source_chunk_id=["chunk-2"], documents_id=["doc-1"], clusters=[], ) - groups = { - ("Alice", "Person"): [entity1, entity2], - } - - merged = index._merge_entities(groups) + merged = default_merge_entities_policy([entity1, entity2]) - # Should not duplicate description - assert merged[0].description == "Software engineer" + assert merged.description == "Software engineer" -def test_merge_relations_deduplicates_descriptions(index): +def test_merge_relations_deduplicates_descriptions(): """Test that duplicate relation descriptions are not repeated.""" rel1 = Relation( id="rel-1", @@ -262,28 +203,23 @@ def test_merge_relations_deduplicates_descriptions(index): source_chunk_id=["chunk-1"], ) rel2 = Relation( - id="rel-2", + id="rel-1", subject_id="ent-1", object_id="ent-2", subject_name="Alice", object_name="Bob", relation_type="KNOWS", - description="Friends", # Same description + description="Friends", relation_strength=1.0, source_chunk_id=["chunk-2"], ) - groups = { - ("ent-1", "ent-2", "KNOWS"): [rel1, rel2], - } - - merged = index._merge_relations(groups) + merged = default_merge_relations_policy([rel1, rel2]) - # Should not duplicate description - assert merged[0].description == "Friends" + assert merged.description == "Friends" -def test_merge_is_deterministic(index): +def test_merge_is_deterministic(): """Test that merge produces consistent results.""" entity1 = Entity( id="ent-1", @@ -295,7 +231,7 @@ def test_merge_is_deterministic(index): clusters=[], ) entity2 = Entity( - id="ent-2", + id="ent-1", entity_name="Alice", entity_type="Person", description="Desc B", @@ -304,21 +240,15 @@ def test_merge_is_deterministic(index): clusters=[], ) - groups = { - ("Alice", "Person"): [entity1, entity2], - } - - # Merge multiple times - merged1 = index._merge_entities(groups) - merged2 = index._merge_entities(groups) + merged1 = default_merge_entities_policy([entity1, entity2]) + merged2 = default_merge_entities_policy([entity1, entity2]) - # Results should be identical - assert merged1[0].id == merged2[0].id - assert merged1[0].description == merged2[0].description - assert merged1[0].source_chunk_id == merged2[0].source_chunk_id + assert merged1.id == merged2.id + assert merged1.description == merged2.description + assert merged1.source_chunk_id == merged2.source_chunk_id -def test_merge_entities_deduplicates_description_fragments(index): +def test_merge_entities_deduplicates_description_fragments(): entity1 = Entity( id="ent-1", entity_name="Alice", @@ -329,7 +259,7 @@ def test_merge_entities_deduplicates_description_fragments(index): clusters=[], ) entity2 = Entity( - id="ent-2", + id="ent-1", entity_name="Alice", entity_type="Person", description="Software engineer.", @@ -338,12 +268,12 @@ def test_merge_entities_deduplicates_description_fragments(index): clusters=[], ) - merged = index._merge_entities({("Alice", "Person"): [entity1, entity2]}) + merged = default_merge_entities_policy([entity1, entity2]) - assert merged[0].description.count("Software engineer.") == 1 + assert merged.description.count("Software engineer.") == 1 -def test_merge_relations_deduplicates_description_fragments(index): +def test_merge_relations_deduplicates_description_fragments(): rel1 = Relation( id="rel-1", subject_id="ent-1", @@ -356,7 +286,7 @@ def test_merge_relations_deduplicates_description_fragments(index): source_chunk_id=["chunk-1"], ) rel2 = Relation( - id="rel-2", + id="rel-1", subject_id="ent-1", object_id="ent-2", subject_name="Alice", @@ -367,6 +297,58 @@ def test_merge_relations_deduplicates_description_fragments(index): source_chunk_id=["chunk-2"], ) - merged = index._merge_relations({"rel-key": [rel1, rel2]}) + merged = default_merge_relations_policy([rel1, rel2]) + + assert merged.description.count("Friends.") == 1 + + +def test_merge_entities_rejects_different_ids(): + entity1 = Entity( + id="ent-1", + entity_name="Alice", + entity_type="Person", + description="Description 1", + source_chunk_id=["chunk-1"], + documents_id=["doc-1"], + clusters=[], + ) + entity2 = Entity( + id="ent-2", + entity_name="Alice", + entity_type="Person", + description="Description 2", + source_chunk_id=["chunk-2"], + documents_id=["doc-2"], + clusters=[], + ) + + with pytest.raises(ValueError, match="different IDs"): + default_merge_entities_policy([entity1, entity2]) + + +def test_merge_relations_rejects_different_ids(): + rel1 = Relation( + id="rel-1", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Friends", + relation_strength=1.0, + source_chunk_id=["chunk-1"], + ) + rel2 = Relation( + id="rel-2", + subject_id="ent-1", + object_id="ent-2", + subject_name="Alice", + object_name="Bob", + relation_type="KNOWS", + description="Coworkers", + relation_strength=1.0, + source_chunk_id=["chunk-2"], + ) - assert merged[0].description.count("Friends.") == 1 + with pytest.raises(ValueError, match="different IDs"): + default_merge_relations_policy([rel1, rel2]) diff --git a/tests/search_engine/conftest.py b/tests/search_engine/conftest.py index 34c7913..6831bb7 100644 --- a/tests/search_engine/conftest.py +++ b/tests/search_engine/conftest.py @@ -22,16 +22,14 @@ async def embed_text(self, text: str, **kwargs) -> list[float]: @pytest.fixture -def real_kg(): - previous_storage = Settings.storage_folder - Settings.storage_folder = "tests/kg_for_test" +def real_kg(monkeypatch): + monkeypatch.setattr(Settings, "storage_folder", "tests/kg_for_test") kg = KnowledgeGraph( llm=None, embedder=DummyEmbedder(dim=3072), builder_settings=BuilderArguments(use_llm_summarization=False), ) - yield kg - Settings.storage_folder = previous_storage + return kg @pytest.fixture diff --git a/tests/search_engine/test_global_search_engine.py b/tests/search_engine/test_global_search_engine.py index 13503df..358c122 100644 --- a/tests/search_engine/test_global_search_engine.py +++ b/tests/search_engine/test_global_search_engine.py @@ -2,9 +2,10 @@ from unittest.mock import AsyncMock import pytest +from ragu.common.prompts.default_models import GlobalSearchContextModel -from ragu.search_engine.global_search import GlobalSearchEngine -from ragu.search_engine.types import GlobalSearchResult +from ragu.search_engine.base_engine import SearchEngineResponse +from ragu.search_engine.global_search import GlobalSearchEngine, GlobalSearchResult, GlobalSearchRetrieve @pytest.mark.asyncio @@ -16,16 +17,20 @@ async def test_global_search_filters_and_sorts_by_rating(monkeypatch, real_kg): "get_meta_responses", AsyncMock( return_value=[ - {"response": "low", "rating": "1"}, - {"response": "drop", "rating": "0"}, - {"response": "high", "rating": "5"}, + GlobalSearchContextModel(**{"reasoning": "", "response": "low", "rating": "1"}), + GlobalSearchContextModel(**{"reasoning": "", "response": "drop", "rating": "0"}), + GlobalSearchContextModel(**{"reasoning": "", "response": "high", "rating": "5"}), ] ), ) result = await engine.a_search("query") - assert isinstance(result, GlobalSearchResult) - assert [r["response"] for r in result.insights] == ["high", "low"] + assert isinstance(result, GlobalSearchRetrieve) + assert [r["response"] for r in result.result.insights] == ["high", "low"] + assert result.metrics == { + "insight_0_rating": 5.0, + "insight_1_rating": 1.0, + } @pytest.mark.asyncio @@ -33,7 +38,12 @@ async def test_global_query_returns_llm_response(monkeypatch, real_kg): llm = SimpleNamespace(chat_completion=AsyncMock(return_value="global-answer")) engine = GlobalSearchEngine(llm=llm, knowledge_graph=real_kg) engine.truncation = lambda s: s - engine.a_search = AsyncMock(return_value=GlobalSearchResult(insights=[{"response": "x", "rating": "1"}])) + engine.a_search = AsyncMock( + return_value=GlobalSearchRetrieve( + query="question", + result=GlobalSearchResult(insights=[{"response": "x", "rating": "1"}]), + ) + ) from ragu.search_engine import global_search as global_module monkeypatch.setattr( @@ -48,4 +58,5 @@ async def test_global_query_returns_llm_response(monkeypatch, real_kg): ) result = await engine.a_query("question") - assert result == "global-answer" + assert isinstance(result, SearchEngineResponse) + assert result.response == "global-answer" diff --git a/tests/search_engine/test_local_search_engine.py b/tests/search_engine/test_local_search_engine.py index c9a7dec..c4685b5 100644 --- a/tests/search_engine/test_local_search_engine.py +++ b/tests/search_engine/test_local_search_engine.py @@ -5,8 +5,8 @@ from ragu.chunker.types import Chunk from ragu.graph.types import Entity, Relation, CommunitySummary -from ragu.search_engine.local_search import LocalSearchEngine -from ragu.search_engine.types import LocalSearchResult +from ragu.search_engine.base_engine import SearchEngineResponse +from ragu.search_engine.local_search import LocalSearchEngine, LocalSearchResult, LocalSearchRetrieve from ragu.storage.types import EmbeddingHit @@ -17,27 +17,32 @@ def _make_embedder_mock(): @pytest.mark.asyncio async def test_local_search_collects_entities_relations_chunks_and_summaries(real_kg, kg_fixture_ids): entity_ids = kg_fixture_ids["entity_ids"] - real_kg.index.entity_vector_db.query = AsyncMock( - return_value=[ - EmbeddingHit(id=entity_ids[0], distance=0.9), - EmbeddingHit(id=entity_ids[1], distance=0.8), - EmbeddingHit(id="ent-missing", distance=0.7), - ] - ) + existing_entities = await real_kg.index.get_nodes(entity_ids[:2]) engine = LocalSearchEngine( llm=SimpleNamespace(chat_completion=AsyncMock()), knowledge_graph=real_kg, embedder=_make_embedder_mock(), ) + entities = [e for e in existing_entities if e is not None] + engine.retriever.query_entities = AsyncMock( + return_value=( + entities, + [EmbeddingHit(id=entity.id, distance=score) for entity, score in zip(entities, [0.9, 0.8])], + ) + ) result = await engine.a_search("query", top_k=3) - assert isinstance(result, LocalSearchResult) - assert [e.id for e in result.entities] == entity_ids[:2] - assert isinstance(result.relations, list) - assert isinstance(result.chunks, list) - assert isinstance(result.summaries, list) - assert isinstance(result.documents_id, list) + assert isinstance(result, LocalSearchRetrieve) + assert [e.id for e in result.result.entities] == entity_ids[:2] + assert isinstance(result.result.relations, list) + assert isinstance(result.result.chunks, list) + assert isinstance(result.result.summaries, list) + assert isinstance(result.result.documents_id, list) + assert result.metrics["entities"] == [ + {"id": entities[0].id, "name": entities[0].entity_name, "rank": 0, "relevance_score": 0.9}, + {"id": entities[1].id, "name": entities[1].entity_name, "rank": 1, "relevance_score": 0.8}, + ] @pytest.mark.asyncio @@ -65,14 +70,6 @@ async def test_local_search_reranks_entities_relations_summaries_and_chunks(monk chunk_b = Chunk(content="chunk beta", chunk_order_idx=1, doc_id="doc-b") summaries = [CommunitySummary(summary="summary alpha", id="123"), CommunitySummary(summary="summary beta", id="345")] - real_kg.index.entity_vector_db.query = AsyncMock( - return_value=[ - EmbeddingHit(id=entity_a.id, distance=0.9), - EmbeddingHit(id=entity_b.id, distance=0.8), - ] - ) - real_kg.index.get_entities = AsyncMock(return_value=[entity_a, entity_b]) - reranker = SimpleNamespace( score=AsyncMock( side_effect=[ @@ -96,14 +93,27 @@ async def test_local_search_reranks_entities_relations_summaries_and_chunks(monk embedder=_make_embedder_mock(), reranker=reranker, ) + engine.retriever.query_entities = AsyncMock( + return_value=( + [entity_a, entity_b], + [ + EmbeddingHit(id=entity_a.id, distance=0.11), + EmbeddingHit(id=entity_b.id, distance=0.95), + ], + ) + ) result = await engine.a_search("query", top_k=2) - assert [entity.id for entity in result.entities] == [entity_b.id, entity_a.id] - assert [relation.id for relation in result.relations] == [relation_b.id, relation_a.id] - assert [c.summary for c in result.summaries] == ["summary beta", "summary alpha"] - assert [chunk.id for chunk in result.chunks] == [chunk_b.id, chunk_a.id] - assert result.documents_id == ["doc-b", "doc-a"] + assert [entity.id for entity in result.result.entities] == [entity_b.id, entity_a.id] + assert [relation.id for relation in result.result.relations] == [relation_b.id, relation_a.id] + assert [c.summary for c in result.result.summaries] == ["summary beta", "summary alpha"] + assert [chunk.id for chunk in result.result.chunks] == [chunk_b.id, chunk_a.id] + assert result.result.documents_id == ["doc-b", "doc-a"] + assert result.metrics["entities"] == [ + {"id": entity_b.id, "name": "Beta", "rank": 0, "relevance_score": 0.95}, + {"id": entity_a.id, "name": "Alpha", "rank": 1, "relevance_score": 0.11}, + ] @pytest.mark.asyncio @@ -111,7 +121,7 @@ async def test_local_query_returns_raw_result_when_no_response_attr(monkeypatch, llm = SimpleNamespace(chat_completion=AsyncMock(return_value="raw-result")) engine = LocalSearchEngine(llm=llm, knowledge_graph=real_kg, embedder=_make_embedder_mock()) engine.truncation = lambda s: s - engine.a_search = AsyncMock(return_value=LocalSearchResult()) + engine.a_search = AsyncMock(return_value=LocalSearchRetrieve(query="question", result=LocalSearchResult())) from ragu.search_engine import local_search as local_module monkeypatch.setattr( @@ -126,4 +136,5 @@ async def test_local_query_returns_raw_result_when_no_response_attr(monkeypatch, ) result = await engine.a_query("question") - assert result == "raw-result" + assert isinstance(result, SearchEngineResponse) + assert result.response == "raw-result" diff --git a/tests/search_engine/test_mix_search_engine.py b/tests/search_engine/test_mix_search_engine.py index da3785c..cc69f19 100644 --- a/tests/search_engine/test_mix_search_engine.py +++ b/tests/search_engine/test_mix_search_engine.py @@ -4,9 +4,10 @@ import pytest from ragu.common.prompts.messages import ChatMessages, UserMessage -from ragu.search_engine.base_engine import BaseEngine -from ragu.search_engine.mix_search import MixSearchEngine -from ragu.search_engine.types import GlobalSearchResult, NaiveSearchResult +from ragu.search_engine.base_engine import BaseEngine, SearchEngineResponse +from ragu.search_engine.global_search import GlobalSearchResult, GlobalSearchRetrieve +from ragu.search_engine.mix_search import MixSearchRetrieve, MixSearchResult, MixSearchEngine +from ragu.search_engine.naive_search import NaiveSearchResult, NaiveSearchRetrieve class DummyEngine(BaseEngine): @@ -21,13 +22,23 @@ async def a_search(self, query, *args, **kwargs): return self._result async def a_query(self, query: str): - return "unused" + return SearchEngineResponse( + query=query, + response="unused", + retrieval=self._result, + ) @pytest.mark.asyncio async def test_mix_search_collects_contexts_in_engine_order(): - naive_result = NaiveSearchResult(chunks=[], scores=[], documents_id=["doc-1"]) - global_result = GlobalSearchResult(insights=[{"response": "x", "rating": "3"}]) + naive_result = NaiveSearchRetrieve( + query="query", + result=NaiveSearchResult(chunks=[], scores=[], documents_id=["doc-1"]), + ) + global_result = GlobalSearchRetrieve( + query="query", + result=GlobalSearchResult(insights=[{"response": "x", "rating": "3"}]), + ) engine = MixSearchEngine( llm=SimpleNamespace(chat_completion=AsyncMock()), engines=[ @@ -38,13 +49,14 @@ async def test_mix_search_collects_contexts_in_engine_order(): result = await engine.a_search("query") - assert isinstance(result, list) - assert result == [naive_result, global_result] + assert isinstance(result, MixSearchRetrieve) + assert result.result.results == [naive_result, global_result] + assert result.metrics == {} @pytest.mark.asyncio async def test_mix_search_records_partial_failures(): - ok_result = NaiveSearchResult() + ok_result = NaiveSearchRetrieve(query="query", result=NaiveSearchResult()) engine = MixSearchEngine( llm=SimpleNamespace(chat_completion=AsyncMock()), engines=[ @@ -56,7 +68,8 @@ async def test_mix_search_records_partial_failures(): result = await engine.a_search("query") - assert result == [ok_result, None] + assert isinstance(result, MixSearchRetrieve) + assert result.result.results == [ok_result] @pytest.mark.asyncio @@ -79,10 +92,10 @@ async def test_mix_query_returns_llm_response(monkeypatch): llm = SimpleNamespace(chat_completion=AsyncMock(return_value="mix-answer")) engine = MixSearchEngine( llm=llm, - engines=[DummyEngine(result=NaiveSearchResult())], + engines=[DummyEngine(result=NaiveSearchRetrieve(query="question", result=NaiveSearchResult()))], ) engine.truncation = lambda s: s - engine._search_all = AsyncMock(return_value=[NaiveSearchResult()]) + engine._search_all = AsyncMock(return_value=[NaiveSearchRetrieve(query="question", result=NaiveSearchResult())]) engine._query_all = AsyncMock() from ragu.search_engine import mix_search as mix_module @@ -103,7 +116,8 @@ async def test_mix_query_returns_llm_response(monkeypatch): ) result = await engine.a_query("question") - assert result == "mix-answer" + assert isinstance(result, SearchEngineResponse) + assert result.response == "mix-answer" engine._search_all.assert_awaited_once_with("question") engine._query_all.assert_not_awaited() @@ -113,11 +127,19 @@ async def test_mix_query_can_ensemble_engine_responses(monkeypatch): llm = SimpleNamespace(chat_completion=AsyncMock(return_value="ensemble-answer")) engine = MixSearchEngine( llm=llm, - engines=[DummyEngine(result=NaiveSearchResult())], + engines=[DummyEngine(result=NaiveSearchRetrieve(query="question", result=NaiveSearchResult()))], ) engine.truncation = lambda s: s engine._search_all = AsyncMock() - engine._query_all = AsyncMock(return_value=["engine-answer"]) + engine._query_all = AsyncMock( + return_value=[ + SearchEngineResponse( + query="question", + response="engine-answer", + retrieval=NaiveSearchRetrieve(query="question", result=NaiveSearchResult()), + ) + ] + ) from ragu.search_engine import mix_search as mix_module monkeypatch.setattr( @@ -137,6 +159,7 @@ async def test_mix_query_can_ensemble_engine_responses(monkeypatch): ) result = await engine.a_query("question", ensemble_responses=True) - assert result == "ensemble-answer" + assert isinstance(result, SearchEngineResponse) + assert result.response == "ensemble-answer" engine._query_all.assert_awaited_once_with("question") engine._search_all.assert_not_awaited() diff --git a/tests/search_engine/test_naive_search_engine.py b/tests/search_engine/test_naive_search_engine.py index 92d3e2a..088af56 100644 --- a/tests/search_engine/test_naive_search_engine.py +++ b/tests/search_engine/test_naive_search_engine.py @@ -3,8 +3,9 @@ import pytest -from ragu.search_engine.naive_search import NaiveSearchEngine -from ragu.search_engine.types import NaiveSearchResult +from ragu.chunker.types import Chunk +from ragu.search_engine.base_engine import SearchEngineResponse +from ragu.search_engine.naive_search import NaiveSearchEngine, NaiveSearchResult, NaiveSearchRetrieve from ragu.storage.types import EmbeddingHit @@ -15,14 +16,6 @@ def _make_embedder_mock(): @pytest.mark.asyncio async def test_naive_search_rerank_and_rerank_top_k(real_kg, kg_fixture_ids): chunk_ids = kg_fixture_ids["chunk_ids"] - real_kg.index.chunk_vector_db.query = AsyncMock( - return_value=[ - EmbeddingHit(id=chunk_ids[0], distance=0.2), - EmbeddingHit(id=chunk_ids[1], distance=0.8), - EmbeddingHit(id="chunk-missing", distance=0.5), - ] - ) - reranker = SimpleNamespace(score=AsyncMock(return_value=[(1, 0.95), (0, 0.11)])) llm = SimpleNamespace(chat_completion=AsyncMock()) @@ -32,37 +25,58 @@ async def test_naive_search_rerank_and_rerank_top_k(real_kg, kg_fixture_ids): embedder=_make_embedder_mock(), reranker=reranker ) + engine.retriever.query_chunks = AsyncMock( + return_value=( + [ + Chunk(content="chunk one", chunk_order_idx=0, doc_id="doc-1"), + Chunk(content="chunk two", chunk_order_idx=1, doc_id="doc-2"), + ], + [ + EmbeddingHit(id=chunk_ids[0], distance=0.2), + EmbeddingHit(id=chunk_ids[1], distance=0.8), + ], + ) + ) + setattr(engine.retriever.query_chunks.return_value[0][0], "id", chunk_ids[0]) + setattr(engine.retriever.query_chunks.return_value[0][1], "id", chunk_ids[1]) result = await engine.a_search("query", top_k=3, rerank_top_k=1) - assert isinstance(result, NaiveSearchResult) - assert len(result.chunks) == 1 - assert result.chunks[0].id == chunk_ids[1] - assert result.scores == [0.95] - assert len(result.documents_id) == 1 + assert isinstance(result, NaiveSearchRetrieve) + assert len(result.result.chunks) == 1 + assert result.result.chunks[0].id == chunk_ids[1] + assert result.result.scores == [0.95] + assert len(result.result.documents_id) == 1 + assert result.metrics["chunks"] == [ + {"id": chunk_ids[1], "rank": 0, "score": 0.95}, + ] @pytest.mark.asyncio async def test_naive_search_empty_returns_empty_result(real_kg): - real_kg.index.chunk_vector_db.query = AsyncMock(return_value=[]) engine = NaiveSearchEngine( llm=SimpleNamespace(chat_completion=AsyncMock()), knowledge_graph=real_kg, embedder=_make_embedder_mock(), ) + engine.retriever.query_chunks = AsyncMock(return_value=([], [])) result = await engine.a_search("query") - assert result.chunks == [] - assert result.scores == [] - assert result.documents_id == [] + assert result.result.chunks == [] + assert result.result.scores == [] + assert result.result.documents_id == [] + assert result.metrics == {} @pytest.mark.asyncio async def test_naive_query_uses_llm_response(monkeypatch): llm = SimpleNamespace(chat_completion=AsyncMock(return_value="naive-answer")) - kg = SimpleNamespace(index=SimpleNamespace(chunk_vector_db=SimpleNamespace(query=AsyncMock(return_value=[])))) + kg = SimpleNamespace( + index=SimpleNamespace(chunks_kv_storage=SimpleNamespace(get_by_ids=AsyncMock(return_value=[]))), + sparse_embedder=None, + ) engine = NaiveSearchEngine(llm=llm, knowledge_graph=kg, embedder=_make_embedder_mock()) engine.truncation = lambda s: s - engine.a_search = AsyncMock(return_value=NaiveSearchResult()) + engine.a_search = AsyncMock(return_value=NaiveSearchRetrieve(query="question", result=NaiveSearchResult())) from ragu.search_engine import naive_search as naive_module monkeypatch.setattr( @@ -77,4 +91,5 @@ async def test_naive_query_uses_llm_response(monkeypatch): ) result = await engine.a_query("question") - assert result == "naive-answer" + assert isinstance(result, SearchEngineResponse) + assert result.response == "naive-answer" diff --git a/tests/storage/conftest.py b/tests/storage/conftest.py new file mode 100644 index 0000000..3636c38 --- /dev/null +++ b/tests/storage/conftest.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path +from typing import Callable + +import pytest + +from ragu.storage.vdb_storage_adapters.nano_vdb import NanoVectorDBStorage + +from tests.storage.qdrant_testkit import load_qdrant_storage + + +@dataclass(frozen=True) +class VDBBackendCase: + name: str + factory: Callable[[Path, pytest.MonkeyPatch], object] + supports_sparse: bool = False + supports_persistence: bool = True + + +def _make_nano_storage(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + return NanoVectorDBStorage( + embedding_dim=3, + filename=str(tmp_path / "vdb.json"), + cosine_threshold=0.0, + ) + + +def _make_qdrant_dense_storage(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + QdrantVectorDBStorage = load_qdrant_storage(monkeypatch) + return QdrantVectorDBStorage( + embedding_dim=3, + filename=str(tmp_path / "vdb.json"), + ) + + +def _make_qdrant_sparse_storage(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): + QdrantVectorDBStorage = load_qdrant_storage(monkeypatch) + return QdrantVectorDBStorage( + embedding_dim=3, + filename=str(tmp_path / "vdb.json"), + sparse_type="bm42", + ) + + +ALL_VDB_CASES = [ + VDBBackendCase( + name="nano", + factory=_make_nano_storage, + supports_sparse=False, + ), + VDBBackendCase( + name="qdrant-dense", + factory=_make_qdrant_dense_storage, + supports_sparse=False, + ), + VDBBackendCase( + name="qdrant-bm42", + factory=_make_qdrant_sparse_storage, + supports_sparse=True, + ), +] + + +def pytest_addoption(parser): + parser.addoption( + "--vdb-backend", + action="append", + default=[], + help="Run shared VDB contract tests only for the selected backend ids.", + ) + + +@pytest.fixture(params=ALL_VDB_CASES, ids=lambda case: case.name) +def vdb_backend_case(request, pytestconfig): + case = request.param + selected = pytestconfig.getoption("--vdb-backend") + if selected and case.name not in selected: + pytest.skip(f"Backend {case.name} not selected") + return case + + +@pytest.fixture +def vdb_storage(vdb_backend_case, tmp_path, monkeypatch): + return vdb_backend_case.factory(tmp_path, monkeypatch) diff --git a/tests/storage/qdrant_testkit.py b/tests/storage/qdrant_testkit.py new file mode 100644 index 0000000..c5fa31d --- /dev/null +++ b/tests/storage/qdrant_testkit.py @@ -0,0 +1,357 @@ +from __future__ import annotations + +import importlib +import math +import sys +from dataclasses import dataclass +from types import ModuleType, SimpleNamespace +from typing import Any + + +@dataclass +class FakeVectorParams: + size: int + distance: str + + +@dataclass +class FakeSparseVectorParams: + modifier: str | None = None + + +@dataclass +class FakeSparseVector: + indices: list[int] + values: list[float] + + +@dataclass +class FakePointStruct: + id: str + vector: list[float] | dict[str, object] | None + payload: dict + + +@dataclass +class FakePointIdsList: + points: list[str] + + +@dataclass +class FakeScoredPoint: + id: str + score: float + payload: dict + + +@dataclass +class FakeQueryResponse: + points: list[FakeScoredPoint] + + +@dataclass +class FakePrefetch: + query: list[float] | FakeSparseVector + using: str + limit: int + + +@dataclass +class FakeFusionQuery: + fusion: str + + +class FakeAsyncQdrantClient: + registries: dict[str, dict[str, dict[str, object]]] = {} + instances: list["FakeAsyncQdrantClient"] = [] + + def __init__(self, path: str | None = None, **kwargs: Any): + self.path = None if path is None else str(path) + self.kwargs = kwargs + self.location = self.path or "__memory__" + self.registry = self.registries.setdefault(self.location, {}) + self.instances.append(self) + + @classmethod + def reset(cls) -> None: + cls.registries = {} + cls.instances = [] + + async def collection_exists(self, collection_name: str) -> bool: + return collection_name in self.registry + + async def create_collection( + self, + collection_name: str, + vectors_config, + sparse_vectors_config=None, + **kwargs: Any, + ) -> None: + self.registry.setdefault( + collection_name, + { + "vectors_config": vectors_config, + "sparse_vectors_config": sparse_vectors_config or {}, + "points": {}, + }, + ) + + async def get_collection(self, collection_name: str): + collection = self.registry[collection_name] + vectors = collection["vectors_config"] + sparse_vectors = collection.get("sparse_vectors_config", {}) + return SimpleNamespace( + config=SimpleNamespace( + params=SimpleNamespace( + vectors=vectors if isinstance(vectors, dict) else SimpleNamespace( + size=vectors.size, + distance=vectors.distance, + ), + sparse_vectors=sparse_vectors, + ) + ) + ) + + async def upsert(self, collection_name: str, points: list[FakePointStruct], **kwargs: Any) -> None: + collection = self.registry[collection_name] + stored_points = collection["points"] + for point in points: + stored_points[point.id] = point + + async def query_points( + self, + collection_name: str, + query, + limit: int, + with_payload: bool = True, + using: str | None = None, + prefetch: FakePrefetch | list[FakePrefetch] | None = None, + score_threshold: float | None = None, + **kwargs: Any, + ) -> FakeQueryResponse: + collection = self.registry[collection_name] + stored_points = collection["points"] + if prefetch is not None and isinstance(query, FakeFusionQuery): + prefetches = prefetch if isinstance(prefetch, list) else [prefetch] + ranked_lists = [ + self._rank_points( + stored_points, + query=entry.query, + using=entry.using, + limit=entry.limit, + with_payload=with_payload, + ) + for entry in prefetches + ] + fused = self._fuse_rrf(ranked_lists) + return FakeQueryResponse(points=fused[:limit]) + + scored_points = self._rank_points( + stored_points, + query=query, + using=using, + limit=limit, + with_payload=with_payload, + score_threshold=score_threshold, + ) + return FakeQueryResponse(points=scored_points) + + async def delete(self, collection_name: str, points_selector, **kwargs: Any) -> None: + collection = self.registry[collection_name] + stored_points = collection["points"] + + if isinstance(points_selector, FakePointIdsList): + ids = points_selector.points + else: + ids = list(points_selector) + + for point_id in ids: + stored_points.pop(point_id, None) + + async def scroll( + self, + collection_name: str, + limit: int, + offset=None, + with_payload: bool = False, + with_vectors: bool = False, + **kwargs: Any, + ): + points = list(self.registry[collection_name]["points"].values()) + start_index = 0 + if offset is not None: + for index, point in enumerate(points): + if point.id == offset: + start_index = index + 1 + break + + batch = points[start_index:start_index + limit] + next_offset = None + if start_index + limit < len(points): + next_offset = batch[-1].id + + return [ + FakePointStruct( + id=point.id, + vector=point.vector if with_vectors else None, + payload=point.payload if with_payload else {}, + ) + for point in batch + ], next_offset + + async def retrieve( + self, + collection_name: str, + ids: list[str], + with_vectors: bool = False, + with_payload: bool = True, + **kwargs: Any, + ): + stored_points = self.registry[collection_name]["points"] + results = [] + for point_id in ids: + point = stored_points.get(point_id) + if point is None: + continue + results.append( + FakePointStruct( + id=point.id, + vector=point.vector if with_vectors else None, + payload=point.payload if with_payload else {}, + ) + ) + return results + + async def close(self, **kwargs: Any) -> None: + return None + + @staticmethod + def _vector_for_using(point: FakePointStruct, using: str | None): + if isinstance(point.vector, dict): + if using is None: + return point.vector.get("dense") + return point.vector.get(using) + return point.vector + + def _rank_points( + self, + stored_points: dict[str, FakePointStruct], + query, + using: str | None, + limit: int, + with_payload: bool, + score_threshold: float | None = None, + ) -> list[FakeScoredPoint]: + scored_points: list[FakeScoredPoint] = [] + for point in stored_points.values(): + target_vector = self._vector_for_using(point, using) + if target_vector is None: + continue + if isinstance(query, FakeSparseVector): + assert isinstance(target_vector, FakeSparseVector) + score = self._sparse_similarity(query, target_vector) + else: + assert isinstance(target_vector, list) + score = self._cosine_similarity(query, target_vector) + if score_threshold is not None and score < score_threshold: + continue + scored_points.append( + FakeScoredPoint( + id=point.id, + score=score, + payload=point.payload if with_payload else {}, + ) + ) + scored_points.sort(key=lambda item: item.score, reverse=True) + return scored_points[:limit] + + @staticmethod + def _fuse_rrf(ranked_lists: list[list[FakeScoredPoint]]) -> list[FakeScoredPoint]: + payload_by_id: dict[str, dict] = {} + score_by_id: dict[str, float] = {} + for ranked in ranked_lists: + for rank, point in enumerate(ranked, start=1): + payload_by_id.setdefault(point.id, point.payload) + score_by_id[point.id] = score_by_id.get(point.id, 0.0) + 1.0 / (60 + rank) + fused = [ + FakeScoredPoint(id=point_id, score=score, payload=payload_by_id[point_id]) + for point_id, score in score_by_id.items() + ] + fused.sort(key=lambda item: item.score, reverse=True) + return fused + + @staticmethod + def _cosine_similarity(left: list[float], right: list[float]) -> float: + numerator = sum(a * b for a, b in zip(left, right)) + left_norm = math.sqrt(sum(value * value for value in left)) + right_norm = math.sqrt(sum(value * value for value in right)) + if left_norm == 0.0 or right_norm == 0.0: + return 0.0 + return numerator / (left_norm * right_norm) + + @staticmethod + def _sparse_similarity(left: FakeSparseVector, right: FakeSparseVector) -> float: + right_lookup = dict(zip(right.indices, right.values)) + return sum(value * right_lookup.get(index, 0.0) for index, value in zip(left.indices, left.values)) + + +def install_fake_qdrant(monkeypatch) -> None: + FakeAsyncQdrantClient.reset() + + models_module = ModuleType("qdrant_client.models") + models_module.Distance = SimpleNamespace(COSINE="cosine") + models_module.Modifier = SimpleNamespace(IDF="idf") + models_module.PointIdsList = FakePointIdsList + models_module.PointStruct = FakePointStruct + models_module.Prefetch = FakePrefetch + models_module.SparseVector = FakeSparseVector + models_module.SparseVectorParams = FakeSparseVectorParams + models_module.VectorParams = FakeVectorParams + + http_models_models_module = ModuleType("qdrant_client.http.models.models") + http_models_models_module.Distance = models_module.Distance + http_models_models_module.Fusion = SimpleNamespace(RRF="rrf") + http_models_models_module.FusionQuery = FakeFusionQuery + http_models_models_module.Modifier = models_module.Modifier + http_models_models_module.PointIdsList = FakePointIdsList + http_models_models_module.Prefetch = FakePrefetch + http_models_models_module.PointStruct = FakePointStruct + http_models_models_module.SparseVector = FakeSparseVector + http_models_models_module.SparseVectorParams = FakeSparseVectorParams + http_models_models_module.VectorParams = FakeVectorParams + + http_models_module = ModuleType("qdrant_client.http.models") + http_models_module.Fusion = http_models_models_module.Fusion + http_models_module.FusionQuery = FakeFusionQuery + http_models_module.models = http_models_models_module + + http_module = ModuleType("qdrant_client.http") + http_module.models = http_models_module + + conversions_common_types_module = ModuleType("qdrant_client.conversions.common_types") + conversions_common_types_module.CollectionInfo = object + conversions_common_types_module.QueryResponse = FakeQueryResponse + + conversions_module = ModuleType("qdrant_client.conversions") + conversions_module.common_types = conversions_common_types_module + + qdrant_module = ModuleType("qdrant_client") + qdrant_module.AsyncQdrantClient = FakeAsyncQdrantClient + qdrant_module.conversions = conversions_module + qdrant_module.models = models_module + qdrant_module.http = http_module + + monkeypatch.setitem(sys.modules, "qdrant_client", qdrant_module) + monkeypatch.setitem(sys.modules, "qdrant_client.conversions", conversions_module) + monkeypatch.setitem(sys.modules, "qdrant_client.conversions.common_types", conversions_common_types_module) + monkeypatch.setitem(sys.modules, "qdrant_client.models", models_module) + monkeypatch.setitem(sys.modules, "qdrant_client.http", http_module) + monkeypatch.setitem(sys.modules, "qdrant_client.http.models", http_models_module) + monkeypatch.setitem(sys.modules, "qdrant_client.http.models.models", http_models_models_module) + + +def load_qdrant_storage(monkeypatch): + install_fake_qdrant(monkeypatch) + sys.modules.pop("ragu.storage.vdb_storage_adapters.qdrant_vdb", None) + module = importlib.import_module("ragu.storage.vdb_storage_adapters.qdrant_vdb") + return module.QdrantVectorDBStorage diff --git a/tests/storage/test_backend_batch_operations.py b/tests/storage/test_backend_batch_operations.py index 92b7829..a276963 100644 --- a/tests/storage/test_backend_batch_operations.py +++ b/tests/storage/test_backend_batch_operations.py @@ -1,7 +1,7 @@ """ Tests for batch operations in storage backends. """ - +import numpy as np import pytest from ragu.graph.types import Entity, Relation from ragu.storage.graph_storage_adapters.networkx_adapter import NetworkXStorage @@ -265,21 +265,21 @@ async def test_kv_delete(tmp_path): @pytest.mark.asyncio async def test_vdb_delete(tmp_path): """Test vector DB delete operation.""" - from ragu.storage.types import Embedding + from ragu.storage.types import Point dim = 128 storage_file = tmp_path / "test_vdb.json" vdb = NanoVectorDBStorage(embedding_dim=dim, filename=str(storage_file), cosine_threshold=0.0) # Insert data - embeddings = [ - Embedding(id="id1", vector=[0.1] * dim, metadata={"content": "test1"}), - Embedding(id="id2", vector=[0.2] * dim, metadata={"content": "test2"}), + points = [ + Point(id="id1", dense_embedding=np.array([0.1] * dim), metadata={"content": "test1"}), + Point(id="id2", dense_embedding=np.array([0.2] * dim), metadata={"content": "test2"}), ] - await vdb.upsert(embeddings) + await vdb.upsert(points) # Get initial count - query_emb = Embedding(vector=[0.15] * dim) + query_emb = Point(dense_embedding=np.array([0.15] * dim)) initial_results = await vdb.query(query_emb, top_k=10) assert len(initial_results) == 2 diff --git a/tests/storage/test_qdrant_vdb_sparse_modes.py b/tests/storage/test_qdrant_vdb_sparse_modes.py new file mode 100644 index 0000000..0ce3123 --- /dev/null +++ b/tests/storage/test_qdrant_vdb_sparse_modes.py @@ -0,0 +1,279 @@ +from __future__ import annotations + +import importlib +import sys +from dataclasses import dataclass +from types import ModuleType, SimpleNamespace + +import numpy as np +import pytest + +from ragu.storage.types import Point, SparseEmbedding + + +@dataclass +class _FakeVectorParams: + size: int + distance: str + + +@dataclass +class _FakeSparseVectorParams: + modifier: str | None = None + + +@dataclass +class _FakeSparseVector: + indices: list[int] + values: list[float] + + +@dataclass +class _FakePointStruct: + id: str + vector: dict[str, object] + payload: dict + + +@dataclass +class _FakePointIdsList: + points: list[str] + + +class FakeAsyncQdrantClient: + registries: dict[str, dict[str, dict[str, object]]] = {} + instances: list["FakeAsyncQdrantClient"] = [] + + def __init__(self, path: str | None = None, **kwargs): + self.path = None if path is None else str(path) + self.kwargs = kwargs + self.location = self.path or "__memory__" + self.registry = self.registries.setdefault(self.location, {}) + self.instances.append(self) + + @classmethod + def reset(cls) -> None: + cls.registries = {} + cls.instances = [] + + async def collection_exists(self, collection_name: str) -> bool: + return collection_name in self.registry + + async def create_collection( + self, + collection_name: str, + vectors_config, + sparse_vectors_config=None, + **kwargs, + ) -> None: + self.registry[collection_name] = { + "vectors_config": vectors_config, + "sparse_vectors_config": sparse_vectors_config or {}, + "points": {}, + } + + async def get_collection(self, collection_name: str): + collection = self.registry[collection_name] + return SimpleNamespace( + config=SimpleNamespace( + params=SimpleNamespace( + vectors=collection["vectors_config"], + sparse_vectors=collection["sparse_vectors_config"], + ) + ) + ) + + async def upsert(self, collection_name: str, points: list[_FakePointStruct], **kwargs) -> None: + self.registry[collection_name]["points"].update({point.id: point for point in points}) + + async def query_points(self, *args, **kwargs): + raise NotImplementedError + + async def scroll( + self, + collection_name: str, + limit: int, + offset=None, + with_payload: bool = False, + with_vectors: bool = False, + **kwargs, + ): + points = list(self.registry[collection_name]["points"].values()) + start_index = 0 + if offset is not None: + for index, point in enumerate(points): + if point.id == offset: + start_index = index + 1 + break + + batch = points[start_index:start_index + limit] + next_offset = None + if start_index + limit < len(points): + next_offset = batch[-1].id + + return [ + _FakePointStruct( + id=point.id, + vector=point.vector if with_vectors else None, + payload=point.payload if with_payload else {}, + ) + for point in batch + ], next_offset + + async def retrieve( + self, + collection_name: str, + ids: list[str], + with_vectors: bool = False, + with_payload: bool = True, + **kwargs, + ): + stored_points = self.registry[collection_name]["points"] + results = [] + for point_id in ids: + point = stored_points.get(point_id) + if point is None: + continue + results.append( + _FakePointStruct( + id=point.id, + vector=point.vector if with_vectors else None, + payload=point.payload if with_payload else {}, + ) + ) + return results + + async def delete(self, *args, **kwargs): + return None + + +def _install_fake_qdrant(monkeypatch: pytest.MonkeyPatch) -> None: + FakeAsyncQdrantClient.reset() + + models_module = ModuleType("qdrant_client.models") + models_module.Distance = SimpleNamespace(COSINE="cosine") + models_module.Modifier = SimpleNamespace(IDF="idf") + models_module.PointIdsList = _FakePointIdsList + models_module.PointStruct = _FakePointStruct + models_module.Prefetch = object + models_module.SparseVector = _FakeSparseVector + models_module.SparseVectorParams = _FakeSparseVectorParams + models_module.VectorParams = _FakeVectorParams + + http_models_module = ModuleType("qdrant_client.http.models") + http_models_module.Fusion = SimpleNamespace(RRF="rrf") + http_models_module.FusionQuery = object + + http_module = ModuleType("qdrant_client.http") + http_module.models = http_models_module + + conversions_common_types_module = ModuleType("qdrant_client.conversions.common_types") + conversions_common_types_module.CollectionInfo = object + conversions_common_types_module.QueryResponse = object + + conversions_module = ModuleType("qdrant_client.conversions") + conversions_module.common_types = conversions_common_types_module + + qdrant_module = ModuleType("qdrant_client") + qdrant_module.AsyncQdrantClient = FakeAsyncQdrantClient + qdrant_module.conversions = conversions_module + qdrant_module.models = models_module + qdrant_module.http = http_module + + monkeypatch.setitem(sys.modules, "qdrant_client", qdrant_module) + monkeypatch.setitem(sys.modules, "qdrant_client.conversions", conversions_module) + monkeypatch.setitem(sys.modules, "qdrant_client.conversions.common_types", conversions_common_types_module) + monkeypatch.setitem(sys.modules, "qdrant_client.models", models_module) + monkeypatch.setitem(sys.modules, "qdrant_client.http", http_module) + monkeypatch.setitem(sys.modules, "qdrant_client.http.models", http_models_module) + + +def _load_qdrant_storage(monkeypatch: pytest.MonkeyPatch): + _install_fake_qdrant(monkeypatch) + sys.modules.pop("ragu.storage.vdb_storage_adapters.qdrant_vdb", None) + module = importlib.import_module("ragu.storage.vdb_storage_adapters.qdrant_vdb") + return module.QdrantVectorDBStorage + + +@pytest.mark.asyncio +async def test_bm42_sparse_mode_creates_idf_index_and_uses_bm42_vector_name(monkeypatch, tmp_path): + QdrantVectorDBStorage = _load_qdrant_storage(monkeypatch) + storage = QdrantVectorDBStorage( + embedding_dim=3, + filename=str(tmp_path / "vdb.json"), + sparse_type="bm42", + ) + + await storage.upsert( + [ + Point( + id="doc-1", + dense_embedding=np.array([1.0, 0.0, 0.0]), + sparse_embedding=SparseEmbedding(indices=[7, 9], values=[1.0, 0.5]), + metadata={"tag": "hybrid"}, + ) + ] + ) + + collection = FakeAsyncQdrantClient.registries[str(tmp_path)][storage.collection_name] + sparse_config = collection["sparse_vectors_config"]["bm42"] + stored_point = next(iter(collection["points"].values())) + + assert sparse_config.modifier == "idf" + assert "bm42" in stored_point.vector + assert stored_point.vector["bm42"] == _FakeSparseVector(indices=[7, 9], values=[1.0, 0.5]) + + +@pytest.mark.asyncio +async def test_bm42_sparse_mode_validates_existing_collection_modifier(monkeypatch, tmp_path): + QdrantVectorDBStorage = _load_qdrant_storage(monkeypatch) + storage = QdrantVectorDBStorage( + embedding_dim=3, + filename=str(tmp_path / "vdb.json"), + sparse_type="bm42", + ) + + FakeAsyncQdrantClient.registries[str(tmp_path)] = { + storage.collection_name: { + "vectors_config": { + "dense": _FakeVectorParams(size=3, distance="cosine"), + }, + "sparse_vectors_config": { + "bm42": _FakeSparseVectorParams(modifier="idf"), + }, + "points": {}, + } + } + + await storage.index_start_callback() + + +@pytest.mark.asyncio +async def test_remote_qdrant_args_are_explicit_constructor_parameters(monkeypatch, tmp_path): + QdrantVectorDBStorage = _load_qdrant_storage(monkeypatch) + storage = QdrantVectorDBStorage( + embedding_dim=3, + filename=str(tmp_path / "vdb.json"), + url="http://qdrant.example", + host="qdrant.example", + port=6333, + grpc_port=6334, + api_key="secret", + location="us-east", + timeout=10, + ) + + await storage.index_start_callback() + + client = FakeAsyncQdrantClient.instances[0] + assert client.path is None + assert client.kwargs == { + "url": "http://qdrant.example", + "host": "qdrant.example", + "port": 6333, + "grpc_port": 6334, + "api_key": "secret", + "location": "us-east", + "timeout": 10, + } + diff --git a/tests/storage/test_qdrant_vdb_storage.py b/tests/storage/test_qdrant_vdb_storage.py new file mode 100644 index 0000000..275eaec --- /dev/null +++ b/tests/storage/test_qdrant_vdb_storage.py @@ -0,0 +1,343 @@ +from __future__ import annotations + +import importlib +import math +import sys +from dataclasses import dataclass +from pathlib import Path +from types import ModuleType, SimpleNamespace + +import numpy as np +import pytest + +from ragu.storage.types import Point + + +@dataclass +class _FakeVectorParams: + size: int + distance: str + + +@dataclass +class _FakePointStruct: + id: str + vector: list[float] | dict[str, object] + payload: dict + + +@dataclass +class _FakePointIdsList: + points: list[str] + + +@dataclass +class _FakeScoredPoint: + id: str + score: float + payload: dict + + +@dataclass +class _FakeQueryResponse: + points: list[_FakeScoredPoint] + + +@dataclass +class _FakeSparseVector: + indices: list[int] + values: list[float] + + +@dataclass +class _FakeSparseVectorParams: + modifier: str | None = None + + +@dataclass +class _FakePrefetch: + query: list[float] | _FakeSparseVector + using: str + limit: int + + +@dataclass +class _FakeFusionQuery: + fusion: str + + +class FakeAsyncQdrantClient: + registries: dict[str, dict[str, dict[str, object]]] = {} + instances: list["FakeAsyncQdrantClient"] = [] + + def __init__(self, path: str | None = None, **kwargs): + self.path = None if path is None else str(path) + self.kwargs = kwargs + self.location = self.path or "__memory__" + self.registry = self.registries.setdefault(self.location, {}) + self.instances.append(self) + + @classmethod + def reset(cls) -> None: + cls.registries = {} + cls.instances = [] + + async def collection_exists(self, collection_name: str) -> bool: + return collection_name in self.registry + + async def create_collection( + self, + collection_name: str, + vectors_config, + sparse_vectors_config=None, + **kwargs, + ) -> None: + self.registry.setdefault( + collection_name, + { + "vectors_config": vectors_config, + "sparse_vectors_config": sparse_vectors_config or {}, + "points": {}, + }, + ) + + async def get_collection(self, collection_name: str): + collection = self.registry[collection_name] + vectors = collection["vectors_config"] + sparse_vectors = collection.get("sparse_vectors_config", {}) + return SimpleNamespace( + config=SimpleNamespace( + params=SimpleNamespace( + vectors=vectors if isinstance(vectors, dict) else SimpleNamespace( + size=vectors.size, + distance=vectors.distance, + ), + sparse_vectors=sparse_vectors, + ) + ) + ) + + async def upsert(self, collection_name: str, points: list[_FakePointStruct], **kwargs) -> None: + collection = self.registry[collection_name] + stored_points = collection["points"] + for point in points: + stored_points[point.id] = point + + async def query_points( + self, + collection_name: str, + query, + limit: int, + with_payload: bool = True, + using: str | None = None, + prefetch: _FakePrefetch | list[_FakePrefetch] | None = None, + score_threshold: float | None = None, + **kwargs, + ) -> _FakeQueryResponse: + collection = self.registry[collection_name] + stored_points = collection["points"] + if prefetch is not None and isinstance(query, _FakeFusionQuery): + prefetches = prefetch if isinstance(prefetch, list) else [prefetch] + ranked_lists = [ + self._rank_points( + stored_points, + query=entry.query, + using=entry.using, + limit=entry.limit, + with_payload=with_payload, + ) + for entry in prefetches + ] + fused = self._fuse_rrf(ranked_lists) + return _FakeQueryResponse(points=fused[:limit]) + + scored_points = self._rank_points( + stored_points, + query=query, + using=using, + limit=limit, + with_payload=with_payload, + score_threshold=score_threshold, + ) + return _FakeQueryResponse(points=scored_points) + + async def delete(self, collection_name: str, points_selector, **kwargs) -> None: + collection = self.registry[collection_name] + stored_points = collection["points"] + + if isinstance(points_selector, _FakePointIdsList): + ids = points_selector.points + else: + ids = list(points_selector) + + for point_id in ids: + stored_points.pop(point_id, None) + + async def close(self, **kwargs) -> None: + return None + + @staticmethod + def _vector_for_using(point: _FakePointStruct, using: str | None): + if isinstance(point.vector, dict): + if using is None: + return point.vector.get("dense") + return point.vector.get(using) + return point.vector + + def _rank_points( + self, + stored_points: dict[str, _FakePointStruct], + query, + using: str | None, + limit: int, + with_payload: bool, + score_threshold: float | None = None, + ) -> list[_FakeScoredPoint]: + scored_points: list[_FakeScoredPoint] = [] + for point in stored_points.values(): + target_vector = self._vector_for_using(point, using) + if target_vector is None: + continue + if isinstance(query, _FakeSparseVector): + assert isinstance(target_vector, _FakeSparseVector) + score = _sparse_similarity(query, target_vector) + else: + assert isinstance(target_vector, list) + score = _cosine_similarity(query, target_vector) + if score_threshold is not None and score < score_threshold: + continue + scored_points.append( + _FakeScoredPoint( + id=point.id, + score=score, + payload=point.payload if with_payload else {}, + ) + ) + scored_points.sort(key=lambda item: item.score, reverse=True) + return scored_points[:limit] + + @staticmethod + def _fuse_rrf(ranked_lists: list[list[_FakeScoredPoint]]) -> list[_FakeScoredPoint]: + payload_by_id: dict[str, dict] = {} + score_by_id: dict[str, float] = {} + for ranked in ranked_lists: + for rank, point in enumerate(ranked, start=1): + payload_by_id.setdefault(point.id, point.payload) + score_by_id[point.id] = score_by_id.get(point.id, 0.0) + 1.0 / (60 + rank) + fused = [ + _FakeScoredPoint(id=point_id, score=score, payload=payload_by_id[point_id]) + for point_id, score in score_by_id.items() + ] + fused.sort(key=lambda item: item.score, reverse=True) + return fused + + +def _cosine_similarity(left: list[float], right: list[float]) -> float: + numerator = sum(a * b for a, b in zip(left, right)) + left_norm = math.sqrt(sum(value * value for value in left)) + right_norm = math.sqrt(sum(value * value for value in right)) + if left_norm == 0.0 or right_norm == 0.0: + return 0.0 + return numerator / (left_norm * right_norm) + + +def _sparse_similarity(left: _FakeSparseVector, right: _FakeSparseVector) -> float: + right_lookup = dict(zip(right.indices, right.values)) + return sum(value * right_lookup.get(index, 0.0) for index, value in zip(left.indices, left.values)) + + +def _install_fake_qdrant(monkeypatch: pytest.MonkeyPatch) -> None: + FakeAsyncQdrantClient.reset() + + models_module = ModuleType("qdrant_client.models") + models_module.Distance = SimpleNamespace(COSINE="cosine") + models_module.Modifier = SimpleNamespace(IDF="idf") + models_module.PointIdsList = _FakePointIdsList + models_module.PointStruct = _FakePointStruct + models_module.Prefetch = _FakePrefetch + models_module.SparseVector = _FakeSparseVector + models_module.SparseVectorParams = _FakeSparseVectorParams + models_module.VectorParams = _FakeVectorParams + + http_models_models_module = ModuleType("qdrant_client.http.models.models") + http_models_models_module.Distance = models_module.Distance + http_models_models_module.Fusion = SimpleNamespace(RRF="rrf") + http_models_models_module.FusionQuery = _FakeFusionQuery + http_models_models_module.Modifier = models_module.Modifier + http_models_models_module.PointIdsList = _FakePointIdsList + http_models_models_module.Prefetch = _FakePrefetch + http_models_models_module.PointStruct = _FakePointStruct + http_models_models_module.SparseVector = _FakeSparseVector + http_models_models_module.SparseVectorParams = _FakeSparseVectorParams + http_models_models_module.VectorParams = _FakeVectorParams + + http_models_module = ModuleType("qdrant_client.http.models") + http_models_module.Fusion = http_models_models_module.Fusion + http_models_module.FusionQuery = _FakeFusionQuery + http_models_module.models = http_models_models_module + + http_module = ModuleType("qdrant_client.http") + http_module.models = http_models_module + + conversions_common_types_module = ModuleType("qdrant_client.conversions.common_types") + conversions_common_types_module.CollectionInfo = object + conversions_common_types_module.QueryResponse = _FakeQueryResponse + + conversions_module = ModuleType("qdrant_client.conversions") + conversions_module.common_types = conversions_common_types_module + + qdrant_module = ModuleType("qdrant_client") + qdrant_module.AsyncQdrantClient = FakeAsyncQdrantClient + qdrant_module.conversions = conversions_module + qdrant_module.models = models_module + qdrant_module.http = http_module + + monkeypatch.setitem(sys.modules, "qdrant_client", qdrant_module) + monkeypatch.setitem(sys.modules, "qdrant_client.conversions", conversions_module) + monkeypatch.setitem(sys.modules, "qdrant_client.conversions.common_types", conversions_common_types_module) + monkeypatch.setitem(sys.modules, "qdrant_client.models", models_module) + monkeypatch.setitem(sys.modules, "qdrant_client.http", http_module) + monkeypatch.setitem(sys.modules, "qdrant_client.http.models", http_models_module) + monkeypatch.setitem(sys.modules, "qdrant_client.http.models.models", http_models_models_module) + + +def _load_qdrant_storage(monkeypatch: pytest.MonkeyPatch): + _install_fake_qdrant(monkeypatch) + sys.modules.pop("ragu.storage.vdb_storage_adapters.qdrant_vdb", None) + module = importlib.import_module("ragu.storage.vdb_storage_adapters.qdrant_vdb") + return module.QdrantVectorDBStorage + + +@pytest.mark.asyncio +async def test_upsert_accepts_empty_sparse_list(monkeypatch, tmp_path): + QdrantVectorDBStorage = _load_qdrant_storage(monkeypatch) + storage_file = tmp_path / "vdb.json" + vdb = QdrantVectorDBStorage(embedding_dim=3, filename=str(storage_file)) + + await vdb.upsert( + [Point(id="doc-1", dense_embedding=np.array([1.0, 0.0, 0.0]), metadata={"tag": "dense-only"})], + sparse_data=[], + ) + + collection = FakeAsyncQdrantClient.registries[str(tmp_path)][vdb.collection_name] + stored_point = next(iter(collection["points"].values())) + assert isinstance(stored_point.vector, dict) + assert stored_point.vector["dense"] == [1.0, 0.0, 0.0] + assert "sparse" not in stored_point.vector + + +@pytest.mark.asyncio +async def test_filename_drives_local_path_and_collection_name(monkeypatch, tmp_path): + QdrantVectorDBStorage = _load_qdrant_storage(monkeypatch) + storage_file = tmp_path / "vdb_entity.json" + vdb = QdrantVectorDBStorage(embedding_dim=3, filename=str(storage_file), cosine_threshold=0.0) + + await vdb.upsert([Point(id="ent-1", dense_embedding=np.array([1.0, 0.0, 0.0]), metadata={"kind": "entity"})]) + + assert len(FakeAsyncQdrantClient.instances) == 1 + assert Path(FakeAsyncQdrantClient.instances[0].path) == tmp_path + assert vdb.collection_name in FakeAsyncQdrantClient.registries[str(tmp_path)] + assert vdb.collection_name.endswith("_vdb_entity") + created_collection = FakeAsyncQdrantClient.registries[str(tmp_path)][vdb.collection_name] + assert "dense" in created_collection["vectors_config"] + assert created_collection["sparse_vectors_config"] == {} diff --git a/tests/storage/test_vdb_contract.py b/tests/storage/test_vdb_contract.py new file mode 100644 index 0000000..31f0e7e --- /dev/null +++ b/tests/storage/test_vdb_contract.py @@ -0,0 +1,152 @@ +from __future__ import annotations + +import numpy as np +import pytest + +from ragu.storage.types import EmbeddingHit, Point, SparseEmbedding + + +def _point(record_id: str, dense: list[float], **metadata) -> Point: + return Point( + id=record_id, + dense_embedding=np.array(dense), + metadata=metadata, + ) + + +@pytest.mark.asyncio +async def test_vdb_contract_upsert_and_query_round_trip(vdb_storage): + await vdb_storage.upsert([ + _point("id-alpha", [1.0, 0.0, 0.0], tag="A"), + _point("id-beta", [0.0, 1.0, 0.0], tag="B"), + ]) + + results = await vdb_storage.query(Point(dense_embedding=np.array([1.0, 0.0, 0.0])), top_k=10) + + assert len(results) >= 1 + assert isinstance(results[0], EmbeddingHit) + assert results[0].id == "id-alpha" + assert results[0].metadata["tag"] == "A" + + +@pytest.mark.asyncio +async def test_vdb_contract_get_all_ids(vdb_storage): + await vdb_storage.upsert([ + _point("id-alpha", [1.0, 0.0, 0.0], tag="A"), + _point("id-beta", [0.0, 1.0, 0.0], tag="B"), + ]) + + ids = await vdb_storage.get_all_ids() + + assert set(ids) == {"id-alpha", "id-beta"} + + +@pytest.mark.asyncio +async def test_vdb_contract_get_payloads_by_ids_preserves_input_order(vdb_storage): + await vdb_storage.upsert([ + _point("id-alpha", [1.0, 0.0, 0.0], tag="A"), + _point("id-beta", [0.0, 1.0, 0.0], tag="B"), + ]) + + payloads = await vdb_storage.get_payloads_by_ids(["id-beta", "missing", "id-alpha"]) + + assert payloads[0] is not None + assert payloads[1] is None + assert payloads[2] is not None + assert payloads[0]["__id__"] == "id-beta" + assert payloads[2]["__id__"] == "id-alpha" + + +@pytest.mark.asyncio +async def test_vdb_contract_get_points_by_ids_preserves_input_order(vdb_storage): + await vdb_storage.upsert([ + _point("id-alpha", [1.0, 0.0, 0.0], tag="A"), + _point("id-beta", [0.0, 1.0, 0.0], tag="B"), + ]) + + points = await vdb_storage.get_points_by_ids(["id-beta", "missing", "id-alpha"]) + + assert points[0] is not None + assert points[1] is None + assert points[2] is not None + assert points[0].id == "id-beta" + assert list(points[0].dense_embedding) == [0.0, 1.0, 0.0] + assert points[2].id == "id-alpha" + assert points[2].metadata["tag"] == "A" + + +@pytest.mark.asyncio +async def test_vdb_contract_delete_existing_and_missing_ids(vdb_storage): + await vdb_storage.upsert([ + _point("id-alpha", [1.0, 0.0, 0.0], tag="A"), + _point("id-beta", [0.0, 1.0, 0.0], tag="B"), + ]) + + await vdb_storage.delete([]) + await vdb_storage.delete(["id-alpha", "missing"]) + + ids = await vdb_storage.get_all_ids() + + assert "id-alpha" not in ids + assert "id-beta" in ids + + +@pytest.mark.asyncio +async def test_vdb_contract_persistence_round_trip(vdb_backend_case, vdb_storage, tmp_path, monkeypatch): + if not vdb_backend_case.supports_persistence: + pytest.skip("Backend does not support persistence round-trip") + + await vdb_storage.upsert([ + _point("id-persist", [1.0, 0.0, 0.0], tag="persist"), + ]) + await vdb_storage.index_done_callback() + + if vdb_backend_case.name.startswith("qdrant"): + reload_kwargs = { + "embedding_dim": 3, + "filename": str(tmp_path / "vdb.json"), + } + if vdb_backend_case.supports_sparse: + reload_kwargs["sparse_type"] = "bm42" + reloaded = type(vdb_storage)(**reload_kwargs) + else: + reloaded = vdb_backend_case.factory(tmp_path, monkeypatch) + results = await reloaded.query(Point(dense_embedding=np.array([1.0, 0.0, 0.0])), top_k=10) + + assert any(result.id == "id-persist" for result in results) + + +@pytest.mark.asyncio +async def test_vdb_contract_sparse_round_trip(vdb_backend_case, vdb_storage): + if not vdb_backend_case.supports_sparse: + pytest.skip("Backend does not support sparse vectors") + + await vdb_storage.upsert([ + Point( + id="id-hybrid", + dense_embedding=np.array([1.0, 0.0, 0.0]), + sparse_embedding=SparseEmbedding(indices=[7, 9], values=[1.0, 0.5]), + metadata={"tag": "hybrid"}, + ), + Point( + id="id-other", + dense_embedding=np.array([0.7, 0.7, 0.0]), + sparse_embedding=SparseEmbedding(indices=[3], values=[0.1]), + metadata={"tag": "other"}, + ), + ]) + + results = await vdb_storage.query( + Point( + dense_embedding=np.array([1.0, 0.0, 0.0]), + sparse_embedding=SparseEmbedding(indices=[7], values=[1.0]), + ), + top_k=10, + ) + points = await vdb_storage.get_points_by_ids(["id-hybrid"]) + + assert results[0].id == "id-hybrid" + assert points[0] is not None + assert points[0].sparse_embedding is not None + assert points[0].sparse_embedding.indices == [7, 9] + assert points[0].sparse_embedding.values == [1.0, 0.5] From bfaebf0e4353d71cf6817513b872e71d1537f1cb Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 27 Apr 2026 00:23:45 +0700 Subject: [PATCH 41/42] Update requirements --- pyproject.toml | 12 +- uv.lock | 3348 ++++++++++++++++++++---------------------------- 2 files changed, 1428 insertions(+), 1932 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 759c54b..0e93cf2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,18 +4,16 @@ build-backend = "setuptools.build_meta" [project] name = "graph_ragu" -version = "0.0.1" +version = "0.0.2" description = "GraphRAG system" requires-python = ">=3.10" dependencies = [ "aiolimiter>=1.2.1", - "graspologic>=3.4.4", "loguru>=0.7.3", "nano-vectordb>=0.0.4.3", "networkx>=3.4.2", "nltk>=3.9.2", - "numpy>=1.26.4", - "openai>=1.109.1", + "openai>=2.32.0", "pandas>=2.3.3", "pydantic>=2.11.10", "pydantic-settings>=2.11.0", @@ -25,6 +23,12 @@ dependencies = [ "tqdm>=4.67.1", "diskcache>=5.6.3", "jinja2>=3.1.6", + "qdrant-client>=1.12.1", + "fastembed==0.8.0", + "pymorphy3>=2.0.6", + "graspologic-native>=1.2.5", + "scikit-learn>=1.7.2", + "requests>=2.33.1", ] [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index f0a8047..cd6d8c1 100644 --- a/uv.lock +++ b/uv.lock @@ -2,7 +2,10 @@ version = 1 revision = 3 requires-python = ">=3.10" resolution-markers = [ - "python_full_version >= '3.12'", + "python_full_version >= '3.14' and platform_machine != 's390x'", + "python_full_version >= '3.14' and platform_machine == 's390x'", + "python_full_version == '3.13.*'", + "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", "python_full_version < '3.11'", ] @@ -13,7 +16,8 @@ version = "1.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "packaging" }, { name = "psutil" }, { name = "pyyaml" }, @@ -25,101 +29,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/d2/c581486aa6c4fbd7394c23c47b83fa1a919d34194e16944241daf9e762dd/accelerate-1.12.0-py3-none-any.whl", hash = "sha256:3e2091cd341423207e2f084a6654b1efcd250dc326f2a37d6dde446e07cabb11", size = 380935, upload-time = "2025-11-21T11:27:44.522Z" }, ] -[[package]] -name = "aiohappyeyeballs" -version = "2.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, -] - -[[package]] -name = "aiohttp" -version = "3.12.15" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohappyeyeballs" }, - { name = "aiosignal" }, - { name = "async-timeout", marker = "python_full_version < '3.11'" }, - { name = "attrs" }, - { name = "frozenlist" }, - { name = "multidict" }, - { name = "propcache" }, - { name = "yarl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/dc/ef9394bde9080128ad401ac7ede185267ed637df03b51f05d14d1c99ad67/aiohttp-3.12.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc", size = 703921, upload-time = "2025-07-29T05:49:43.584Z" }, - { url = "https://files.pythonhosted.org/packages/8f/42/63fccfc3a7ed97eb6e1a71722396f409c46b60a0552d8a56d7aad74e0df5/aiohttp-3.12.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af", size = 480288, upload-time = "2025-07-29T05:49:47.851Z" }, - { url = "https://files.pythonhosted.org/packages/9c/a2/7b8a020549f66ea2a68129db6960a762d2393248f1994499f8ba9728bbed/aiohttp-3.12.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421", size = 468063, upload-time = "2025-07-29T05:49:49.789Z" }, - { url = "https://files.pythonhosted.org/packages/8f/f5/d11e088da9176e2ad8220338ae0000ed5429a15f3c9dfd983f39105399cd/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79", size = 1650122, upload-time = "2025-07-29T05:49:51.874Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6b/b60ce2757e2faed3d70ed45dafee48cee7bfb878785a9423f7e883f0639c/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77", size = 1624176, upload-time = "2025-07-29T05:49:53.805Z" }, - { url = "https://files.pythonhosted.org/packages/dd/de/8c9fde2072a1b72c4fadecf4f7d4be7a85b1d9a4ab333d8245694057b4c6/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c", size = 1696583, upload-time = "2025-07-29T05:49:55.338Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ad/07f863ca3d895a1ad958a54006c6dafb4f9310f8c2fdb5f961b8529029d3/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4", size = 1738896, upload-time = "2025-07-29T05:49:57.045Z" }, - { url = "https://files.pythonhosted.org/packages/20/43/2bd482ebe2b126533e8755a49b128ec4e58f1a3af56879a3abdb7b42c54f/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6", size = 1643561, upload-time = "2025-07-29T05:49:58.762Z" }, - { url = "https://files.pythonhosted.org/packages/23/40/2fa9f514c4cf4cbae8d7911927f81a1901838baf5e09a8b2c299de1acfe5/aiohttp-3.12.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2", size = 1583685, upload-time = "2025-07-29T05:50:00.375Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c3/94dc7357bc421f4fb978ca72a201a6c604ee90148f1181790c129396ceeb/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d", size = 1627533, upload-time = "2025-07-29T05:50:02.306Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3f/1f8911fe1844a07001e26593b5c255a685318943864b27b4e0267e840f95/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb", size = 1638319, upload-time = "2025-07-29T05:50:04.282Z" }, - { url = "https://files.pythonhosted.org/packages/4e/46/27bf57a99168c4e145ffee6b63d0458b9c66e58bb70687c23ad3d2f0bd17/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5", size = 1613776, upload-time = "2025-07-29T05:50:05.863Z" }, - { url = "https://files.pythonhosted.org/packages/0f/7e/1d2d9061a574584bb4ad3dbdba0da90a27fdc795bc227def3a46186a8bc1/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b", size = 1693359, upload-time = "2025-07-29T05:50:07.563Z" }, - { url = "https://files.pythonhosted.org/packages/08/98/bee429b52233c4a391980a5b3b196b060872a13eadd41c3a34be9b1469ed/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065", size = 1716598, upload-time = "2025-07-29T05:50:09.33Z" }, - { url = "https://files.pythonhosted.org/packages/57/39/b0314c1ea774df3392751b686104a3938c63ece2b7ce0ba1ed7c0b4a934f/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1", size = 1644940, upload-time = "2025-07-29T05:50:11.334Z" }, - { url = "https://files.pythonhosted.org/packages/1b/83/3dacb8d3f8f512c8ca43e3fa8a68b20583bd25636ffa4e56ee841ffd79ae/aiohttp-3.12.15-cp310-cp310-win32.whl", hash = "sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a", size = 429239, upload-time = "2025-07-29T05:50:12.803Z" }, - { url = "https://files.pythonhosted.org/packages/eb/f9/470b5daba04d558c9673ca2034f28d067f3202a40e17804425f0c331c89f/aiohttp-3.12.15-cp310-cp310-win_amd64.whl", hash = "sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830", size = 452297, upload-time = "2025-07-29T05:50:14.266Z" }, - { url = "https://files.pythonhosted.org/packages/20/19/9e86722ec8e835959bd97ce8c1efa78cf361fa4531fca372551abcc9cdd6/aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117", size = 711246, upload-time = "2025-07-29T05:50:15.937Z" }, - { url = "https://files.pythonhosted.org/packages/71/f9/0a31fcb1a7d4629ac9d8f01f1cb9242e2f9943f47f5d03215af91c3c1a26/aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe", size = 483515, upload-time = "2025-07-29T05:50:17.442Z" }, - { url = "https://files.pythonhosted.org/packages/62/6c/94846f576f1d11df0c2e41d3001000527c0fdf63fce7e69b3927a731325d/aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9", size = 471776, upload-time = "2025-07-29T05:50:19.568Z" }, - { url = "https://files.pythonhosted.org/packages/f8/6c/f766d0aaafcee0447fad0328da780d344489c042e25cd58fde566bf40aed/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5", size = 1741977, upload-time = "2025-07-29T05:50:21.665Z" }, - { url = "https://files.pythonhosted.org/packages/17/e5/fb779a05ba6ff44d7bc1e9d24c644e876bfff5abe5454f7b854cace1b9cc/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728", size = 1690645, upload-time = "2025-07-29T05:50:23.333Z" }, - { url = "https://files.pythonhosted.org/packages/37/4e/a22e799c2035f5d6a4ad2cf8e7c1d1bd0923192871dd6e367dafb158b14c/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16", size = 1789437, upload-time = "2025-07-29T05:50:25.007Z" }, - { url = "https://files.pythonhosted.org/packages/28/e5/55a33b991f6433569babb56018b2fb8fb9146424f8b3a0c8ecca80556762/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0", size = 1828482, upload-time = "2025-07-29T05:50:26.693Z" }, - { url = "https://files.pythonhosted.org/packages/c6/82/1ddf0ea4f2f3afe79dffed5e8a246737cff6cbe781887a6a170299e33204/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b", size = 1730944, upload-time = "2025-07-29T05:50:28.382Z" }, - { url = "https://files.pythonhosted.org/packages/1b/96/784c785674117b4cb3877522a177ba1b5e4db9ce0fd519430b5de76eec90/aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd", size = 1668020, upload-time = "2025-07-29T05:50:30.032Z" }, - { url = "https://files.pythonhosted.org/packages/12/8a/8b75f203ea7e5c21c0920d84dd24a5c0e971fe1e9b9ebbf29ae7e8e39790/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8", size = 1716292, upload-time = "2025-07-29T05:50:31.983Z" }, - { url = "https://files.pythonhosted.org/packages/47/0b/a1451543475bb6b86a5cfc27861e52b14085ae232896a2654ff1231c0992/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50", size = 1711451, upload-time = "2025-07-29T05:50:33.989Z" }, - { url = "https://files.pythonhosted.org/packages/55/fd/793a23a197cc2f0d29188805cfc93aa613407f07e5f9da5cd1366afd9d7c/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676", size = 1691634, upload-time = "2025-07-29T05:50:35.846Z" }, - { url = "https://files.pythonhosted.org/packages/ca/bf/23a335a6670b5f5dfc6d268328e55a22651b440fca341a64fccf1eada0c6/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7", size = 1785238, upload-time = "2025-07-29T05:50:37.597Z" }, - { url = "https://files.pythonhosted.org/packages/57/4f/ed60a591839a9d85d40694aba5cef86dde9ee51ce6cca0bb30d6eb1581e7/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7", size = 1805701, upload-time = "2025-07-29T05:50:39.591Z" }, - { url = "https://files.pythonhosted.org/packages/85/e0/444747a9455c5de188c0f4a0173ee701e2e325d4b2550e9af84abb20cdba/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685", size = 1718758, upload-time = "2025-07-29T05:50:41.292Z" }, - { url = "https://files.pythonhosted.org/packages/36/ab/1006278d1ffd13a698e5dd4bfa01e5878f6bddefc296c8b62649753ff249/aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b", size = 428868, upload-time = "2025-07-29T05:50:43.063Z" }, - { url = "https://files.pythonhosted.org/packages/10/97/ad2b18700708452400278039272032170246a1bf8ec5d832772372c71f1a/aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d", size = 453273, upload-time = "2025-07-29T05:50:44.613Z" }, - { url = "https://files.pythonhosted.org/packages/63/97/77cb2450d9b35f517d6cf506256bf4f5bda3f93a66b4ad64ba7fc917899c/aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7", size = 702333, upload-time = "2025-07-29T05:50:46.507Z" }, - { url = "https://files.pythonhosted.org/packages/83/6d/0544e6b08b748682c30b9f65640d006e51f90763b41d7c546693bc22900d/aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444", size = 476948, upload-time = "2025-07-29T05:50:48.067Z" }, - { url = "https://files.pythonhosted.org/packages/3a/1d/c8c40e611e5094330284b1aea8a4b02ca0858f8458614fa35754cab42b9c/aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d", size = 469787, upload-time = "2025-07-29T05:50:49.669Z" }, - { url = "https://files.pythonhosted.org/packages/38/7d/b76438e70319796bfff717f325d97ce2e9310f752a267bfdf5192ac6082b/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c", size = 1716590, upload-time = "2025-07-29T05:50:51.368Z" }, - { url = "https://files.pythonhosted.org/packages/79/b1/60370d70cdf8b269ee1444b390cbd72ce514f0d1cd1a715821c784d272c9/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0", size = 1699241, upload-time = "2025-07-29T05:50:53.628Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2b/4968a7b8792437ebc12186db31523f541943e99bda8f30335c482bea6879/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab", size = 1754335, upload-time = "2025-07-29T05:50:55.394Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c1/49524ed553f9a0bec1a11fac09e790f49ff669bcd14164f9fab608831c4d/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb", size = 1800491, upload-time = "2025-07-29T05:50:57.202Z" }, - { url = "https://files.pythonhosted.org/packages/de/5e/3bf5acea47a96a28c121b167f5ef659cf71208b19e52a88cdfa5c37f1fcc/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545", size = 1719929, upload-time = "2025-07-29T05:50:59.192Z" }, - { url = "https://files.pythonhosted.org/packages/39/94/8ae30b806835bcd1cba799ba35347dee6961a11bd507db634516210e91d8/aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c", size = 1635733, upload-time = "2025-07-29T05:51:01.394Z" }, - { url = "https://files.pythonhosted.org/packages/7a/46/06cdef71dd03acd9da7f51ab3a9107318aee12ad38d273f654e4f981583a/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd", size = 1696790, upload-time = "2025-07-29T05:51:03.657Z" }, - { url = "https://files.pythonhosted.org/packages/02/90/6b4cfaaf92ed98d0ec4d173e78b99b4b1a7551250be8937d9d67ecb356b4/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f", size = 1718245, upload-time = "2025-07-29T05:51:05.911Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e6/2593751670fa06f080a846f37f112cbe6f873ba510d070136a6ed46117c6/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d", size = 1658899, upload-time = "2025-07-29T05:51:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/8f/28/c15bacbdb8b8eb5bf39b10680d129ea7410b859e379b03190f02fa104ffd/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519", size = 1738459, upload-time = "2025-07-29T05:51:09.56Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/c269cbc4faa01fb10f143b1670633a8ddd5b2e1ffd0548f7aa49cb5c70e2/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea", size = 1766434, upload-time = "2025-07-29T05:51:11.423Z" }, - { url = "https://files.pythonhosted.org/packages/52/b0/4ff3abd81aa7d929b27d2e1403722a65fc87b763e3a97b3a2a494bfc63bc/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3", size = 1726045, upload-time = "2025-07-29T05:51:13.689Z" }, - { url = "https://files.pythonhosted.org/packages/71/16/949225a6a2dd6efcbd855fbd90cf476052e648fb011aa538e3b15b89a57a/aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1", size = 423591, upload-time = "2025-07-29T05:51:15.452Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d8/fa65d2a349fe938b76d309db1a56a75c4fb8cc7b17a398b698488a939903/aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34", size = 450266, upload-time = "2025-07-29T05:51:17.239Z" }, - { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, - { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, - { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, - { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, - { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, - { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, - { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, - { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, - { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, - { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, - { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, - { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, - { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, -] - [[package]] name = "aiolimiter" version = "1.2.1" @@ -130,16 +39,12 @@ wheels = [ ] [[package]] -name = "aiosignal" -version = "1.4.0" +name = "annotated-doc" +version = "0.0.4" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, ] [[package]] @@ -166,45 +71,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, ] -[[package]] -name = "anytree" -version = "2.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/a8/eb55fab589c56f9b6be2b3fd6997aa04bb6f3da93b01154ce6fc8e799db2/anytree-2.13.0.tar.gz", hash = "sha256:c9d3aa6825fdd06af7ebb05b4ef291d2db63e62bb1f9b7d9b71354be9d362714", size = 48389, upload-time = "2025-04-08T21:06:30.662Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/98/f6aa7fe0783e42be3093d8ef1b0ecdc22c34c0d69640dfb37f56925cb141/anytree-2.13.0-py3-none-any.whl", hash = "sha256:4cbcf10df36b1f1cba131b7e487ff3edafc9d6e932a3c70071b5b768bab901ff", size = 45077, upload-time = "2025-04-08T21:06:29.494Z" }, -] - -[[package]] -name = "async-timeout" -version = "5.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, -] - -[[package]] -name = "attrs" -version = "25.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, -] - -[[package]] -name = "autograd" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/67/1c/3c24ec03c8ba4decc742b1df5a10c52f98c84ca8797757f313e7bdcdf276/autograd-1.8.0.tar.gz", hash = "sha256:107374ded5b09fc8643ac925348c0369e7b0e73bbed9565ffd61b8fd04425683", size = 2562146, upload-time = "2025-05-05T12:49:02.502Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/ea/e16f0c423f7d83cf8b79cae9452040fb7b2e020c7439a167ee7c317de448/autograd-1.8.0-py3-none-any.whl", hash = "sha256:4ab9084294f814cf56c280adbe19612546a35574d67c574b04933c7d2ecb7d78", size = 51478, upload-time = "2025-05-05T12:49:00.585Z" }, -] - [[package]] name = "backports-asyncio-runner" version = "1.2.0" @@ -214,15 +80,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, ] -[[package]] -name = "beartype" -version = "0.18.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/15/4e623478a9628ad4cee2391f19aba0b16c1dd6fedcb2a399f0928097b597/beartype-0.18.5.tar.gz", hash = "sha256:264ddc2f1da9ec94ff639141fbe33d22e12a9f75aa863b83b7046ffff1381927", size = 1193506, upload-time = "2024-04-21T07:25:58.64Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/43/7a1259741bd989723272ac7d381a43be932422abcff09a1d9f7ba212cb74/beartype-0.18.5-py3-none-any.whl", hash = "sha256:5301a14f2a9a5540fe47ec6d34d758e9cd8331d36c4760fc7a5499ab86310089", size = 917762, upload-time = "2024-04-21T07:25:55.758Z" }, -] - [[package]] name = "certifi" version = "2025.10.5" @@ -318,258 +175,128 @@ wheels = [ ] [[package]] -name = "contourpy" -version = "1.3.2" +name = "coloredlogs" +version = "15.0.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] dependencies = [ - { name = "numpy", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, - { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, - { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, - { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, - { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, - { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, - { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, - { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, - { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, - { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, - { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, - { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, - { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, - { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, - { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, - { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, - { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, - { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, - { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, - { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, - { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, - { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, - { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, - { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, - { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, - { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, - { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, - { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, - { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, - { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, - { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, - { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, - { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, - { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, - { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, - { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, - { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, - { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, - { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, - { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, - { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, - { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, - { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, - { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, - { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, - { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, - { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, - { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, -] - -[[package]] -name = "contourpy" -version = "1.3.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", + { name = "humanfriendly", marker = "python_full_version < '3.13'" }, ] -dependencies = [ - { name = "numpy", marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, - { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, - { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, - { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, - { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, - { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, - { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, - { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, - { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, - { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, - { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, - { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, - { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, - { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, - { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, - { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, - { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, - { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, - { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, - { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, - { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, - { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, - { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, - { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, - { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, - { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, - { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, - { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, - { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, - { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, - { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, - { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, - { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, - { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, - { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, - { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, - { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, - { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, - { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, - { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, - { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, - { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, - { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, - { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, - { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, - { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, - { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, - { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, - { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, - { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, - { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, - { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, - { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, - { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, - { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, - { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, - { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" }, ] [[package]] name = "coverage" -version = "7.13.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/9a/3742e58fd04b233df95c012ee9f3dfe04708a5e1d32613bd2d47d4e1be0d/coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147", size = 218633, upload-time = "2025-12-28T15:40:10.165Z" }, - { url = "https://files.pythonhosted.org/packages/7e/45/7e6bdc94d89cd7c8017ce735cf50478ddfe765d4fbf0c24d71d30ea33d7a/coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d", size = 219147, upload-time = "2025-12-28T15:40:12.069Z" }, - { url = "https://files.pythonhosted.org/packages/f7/38/0d6a258625fd7f10773fe94097dc16937a5f0e3e0cdf3adef67d3ac6baef/coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0", size = 245894, upload-time = "2025-12-28T15:40:13.556Z" }, - { url = "https://files.pythonhosted.org/packages/27/58/409d15ea487986994cbd4d06376e9860e9b157cfbfd402b1236770ab8dd2/coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90", size = 247721, upload-time = "2025-12-28T15:40:15.37Z" }, - { url = "https://files.pythonhosted.org/packages/da/bf/6e8056a83fd7a96c93341f1ffe10df636dd89f26d5e7b9ca511ce3bcf0df/coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d", size = 249585, upload-time = "2025-12-28T15:40:17.226Z" }, - { url = "https://files.pythonhosted.org/packages/f4/15/e1daff723f9f5959acb63cbe35b11203a9df77ee4b95b45fffd38b318390/coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b", size = 246597, upload-time = "2025-12-28T15:40:19.028Z" }, - { url = "https://files.pythonhosted.org/packages/74/a6/1efd31c5433743a6ddbc9d37ac30c196bb07c7eab3d74fbb99b924c93174/coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6", size = 247626, upload-time = "2025-12-28T15:40:20.846Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9f/1609267dd3e749f57fdd66ca6752567d1c13b58a20a809dc409b263d0b5f/coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e", size = 245629, upload-time = "2025-12-28T15:40:22.397Z" }, - { url = "https://files.pythonhosted.org/packages/e2/f6/6815a220d5ec2466383d7cc36131b9fa6ecbe95c50ec52a631ba733f306a/coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae", size = 245901, upload-time = "2025-12-28T15:40:23.836Z" }, - { url = "https://files.pythonhosted.org/packages/ac/58/40576554cd12e0872faf6d2c0eb3bc85f71d78427946ddd19ad65201e2c0/coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29", size = 246505, upload-time = "2025-12-28T15:40:25.421Z" }, - { url = "https://files.pythonhosted.org/packages/3b/77/9233a90253fba576b0eee81707b5781d0e21d97478e5377b226c5b096c0f/coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f", size = 221257, upload-time = "2025-12-28T15:40:27.217Z" }, - { url = "https://files.pythonhosted.org/packages/e0/43/e842ff30c1a0a623ec80db89befb84a3a7aad7bfe44a6ea77d5a3e61fedd/coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1", size = 222191, upload-time = "2025-12-28T15:40:28.916Z" }, - { url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" }, - { url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" }, - { url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" }, - { url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" }, - { url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" }, - { url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" }, - { url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" }, - { url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" }, - { url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" }, - { url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" }, - { url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" }, - { url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" }, - { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" }, - { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" }, - { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" }, - { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" }, - { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" }, - { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" }, - { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" }, - { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" }, - { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" }, - { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78", size = 218951, upload-time = "2025-12-28T15:41:16.653Z" }, - { url = "https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b", size = 219325, upload-time = "2025-12-28T15:41:18.609Z" }, - { url = "https://files.pythonhosted.org/packages/59/f6/efdabdb4929487baeb7cb2a9f7dac457d9356f6ad1b255be283d58b16316/coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd", size = 250309, upload-time = "2025-12-28T15:41:20.629Z" }, - { url = "https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992", size = 252907, upload-time = "2025-12-28T15:41:22.257Z" }, - { url = "https://files.pythonhosted.org/packages/75/38/f1ea837e3dc1231e086db1638947e00d264e7e8c41aa8ecacf6e1e0c05f4/coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4", size = 254148, upload-time = "2025-12-28T15:41:23.87Z" }, - { url = "https://files.pythonhosted.org/packages/7f/43/f4f16b881aaa34954ba446318dea6b9ed5405dd725dd8daac2358eda869a/coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a", size = 250515, upload-time = "2025-12-28T15:41:25.437Z" }, - { url = "https://files.pythonhosted.org/packages/84/34/8cba7f00078bd468ea914134e0144263194ce849ec3baad187ffb6203d1c/coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766", size = 252292, upload-time = "2025-12-28T15:41:28.459Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a4/cffac66c7652d84ee4ac52d3ccb94c015687d3b513f9db04bfcac2ac800d/coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4", size = 250242, upload-time = "2025-12-28T15:41:30.02Z" }, - { url = "https://files.pythonhosted.org/packages/f4/78/9a64d462263dde416f3c0067efade7b52b52796f489b1037a95b0dc389c9/coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398", size = 250068, upload-time = "2025-12-28T15:41:32.007Z" }, - { url = "https://files.pythonhosted.org/packages/69/c8/a8994f5fece06db7c4a97c8fc1973684e178599b42e66280dded0524ef00/coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784", size = 251846, upload-time = "2025-12-28T15:41:33.946Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f7/91fa73c4b80305c86598a2d4e54ba22df6bf7d0d97500944af7ef155d9f7/coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461", size = 221512, upload-time = "2025-12-28T15:41:35.519Z" }, - { url = "https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500", size = 222321, upload-time = "2025-12-28T15:41:37.371Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b8/bdcb7253b7e85157282450262008f1366aa04663f3e3e4c30436f596c3e2/coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9", size = 220949, upload-time = "2025-12-28T15:41:39.553Z" }, - { url = "https://files.pythonhosted.org/packages/70/52/f2be52cc445ff75ea8397948c96c1b4ee14f7f9086ea62fc929c5ae7b717/coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc", size = 219643, upload-time = "2025-12-28T15:41:41.567Z" }, - { url = "https://files.pythonhosted.org/packages/47/79/c85e378eaa239e2edec0c5523f71542c7793fe3340954eafb0bc3904d32d/coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a", size = 219997, upload-time = "2025-12-28T15:41:43.418Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9b/b1ade8bfb653c0bbce2d6d6e90cc6c254cbb99b7248531cc76253cb4da6d/coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4", size = 261296, upload-time = "2025-12-28T15:41:45.207Z" }, - { url = "https://files.pythonhosted.org/packages/1f/af/ebf91e3e1a2473d523e87e87fd8581e0aa08741b96265730e2d79ce78d8d/coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6", size = 263363, upload-time = "2025-12-28T15:41:47.163Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8b/fb2423526d446596624ac7fde12ea4262e66f86f5120114c3cfd0bb2befa/coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1", size = 265783, upload-time = "2025-12-28T15:41:49.03Z" }, - { url = "https://files.pythonhosted.org/packages/9b/26/ef2adb1e22674913b89f0fe7490ecadcef4a71fa96f5ced90c60ec358789/coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd", size = 260508, upload-time = "2025-12-28T15:41:51.035Z" }, - { url = "https://files.pythonhosted.org/packages/ce/7d/f0f59b3404caf662e7b5346247883887687c074ce67ba453ea08c612b1d5/coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c", size = 263357, upload-time = "2025-12-28T15:41:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b1/29896492b0b1a047604d35d6fa804f12818fa30cdad660763a5f3159e158/coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0", size = 260978, upload-time = "2025-12-28T15:41:54.589Z" }, - { url = "https://files.pythonhosted.org/packages/48/f2/971de1238a62e6f0a4128d37adadc8bb882ee96afbe03ff1570291754629/coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e", size = 259877, upload-time = "2025-12-28T15:41:56.263Z" }, - { url = "https://files.pythonhosted.org/packages/6a/fc/0474efcbb590ff8628830e9aaec5f1831594874360e3251f1fdec31d07a3/coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53", size = 262069, upload-time = "2025-12-28T15:41:58.093Z" }, - { url = "https://files.pythonhosted.org/packages/88/4f/3c159b7953db37a7b44c0eab8a95c37d1aa4257c47b4602c04022d5cb975/coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842", size = 222184, upload-time = "2025-12-28T15:41:59.763Z" }, - { url = "https://files.pythonhosted.org/packages/58/a5/6b57d28f81417f9335774f20679d9d13b9a8fb90cd6160957aa3b54a2379/coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2", size = 223250, upload-time = "2025-12-28T15:42:01.52Z" }, - { url = "https://files.pythonhosted.org/packages/81/7c/160796f3b035acfbb58be80e02e484548595aa67e16a6345e7910ace0a38/coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09", size = 221521, upload-time = "2025-12-28T15:42:03.275Z" }, - { url = "https://files.pythonhosted.org/packages/aa/8e/ba0e597560c6563fc0adb902fda6526df5d4aa73bb10adf0574d03bd2206/coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", size = 218996, upload-time = "2025-12-28T15:42:04.978Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8e/764c6e116f4221dc7aa26c4061181ff92edb9c799adae6433d18eeba7a14/coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", size = 219326, upload-time = "2025-12-28T15:42:06.691Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a6/6130dc6d8da28cdcbb0f2bf8865aeca9b157622f7c0031e48c6cf9a0e591/coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", size = 250374, upload-time = "2025-12-28T15:42:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/82/2b/783ded568f7cd6b677762f780ad338bf4b4750205860c17c25f7c708995e/coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", size = 252882, upload-time = "2025-12-28T15:42:10.515Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b2/9808766d082e6a4d59eb0cc881a57fc1600eb2c5882813eefff8254f71b5/coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", size = 254218, upload-time = "2025-12-28T15:42:12.208Z" }, - { url = "https://files.pythonhosted.org/packages/44/ea/52a985bb447c871cb4d2e376e401116520991b597c85afdde1ea9ef54f2c/coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", size = 250391, upload-time = "2025-12-28T15:42:14.21Z" }, - { url = "https://files.pythonhosted.org/packages/7f/1d/125b36cc12310718873cfc8209ecfbc1008f14f4f5fa0662aa608e579353/coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", size = 252239, upload-time = "2025-12-28T15:42:16.292Z" }, - { url = "https://files.pythonhosted.org/packages/6a/16/10c1c164950cade470107f9f14bbac8485f8fb8515f515fca53d337e4a7f/coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", size = 250196, upload-time = "2025-12-28T15:42:18.54Z" }, - { url = "https://files.pythonhosted.org/packages/2a/c6/cd860fac08780c6fd659732f6ced1b40b79c35977c1356344e44d72ba6c4/coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", size = 250008, upload-time = "2025-12-28T15:42:20.365Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/a8c58d3d38f82a5711e1e0a67268362af48e1a03df27c03072ac30feefcf/coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", size = 251671, upload-time = "2025-12-28T15:42:22.114Z" }, - { url = "https://files.pythonhosted.org/packages/f0/bc/fd4c1da651d037a1e3d53e8cb3f8182f4b53271ffa9a95a2e211bacc0349/coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", size = 221777, upload-time = "2025-12-28T15:42:23.919Z" }, - { url = "https://files.pythonhosted.org/packages/4b/50/71acabdc8948464c17e90b5ffd92358579bd0910732c2a1c9537d7536aa6/coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", size = 222592, upload-time = "2025-12-28T15:42:25.619Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c8/a6fb943081bb0cc926499c7907731a6dc9efc2cbdc76d738c0ab752f1a32/coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", size = 221169, upload-time = "2025-12-28T15:42:27.629Z" }, - { url = "https://files.pythonhosted.org/packages/16/61/d5b7a0a0e0e40d62e59bc8c7aa1afbd86280d82728ba97f0673b746b78e2/coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", size = 219730, upload-time = "2025-12-28T15:42:29.306Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2c/8881326445fd071bb49514d1ce97d18a46a980712b51fee84f9ab42845b4/coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", size = 220001, upload-time = "2025-12-28T15:42:31.319Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d7/50de63af51dfa3a7f91cc37ad8fcc1e244b734232fbc8b9ab0f3c834a5cd/coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", size = 261370, upload-time = "2025-12-28T15:42:32.992Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2c/d31722f0ec918fd7453b2758312729f645978d212b410cd0f7c2aed88a94/coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", size = 263485, upload-time = "2025-12-28T15:42:34.759Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7a/2c114fa5c5fc08ba0777e4aec4c97e0b4a1afcb69c75f1f54cff78b073ab/coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", size = 265890, upload-time = "2025-12-28T15:42:36.517Z" }, - { url = "https://files.pythonhosted.org/packages/65/d9/f0794aa1c74ceabc780fe17f6c338456bbc4e96bd950f2e969f48ac6fb20/coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", size = 260445, upload-time = "2025-12-28T15:42:38.646Z" }, - { url = "https://files.pythonhosted.org/packages/49/23/184b22a00d9bb97488863ced9454068c79e413cb23f472da6cbddc6cfc52/coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", size = 263357, upload-time = "2025-12-28T15:42:40.788Z" }, - { url = "https://files.pythonhosted.org/packages/7d/bd/58af54c0c9199ea4190284f389005779d7daf7bf3ce40dcd2d2b2f96da69/coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", size = 260959, upload-time = "2025-12-28T15:42:42.808Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2a/6839294e8f78a4891bf1df79d69c536880ba2f970d0ff09e7513d6e352e9/coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", size = 259792, upload-time = "2025-12-28T15:42:44.818Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c3/528674d4623283310ad676c5af7414b9850ab6d55c2300e8aa4b945ec554/coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", size = 262123, upload-time = "2025-12-28T15:42:47.108Z" }, - { url = "https://files.pythonhosted.org/packages/06/c5/8c0515692fb4c73ac379d8dc09b18eaf0214ecb76ea6e62467ba7a1556ff/coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", size = 222562, upload-time = "2025-12-28T15:42:49.144Z" }, - { url = "https://files.pythonhosted.org/packages/05/0e/c0a0c4678cb30dac735811db529b321d7e1c9120b79bd728d4f4d6b010e9/coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", size = 223670, upload-time = "2025-12-28T15:42:51.218Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/b177aa0011f354abf03a8f30a85032686d290fdeed4222b27d36b4372a50/coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", size = 221707, upload-time = "2025-12-28T15:42:53.034Z" }, - { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, +version = "7.13.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/33/e8c48488c29a73fd089f9d71f9653c1be7478f2ad6b5bc870db11a55d23d/coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5", size = 219255, upload-time = "2026-03-17T10:29:51.081Z" }, + { url = "https://files.pythonhosted.org/packages/da/bd/b0ebe9f677d7f4b74a3e115eec7ddd4bcf892074963a00d91e8b164a6386/coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf", size = 219772, upload-time = "2026-03-17T10:29:52.867Z" }, + { url = "https://files.pythonhosted.org/packages/48/cc/5cb9502f4e01972f54eedd48218bb203fe81e294be606a2bc93970208013/coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8", size = 246532, upload-time = "2026-03-17T10:29:54.688Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d8/3217636d86c7e7b12e126e4f30ef1581047da73140614523af7495ed5f2d/coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4", size = 248333, upload-time = "2026-03-17T10:29:56.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/30/2002ac6729ba2d4357438e2ed3c447ad8562866c8c63fc16f6dfc33afe56/coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d", size = 250211, upload-time = "2026-03-17T10:29:57.938Z" }, + { url = "https://files.pythonhosted.org/packages/6c/85/552496626d6b9359eb0e2f86f920037c9cbfba09b24d914c6e1528155f7d/coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930", size = 252125, upload-time = "2026-03-17T10:29:59.388Z" }, + { url = "https://files.pythonhosted.org/packages/44/21/40256eabdcbccdb6acf6b381b3016a154399a75fe39d406f790ae84d1f3c/coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d", size = 247219, upload-time = "2026-03-17T10:30:01.199Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/96e2a6c3f21a0ea77d7830b254a1542d0328acc8d7bdf6a284ba7e529f77/coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40", size = 248248, upload-time = "2026-03-17T10:30:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/da/ba/8477f549e554827da390ec659f3c38e4b6d95470f4daafc2d8ff94eaa9c2/coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878", size = 246254, upload-time = "2026-03-17T10:30:04.832Z" }, + { url = "https://files.pythonhosted.org/packages/55/59/bc22aef0e6aa179d5b1b001e8b3654785e9adf27ef24c93dc4228ebd5d68/coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400", size = 250067, upload-time = "2026-03-17T10:30:06.535Z" }, + { url = "https://files.pythonhosted.org/packages/de/1b/c6a023a160806a5137dca53468fd97530d6acad24a22003b1578a9c2e429/coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0", size = 246521, upload-time = "2026-03-17T10:30:08.486Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3f/3532c85a55aa2f899fa17c186f831cfa1aa434d88ff792a709636f64130e/coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0", size = 247126, upload-time = "2026-03-17T10:30:09.966Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2e/b9d56af4a24ef45dfbcda88e06870cb7d57b2b0bfa3a888d79b4c8debd76/coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58", size = 221860, upload-time = "2026-03-17T10:30:11.393Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cc/d938417e7a4d7f0433ad4edee8bb2acdc60dc7ac5af19e2a07a048ecbee3/coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e", size = 222788, upload-time = "2026-03-17T10:30:12.886Z" }, + { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, + { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, + { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, + { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, + { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, + { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, + { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, + { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, + { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, + { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, + { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, + { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, + { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, + { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, + { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, + { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, ] [package.optional-dependencies] @@ -578,12 +305,88 @@ toml = [ ] [[package]] -name = "cycler" -version = "0.12.1" +name = "cuda-bindings" +version = "13.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/fe/7351d7e586a8b4c9f89731bfe4cf0148223e8f9903ff09571f78b3fb0682/cuda_bindings-13.2.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08b395f79cb89ce0cd8effff07c4a1e20101b873c256a1aeb286e8fd7bd0f556", size = 5744254, upload-time = "2026-03-11T00:12:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ef/184aa775e970fc089942cd9ec6302e6e44679d4c14549c6a7ea45bf7f798/cuda_bindings-13.2.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6f3682ec3c4769326aafc67c2ba669d97d688d0b7e63e659d36d2f8b72f32d6", size = 6329075, upload-time = "2026-03-11T00:12:32.319Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a9/3a8241c6e19483ac1f1dcf5c10238205dcb8a6e9d0d4d4709240dff28ff4/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:721104c603f059780d287969be3d194a18d0cc3b713ed9049065a1107706759d", size = 5730273, upload-time = "2026-03-11T00:12:37.18Z" }, + { url = "https://files.pythonhosted.org/packages/e9/94/2748597f47bb1600cd466b20cab4159f1530a3a33fe7f70fee199b3abb9e/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1eba9504ac70667dd48313395fe05157518fd6371b532790e96fbb31bbb5a5e1", size = 6313924, upload-time = "2026-03-11T00:12:39.462Z" }, + { url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404, upload-time = "2026-03-11T00:12:44.041Z" }, + { url = "https://files.pythonhosted.org/packages/1f/92/f899f7bbb5617bb65ec52a6eac1e9a1447a86b916c4194f8a5001b8cde0c/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955", size = 6320619, upload-time = "2026-03-11T00:12:45.939Z" }, + { url = "https://files.pythonhosted.org/packages/df/93/eef988860a3ca985f82c4f3174fc0cdd94e07331ba9a92e8e064c260337f/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0", size = 5614610, upload-time = "2026-03-11T00:12:50.337Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d", size = 6149914, upload-time = "2026-03-11T00:12:52.374Z" }, + { url = "https://files.pythonhosted.org/packages/c0/87/87a014f045b77c6de5c8527b0757fe644417b184e5367db977236a141602/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e", size = 5685673, upload-time = "2026-03-11T00:12:56.371Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5e/c0fe77a73aaefd3fff25ffaccaac69c5a63eafdf8b9a4c476626ef0ac703/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626", size = 6191386, upload-time = "2026-03-11T00:12:58.965Z" }, + { url = "https://files.pythonhosted.org/packages/5f/58/ed2c3b39c8dd5f96aa7a4abef0d47a73932c7a988e30f5fa428f00ed0da1/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771", size = 5507469, upload-time = "2026-03-11T00:13:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/0c941b112ceeb21439b05895eace78ca1aa2eaaf695c8521a068fd9b4c00/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b", size = 6059693, upload-time = "2026-03-11T00:13:06.003Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/d6/ac63065d33dd700fee7ebd7d287332401b54e31b9346e142f871e1f0b116/cuda_pathfinder-1.5.3-py3-none-any.whl", hash = "sha256:dff021123aedbb4117cc7ec81717bbfe198fb4e8b5f1ee57e0e084fec5c8577d", size = 49991, upload-time = "2026-04-14T20:09:27.037Z" }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364, upload-time = "2025-12-19T23:24:07.328Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "sys_platform == 'linux'" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +curand = [ + { name = "nvidia-curand", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cusolver = [ + { name = "nvidia-cusolver", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] + +[[package]] +name = "dawg2-python" +version = "0.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/03/85171ce1e59088237aebf21943d1136463f6422820f096ac8cf9322aa851/dawg2_python-0.9.0.tar.gz", hash = "sha256:adea0312acd1a958659e8448ce6899046c0858d0b6c8949a51eebdeb5a113e4a", size = 10278, upload-time = "2025-02-17T13:22:24.261Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, + { url = "https://files.pythonhosted.org/packages/84/3b/7fb4c1a8df59cb80f5f7ecb9646280e000f9ba2ccff8710205dc9aa4604f/dawg2_python-0.9.0-py3-none-any.whl", hash = "sha256:4fab6fc097bd176cd783cd8421b757348ea5a460789e53b0f6bb64831380bab5", size = 9331, upload-time = "2025-02-17T13:22:22.858Z" }, ] [[package]] @@ -604,15 +407,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] -[[package]] -name = "docstring-parser" -version = "0.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, -] - [[package]] name = "exceptiongroup" version = "1.3.0" @@ -625,6 +419,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, ] +[[package]] +name = "fastembed" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "loguru" }, + { name = "mmh3" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "onnxruntime", version = "1.19.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "onnxruntime", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "pillow", version = "10.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "pillow", version = "12.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "py-rust-stemmers" }, + { name = "requests" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/25/58865e36b6e8a9a0d0ff905b5601aa30db97956327c0df42ec4ed6accc21/fastembed-0.8.0.tar.gz", hash = "sha256:75966edfa8b006ee78514c726bd7f6a50721dadc89305279052be9db72fd53e8", size = 75115, upload-time = "2026-03-23T16:34:41.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e8/26b7d78bb8972498c467ca34cb12ee2e60d26ba5eae6d8443189a1af37a5/fastembed-0.8.0-py3-none-any.whl", hash = "sha256:40bee672657574a1009e35ec50030a55f2b426842cb011845379817641bbbbd0", size = 116572, upload-time = "2026-03-23T16:34:40.69Z" }, +] + [[package]] name = "filelock" version = "3.19.1" @@ -635,181 +453,11 @@ wheels = [ ] [[package]] -name = "fonttools" -version = "4.60.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823, upload-time = "2025-09-29T21:13:27.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/70/03e9d89a053caff6ae46053890eba8e4a5665a7c5638279ed4492e6d4b8b/fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28", size = 2810747, upload-time = "2025-09-29T21:10:59.653Z" }, - { url = "https://files.pythonhosted.org/packages/6f/41/449ad5aff9670ab0df0f61ee593906b67a36d7e0b4d0cd7fa41ac0325bf5/fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15", size = 2346909, upload-time = "2025-09-29T21:11:02.882Z" }, - { url = "https://files.pythonhosted.org/packages/9a/18/e5970aa96c8fad1cb19a9479cc3b7602c0c98d250fcdc06a5da994309c50/fonttools-4.60.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee0c0b3b35b34f782afc673d503167157094a16f442ace7c6c5e0ca80b08f50c", size = 4864572, upload-time = "2025-09-29T21:11:05.096Z" }, - { url = "https://files.pythonhosted.org/packages/ce/20/9b2b4051b6ec6689480787d506b5003f72648f50972a92d04527a456192c/fonttools-4.60.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:282dafa55f9659e8999110bd8ed422ebe1c8aecd0dc396550b038e6c9a08b8ea", size = 4794635, upload-time = "2025-09-29T21:11:08.651Z" }, - { url = "https://files.pythonhosted.org/packages/10/52/c791f57347c1be98f8345e3dca4ac483eb97666dd7c47f3059aeffab8b59/fonttools-4.60.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4ba4bd646e86de16160f0fb72e31c3b9b7d0721c3e5b26b9fa2fc931dfdb2652", size = 4843878, upload-time = "2025-09-29T21:11:10.893Z" }, - { url = "https://files.pythonhosted.org/packages/69/e9/35c24a8d01644cee8c090a22fad34d5b61d1e0a8ecbc9945ad785ebf2e9e/fonttools-4.60.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0b0835ed15dd5b40d726bb61c846a688f5b4ce2208ec68779bc81860adb5851a", size = 4954555, upload-time = "2025-09-29T21:11:13.24Z" }, - { url = "https://files.pythonhosted.org/packages/f7/86/fb1e994971be4bdfe3a307de6373ef69a9df83fb66e3faa9c8114893d4cc/fonttools-4.60.1-cp310-cp310-win32.whl", hash = "sha256:1525796c3ffe27bb6268ed2a1bb0dcf214d561dfaf04728abf01489eb5339dce", size = 2232019, upload-time = "2025-09-29T21:11:15.73Z" }, - { url = "https://files.pythonhosted.org/packages/40/84/62a19e2bd56f0e9fb347486a5b26376bade4bf6bbba64dda2c103bd08c94/fonttools-4.60.1-cp310-cp310-win_amd64.whl", hash = "sha256:268ecda8ca6cb5c4f044b1fb9b3b376e8cd1b361cef275082429dc4174907038", size = 2276803, upload-time = "2025-09-29T21:11:18.152Z" }, - { url = "https://files.pythonhosted.org/packages/ea/85/639aa9bface1537e0fb0f643690672dde0695a5bbbc90736bc571b0b1941/fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f", size = 2831872, upload-time = "2025-09-29T21:11:20.329Z" }, - { url = "https://files.pythonhosted.org/packages/6b/47/3c63158459c95093be9618794acb1067b3f4d30dcc5c3e8114b70e67a092/fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2", size = 2356990, upload-time = "2025-09-29T21:11:22.754Z" }, - { url = "https://files.pythonhosted.org/packages/94/dd/1934b537c86fcf99f9761823f1fc37a98fbd54568e8e613f29a90fed95a9/fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914", size = 5042189, upload-time = "2025-09-29T21:11:25.061Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d2/9f4e4c4374dd1daa8367784e1bd910f18ba886db1d6b825b12edf6db3edc/fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1", size = 4978683, upload-time = "2025-09-29T21:11:27.693Z" }, - { url = "https://files.pythonhosted.org/packages/cc/c4/0fb2dfd1ecbe9a07954cc13414713ed1eab17b1c0214ef07fc93df234a47/fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d", size = 5021372, upload-time = "2025-09-29T21:11:30.257Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d5/495fc7ae2fab20223cc87179a8f50f40f9a6f821f271ba8301ae12bb580f/fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa", size = 5132562, upload-time = "2025-09-29T21:11:32.737Z" }, - { url = "https://files.pythonhosted.org/packages/bc/fa/021dab618526323c744e0206b3f5c8596a2e7ae9aa38db5948a131123e83/fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258", size = 2230288, upload-time = "2025-09-29T21:11:35.015Z" }, - { url = "https://files.pythonhosted.org/packages/bb/78/0e1a6d22b427579ea5c8273e1c07def2f325b977faaf60bb7ddc01456cb1/fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf", size = 2278184, upload-time = "2025-09-29T21:11:37.434Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f7/a10b101b7a6f8836a5adb47f2791f2075d044a6ca123f35985c42edc82d8/fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc", size = 2832953, upload-time = "2025-09-29T21:11:39.616Z" }, - { url = "https://files.pythonhosted.org/packages/ed/fe/7bd094b59c926acf2304d2151354ddbeb74b94812f3dc943c231db09cb41/fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877", size = 2352706, upload-time = "2025-09-29T21:11:41.826Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ca/4bb48a26ed95a1e7eba175535fe5805887682140ee0a0d10a88e1de84208/fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c", size = 4923716, upload-time = "2025-09-29T21:11:43.893Z" }, - { url = "https://files.pythonhosted.org/packages/b8/9f/2cb82999f686c1d1ddf06f6ae1a9117a880adbec113611cc9d22b2fdd465/fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401", size = 4968175, upload-time = "2025-09-29T21:11:46.439Z" }, - { url = "https://files.pythonhosted.org/packages/18/79/be569699e37d166b78e6218f2cde8c550204f2505038cdd83b42edc469b9/fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903", size = 4911031, upload-time = "2025-09-29T21:11:48.977Z" }, - { url = "https://files.pythonhosted.org/packages/cc/9f/89411cc116effaec5260ad519162f64f9c150e5522a27cbb05eb62d0c05b/fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed", size = 5062966, upload-time = "2025-09-29T21:11:54.344Z" }, - { url = "https://files.pythonhosted.org/packages/62/a1/f888221934b5731d46cb9991c7a71f30cb1f97c0ef5fcf37f8da8fce6c8e/fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6", size = 2218750, upload-time = "2025-09-29T21:11:56.601Z" }, - { url = "https://files.pythonhosted.org/packages/88/8f/a55b5550cd33cd1028601df41acd057d4be20efa5c958f417b0c0613924d/fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383", size = 2267026, upload-time = "2025-09-29T21:11:58.852Z" }, - { url = "https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb", size = 2825777, upload-time = "2025-09-29T21:12:01.22Z" }, - { url = "https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4", size = 2348080, upload-time = "2025-09-29T21:12:03.785Z" }, - { url = "https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c", size = 4903082, upload-time = "2025-09-29T21:12:06.382Z" }, - { url = "https://files.pythonhosted.org/packages/04/05/06b1455e4bc653fcb2117ac3ef5fa3a8a14919b93c60742d04440605d058/fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77", size = 4960125, upload-time = "2025-09-29T21:12:09.314Z" }, - { url = "https://files.pythonhosted.org/packages/8e/37/f3b840fcb2666f6cb97038793606bdd83488dca2d0b0fc542ccc20afa668/fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199", size = 4901454, upload-time = "2025-09-29T21:12:11.931Z" }, - { url = "https://files.pythonhosted.org/packages/fd/9e/eb76f77e82f8d4a46420aadff12cec6237751b0fb9ef1de373186dcffb5f/fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c", size = 5044495, upload-time = "2025-09-29T21:12:15.241Z" }, - { url = "https://files.pythonhosted.org/packages/f8/b3/cede8f8235d42ff7ae891bae8d619d02c8ac9fd0cfc450c5927a6200c70d/fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272", size = 2217028, upload-time = "2025-09-29T21:12:17.96Z" }, - { url = "https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac", size = 2266200, upload-time = "2025-09-29T21:12:20.14Z" }, - { url = "https://files.pythonhosted.org/packages/9a/83/752ca11c1aa9a899b793a130f2e466b79ea0cf7279c8d79c178fc954a07b/fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3", size = 2822830, upload-time = "2025-09-29T21:12:24.406Z" }, - { url = "https://files.pythonhosted.org/packages/57/17/bbeab391100331950a96ce55cfbbff27d781c1b85ebafb4167eae50d9fe3/fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85", size = 2345524, upload-time = "2025-09-29T21:12:26.819Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2e/d4831caa96d85a84dd0da1d9f90d81cec081f551e0ea216df684092c6c97/fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537", size = 4843490, upload-time = "2025-09-29T21:12:29.123Z" }, - { url = "https://files.pythonhosted.org/packages/49/13/5e2ea7c7a101b6fc3941be65307ef8df92cbbfa6ec4804032baf1893b434/fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003", size = 4944184, upload-time = "2025-09-29T21:12:31.414Z" }, - { url = "https://files.pythonhosted.org/packages/0c/2b/cf9603551c525b73fc47c52ee0b82a891579a93d9651ed694e4e2cd08bb8/fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08", size = 4890218, upload-time = "2025-09-29T21:12:33.936Z" }, - { url = "https://files.pythonhosted.org/packages/fd/2f/933d2352422e25f2376aae74f79eaa882a50fb3bfef3c0d4f50501267101/fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99", size = 4999324, upload-time = "2025-09-29T21:12:36.637Z" }, - { url = "https://files.pythonhosted.org/packages/38/99/234594c0391221f66216bc2c886923513b3399a148defaccf81dc3be6560/fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6", size = 2220861, upload-time = "2025-09-29T21:12:39.108Z" }, - { url = "https://files.pythonhosted.org/packages/3e/1d/edb5b23726dde50fc4068e1493e4fc7658eeefcaf75d4c5ffce067d07ae5/fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987", size = 2270934, upload-time = "2025-09-29T21:12:41.339Z" }, - { url = "https://files.pythonhosted.org/packages/fb/da/1392aaa2170adc7071fe7f9cfd181a5684a7afcde605aebddf1fb4d76df5/fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299", size = 2894340, upload-time = "2025-09-29T21:12:43.774Z" }, - { url = "https://files.pythonhosted.org/packages/bf/a7/3b9f16e010d536ce567058b931a20b590d8f3177b2eda09edd92e392375d/fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01", size = 2375073, upload-time = "2025-09-29T21:12:46.437Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b5/e9bcf51980f98e59bb5bb7c382a63c6f6cac0eec5f67de6d8f2322382065/fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801", size = 4849758, upload-time = "2025-09-29T21:12:48.694Z" }, - { url = "https://files.pythonhosted.org/packages/e3/dc/1d2cf7d1cba82264b2f8385db3f5960e3d8ce756b4dc65b700d2c496f7e9/fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc", size = 5085598, upload-time = "2025-09-29T21:12:51.081Z" }, - { url = "https://files.pythonhosted.org/packages/5d/4d/279e28ba87fb20e0c69baf72b60bbf1c4d873af1476806a7b5f2b7fac1ff/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc", size = 4957603, upload-time = "2025-09-29T21:12:53.423Z" }, - { url = "https://files.pythonhosted.org/packages/78/d4/ff19976305e0c05aa3340c805475abb00224c954d3c65e82c0a69633d55d/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed", size = 4974184, upload-time = "2025-09-29T21:12:55.962Z" }, - { url = "https://files.pythonhosted.org/packages/63/22/8553ff6166f5cd21cfaa115aaacaa0dc73b91c079a8cfd54a482cbc0f4f5/fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259", size = 2282241, upload-time = "2025-09-29T21:12:58.179Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cb/fa7b4d148e11d5a72761a22e595344133e83a9507a4c231df972e657579b/fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c", size = 2345760, upload-time = "2025-09-29T21:13:00.375Z" }, - { url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" }, -] - -[[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/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, - { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, - { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, - { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, - { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, - { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, - { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, - { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, - { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, - { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, - { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, - { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, - { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, - { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, - { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, - { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, - { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, - { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, - { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, - { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, - { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, - { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, - { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, - { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, - { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, - { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, - { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, - { 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" }, +name = "flatbuffers" +version = "25.12.19" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" }, ] [[package]] @@ -821,63 +469,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/47/71/70db47e4f6ce3e5c37a607355f80da8860a33226be640226ac52cb05ef2e/fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7", size = 199289, upload-time = "2025-09-02T19:10:47.708Z" }, ] -[[package]] -name = "future" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490, upload-time = "2024-02-21T11:52:38.461Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326, upload-time = "2024-02-21T11:52:35.956Z" }, -] - -[[package]] -name = "gensim" -version = "4.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "scipy" }, - { name = "smart-open" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ec/bc/36ce4d510085cf150f17d79bb5e88cde942aeba2a894aed5893812ea1e6d/gensim-4.3.3.tar.gz", hash = "sha256:84852076a6a3d88d7dac5be245e24c21c3b819b565e14c1b61fa3e5ee76dcf57", size = 23258708, upload-time = "2024-07-19T14:42:35.418Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/12/047dc8b6bed7c4833bcdfbafc10af0f96dc3847ce37be63b14bd6e6c7767/gensim-4.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4e72840adfbea35c5804fd559bc0cb6bc9f439926220a37d852b7ce76eb325c1", size = 24086876, upload-time = "2024-07-19T14:39:26.268Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6e/7c6d7dda41924b83c4b1eb096942b68b85ba305df7f0963ad0642ac0d73f/gensim-4.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4019263c9d9afae7c669f880c17e09461e77a71afce04ed4d79cf71a4cad2848", size = 24041730, upload-time = "2024-07-19T14:39:34.431Z" }, - { url = "https://files.pythonhosted.org/packages/73/f4/376290613da44ea9d11bdce3a1705ba7cc25f971edb2b460dc192092068c/gensim-4.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dea62d3e2ada547687bde6cbba37efa50b534db77e9d44fd5802676bb072c9d9", size = 26398007, upload-time = "2024-07-19T14:39:41.67Z" }, - { url = "https://files.pythonhosted.org/packages/de/63/776ee55c773f55fa9d4fc1596f2e5e15de109921a6727dfe29cc4f0baeb7/gensim-4.3.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fac93ef5e44982defef9d3c1e4cd00245506b8a29cec19ec5e00f0221b8144c", size = 26506925, upload-time = "2024-07-19T14:39:48.662Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4a/f07e2f255aedd6bb4bd0ae420a465f228a4a91bc78ac359216ea20557be6/gensim-4.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:7c3409f755fb8d62da99cea65e7a40a99d21f8fd86443a3aaf2d90eb68995021", size = 24012924, upload-time = "2024-07-19T14:39:56.224Z" }, - { url = "https://files.pythonhosted.org/packages/7b/f4/f43fd909aa29fd92f0e6d703d90c0e6507a7c6be3d686a025b1e192afa3a/gensim-4.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:99e7b70352aecc6c1674dde82b75f453e7a5d1cc71ac1cfbc460bf1fe20501b7", size = 24082968, upload-time = "2024-07-19T14:40:03.849Z" }, - { url = "https://files.pythonhosted.org/packages/2a/15/aca2fc3b9e97bd0e28be4a4302793c43757b04b828223c6d103c72132f19/gensim-4.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:32a4cac3f3c38af2069eab9524609fc92ebaeb2692b7280cfda365a3517a280a", size = 24036231, upload-time = "2024-07-19T14:40:10.943Z" }, - { url = "https://files.pythonhosted.org/packages/ef/84/e46049a16fa7daa26ac9e83e41b3bc3b30867da832a5d7cb0779da893255/gensim-4.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c071b4329ed1be02446eb7ef637b94c68cf0080c15c57fbcde667fce2e49c3fe", size = 26558362, upload-time = "2024-07-19T14:40:17.997Z" }, - { url = "https://files.pythonhosted.org/packages/78/4f/f6045d5d5f8e7838c42572607ce440f95dbf4de5da41ae664198c2839c05/gensim-4.3.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d662bf96e3d741b6ab61a54be842a7cbf5e45193008b2f4225c758cafd7f9cdc", size = 26662669, upload-time = "2024-07-19T14:40:26.14Z" }, - { url = "https://files.pythonhosted.org/packages/f5/57/f2e6568dbf464a4b270954e5fa3dee4a4054d163a41c0e7bf0a34eb40f0f/gensim-4.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a54bd53a0e6f991abb837f126663353657270e75be53287e8a568ada0b35b1b0", size = 24010102, upload-time = "2024-07-19T14:40:33.359Z" }, - { url = "https://files.pythonhosted.org/packages/40/f1/3231b3fd6f7424f28d7d673679c843da0c61659538262a234f9f43ed5b10/gensim-4.3.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9a65ed1a8c1fc83890b4eb2a45ae2b32e82a0209c970c8c74694d0374c2415cb", size = 24079041, upload-time = "2024-07-19T14:40:40.907Z" }, - { url = "https://files.pythonhosted.org/packages/1f/76/616bc781bc19ee76b387a101211f73e00cf59368fcc221e77f88ea907d04/gensim-4.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4db485e08a0287e0fd6a029d89b90913d1df38f1dcd34cd2ab758873ba9255f3", size = 24035496, upload-time = "2024-07-19T14:40:47.667Z" }, - { url = "https://files.pythonhosted.org/packages/e0/b7/a316ba52548ca405413c23967c1c6c77d00f82cf6b0cb63d268001e023aa/gensim-4.3.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7198987116373ab99f034b292a04ac841531d12b56345851c98b40a3fcd93a85", size = 26487104, upload-time = "2024-07-19T14:40:54.867Z" }, - { url = "https://files.pythonhosted.org/packages/1a/07/7a0d5e6cab4da2769c8018f2472690ccb8cab191bf2fe46342dfd627486b/gensim-4.3.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6237a50de4da7a037b19b2b6c430b6537243dcdedebf94afeb089e951953e601", size = 26606101, upload-time = "2024-07-19T14:41:02.539Z" }, - { url = "https://files.pythonhosted.org/packages/79/7b/747fcb06280764cf20353361162eff68c6b0a3be34c43ead5ae393d3b18e/gensim-4.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:c910c2d5a71f532273166a3a82762959973f0513b221a495fa5a2a07652ee66d", size = 24009244, upload-time = "2024-07-19T14:41:09.732Z" }, -] - [[package]] name = "graph-ragu" -version = "1.0.4" +version = "0.0.2" source = { editable = "." } dependencies = [ { name = "aiolimiter" }, { name = "diskcache" }, - { name = "graspologic" }, - { name = "instructor" }, + { name = "fastembed" }, + { name = "graspologic-native" }, + { name = "jinja2" }, { name = "loguru" }, { name = "nano-vectordb" }, { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "networkx", version = "3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "nltk" }, - { name = "numpy" }, { name = "openai" }, { name = "pandas" }, { name = "pydantic" }, { name = "pydantic-settings" }, + { name = "pymorphy3" }, + { name = "qdrant-client", version = "1.12.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "qdrant-client", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, { name = "razdel" }, + { name = "requests" }, + { name = "scikit-learn" }, { name = "tenacity" }, { name = "tiktoken" }, { name = "tqdm" }, @@ -899,61 +515,36 @@ test = [ [package.metadata] requires-dist = [ { name = "aiolimiter", specifier = ">=1.2.1" }, - { name = "diskcache" }, - { name = "graspologic", specifier = ">=3.4.4" }, + { name = "diskcache", specifier = ">=5.6.3" }, + { name = "fastembed", specifier = "==0.8.0" }, + { name = "graspologic-native", specifier = ">=1.2.5" }, { name = "httpx", extras = ["socks"], marker = "extra == 'test'" }, - { name = "instructor", specifier = ">=1.11.3" }, + { name = "jinja2", specifier = ">=3.1.6" }, { name = "loguru", specifier = ">=0.7.3" }, { name = "nano-vectordb", specifier = ">=0.0.4.3" }, { name = "networkx", specifier = ">=3.4.2" }, { name = "nltk", specifier = ">=3.9.2" }, - { name = "numpy", specifier = ">=1.26.4" }, - { name = "openai", specifier = ">=1.109.1" }, + { name = "openai", specifier = ">=2.32.0" }, { name = "pandas", specifier = ">=2.3.3" }, { name = "pydantic", specifier = ">=2.11.10" }, { name = "pydantic-settings", specifier = ">=2.11.0" }, + { name = "pymorphy3", specifier = ">=2.0.6" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=8.4.2" }, { name = "pytest-asyncio", marker = "extra == 'test'", specifier = ">=1.3.0" }, { name = "pytest-cov", marker = "extra == 'test'", specifier = ">=4.1.0" }, + { name = "qdrant-client", specifier = ">=1.12.1" }, { name = "razdel", specifier = ">=0.5.0" }, + { name = "requests", specifier = ">=2.33.1" }, + { name = "scikit-learn", specifier = ">=1.7.2" }, { name = "sentence-transformers", marker = "extra == 'local'", specifier = ">=5.1.1" }, - { name = "smart-chunker", marker = "extra == 'local'", git = "https://github.com/AsphodelRem/smart_chunker.git" }, + { name = "smart-chunker", marker = "extra == 'local'", specifier = ">=0.0.5" }, { name = "tenacity", specifier = ">=9.1.2" }, { name = "tiktoken", specifier = ">=0.11.0" }, { name = "tqdm", specifier = ">=4.67.1" }, - { name = "transformers", marker = "extra == 'local'", specifier = ">=4.57.0" }, + { name = "transformers", marker = "extra == 'local'", specifier = ">=4.57.1" }, ] provides-extras = ["local", "test"] -[[package]] -name = "graspologic" -version = "3.4.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anytree" }, - { name = "beartype" }, - { name = "future" }, - { name = "gensim" }, - { name = "graspologic-native" }, - { name = "hyppo" }, - { name = "joblib" }, - { name = "matplotlib" }, - { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "networkx", version = "3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "numpy" }, - { name = "pot" }, - { name = "scikit-learn" }, - { name = "scipy" }, - { name = "seaborn" }, - { name = "statsmodels" }, - { name = "typing-extensions" }, - { name = "umap-learn" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/91/bb/0fe2ef85ea775e7b8416b2cf90097aa4b5e0c9c2271d7fe6789bab27d0ca/graspologic-3.4.4.tar.gz", hash = "sha256:79878caf367da3e89046a4ec94291c5b1a5da569f19fdd879d8b45c3563d7110", size = 5134258, upload-time = "2025-09-08T21:44:01.969Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/b0/e26eb8fc25f3093ad168fba4101bdcf43258b91672546d20a2b64283845c/graspologic-3.4.4-py3-none-any.whl", hash = "sha256:4ea5cd50f10eaff3fa90f18a8f66b1f5f42c724ac6aeb95e9f081632fc8d2d00", size = 5200993, upload-time = "2025-09-08T21:43:59.843Z" }, -] - [[package]] name = "graspologic-native" version = "1.2.5" @@ -966,6 +557,130 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/51/21097af79f3d68626539ab829bdbf6cc42933f020e161972927d916e394c/graspologic_native-1.2.5-cp38-abi3-win_amd64.whl", hash = "sha256:c3ef2172d774083d7e2c8e77daccd218571ddeebeb2c1703cebb1a2cc4c56e07", size = 210438, upload-time = "2025-04-02T19:34:21.139Z" }, ] +[[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/9d/cd/bb7b7e54084a344c03d68144450da7ddd5564e51a298ae1662de65f48e2d/grpcio-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:886457a7768e408cdce226ad1ca67d2958917d306523a0e21e1a2fdaa75c9c9c", size = 6050363, upload-time = "2026-03-30T08:46:20.894Z" }, + { url = "https://files.pythonhosted.org/packages/16/02/1417f5c3460dea65f7a2e3c14e8b31e77f7ffb730e9bfadd89eda7a9f477/grpcio-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7b641fc3f1dc647bfd80bd713addc68f6d145956f64677e56d9ebafc0bd72388", size = 12026037, upload-time = "2026-03-30T08:46:25.144Z" }, + { url = "https://files.pythonhosted.org/packages/43/98/c910254eedf2cae368d78336a2de0678e66a7317d27c02522392f949b5c6/grpcio-1.80.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:33eb763f18f006dc7fee1e69831d38d23f5eccd15b2e0f92a13ee1d9242e5e02", size = 6602306, upload-time = "2026-03-30T08:46:27.593Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f8/88ca4e78c077b2b2113d95da1e1ab43efd43d723c9a0397d26529c2c1a56/grpcio-1.80.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:52d143637e3872633fc7dd7c3c6a1c84e396b359f3a72e215f8bf69fd82084fc", size = 7301535, upload-time = "2026-03-30T08:46:29.556Z" }, + { url = "https://files.pythonhosted.org/packages/f9/96/f28660fe2fe0f153288bf4a04e4910b7309d442395135c88ed4f5b3b8b40/grpcio-1.80.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c51bf8ac4575af2e0678bccfb07e47321fc7acb5049b4482832c5c195e04e13a", size = 6808669, upload-time = "2026-03-30T08:46:31.984Z" }, + { url = "https://files.pythonhosted.org/packages/47/eb/3f68a5e955779c00aeef23850e019c1c1d0e032d90633ba49c01ad5a96e0/grpcio-1.80.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:50a9871536d71c4fba24ee856abc03a87764570f0c457dd8db0b4018f379fed9", size = 7409489, upload-time = "2026-03-30T08:46:34.684Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a7/d2f681a4bfb881be40659a309771f3bdfbfdb1190619442816c3f0ffc079/grpcio-1.80.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a72d84ad0514db063e21887fbacd1fd7acb4d494a564cae22227cd45c7fbf199", size = 8423167, upload-time = "2026-03-30T08:46:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/97/8a/29b4589c204959aa35ce5708400a05bba72181807c45c47b3ec000c39333/grpcio-1.80.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f7691a6788ad9196872f95716df5bc643ebba13c97140b7a5ee5c8e75d1dea81", size = 7846761, upload-time = "2026-03-30T08:46:40.091Z" }, + { url = "https://files.pythonhosted.org/packages/6b/d2/ed143e097230ee121ac5848f6ff14372dba91289b10b536d54fb1b7cbae7/grpcio-1.80.0-cp310-cp310-win32.whl", hash = "sha256:46c2390b59d67f84e882694d489f5b45707c657832d7934859ceb8c33f467069", size = 4156534, upload-time = "2026-03-30T08:46:42.026Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c9/df8279bb49b29409995e95efa85b72973d62f8aeff89abee58c91f393710/grpcio-1.80.0-cp310-cp310-win_amd64.whl", hash = "sha256:dc053420fc75749c961e2a4c906398d7c15725d36ccc04ae6d16093167223b58", size = 4889869, upload-time = "2026-03-30T08:46:44.219Z" }, + { url = "https://files.pythonhosted.org/packages/5d/db/1d56e5f5823257b291962d6c0ce106146c6447f405b60b234c4f222a7cde/grpcio-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:dfab85db094068ff42e2a3563f60ab3dddcc9d6488a35abf0132daec13209c8a", size = 6055009, upload-time = "2026-03-30T08:46:46.265Z" }, + { url = "https://files.pythonhosted.org/packages/6e/18/c83f3cad64c5ca63bca7e91e5e46b0d026afc5af9d0a9972472ceba294b3/grpcio-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5c07e82e822e1161354e32da2662f741a4944ea955f9f580ec8fb409dd6f6060", size = 12035295, upload-time = "2026-03-30T08:46:49.099Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8e/e14966b435be2dda99fbe89db9525ea436edc79780431a1c2875a3582644/grpcio-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba0915d51fd4ced2db5ff719f84e270afe0e2d4c45a7bdb1e8d036e4502928c2", size = 6610297, upload-time = "2026-03-30T08:46:52.123Z" }, + { url = "https://files.pythonhosted.org/packages/cc/26/d5eb38f42ce0e3fdc8174ea4d52036ef8d58cc4426cb800f2610f625dd75/grpcio-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3cb8130ba457d2aa09fa6b7c3ed6b6e4e6a2685fce63cb803d479576c4d80e21", size = 7300208, upload-time = "2026-03-30T08:46:54.859Z" }, + { url = "https://files.pythonhosted.org/packages/25/51/bd267c989f85a17a5b3eea65a6feb4ff672af41ca614e5a0279cc0ea381c/grpcio-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09e5e478b3d14afd23f12e49e8b44c8684ac3c5f08561c43a5b9691c54d136ab", size = 6813442, upload-time = "2026-03-30T08:46:57.056Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d9/d80eef735b19e9169e30164bbf889b46f9df9127598a83d174eb13a48b26/grpcio-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00168469238b022500e486c1c33916acf2f2a9b2c022202cf8a1885d2e3073c1", size = 7414743, upload-time = "2026-03-30T08:46:59.682Z" }, + { url = "https://files.pythonhosted.org/packages/de/f2/567f5bd5054398ed6b0509b9a30900376dcf2786bd936812098808b49d8d/grpcio-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8502122a3cc1714038e39a0b071acb1207ca7844208d5ea0d091317555ee7106", size = 8426046, upload-time = "2026-03-30T08:47:02.474Z" }, + { url = "https://files.pythonhosted.org/packages/62/29/73ef0141b4732ff5eacd68430ff2512a65c004696997f70476a83e548e7e/grpcio-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce1794f4ea6cc3ca29463f42d665c32ba1b964b48958a66497917fe9069f26e6", size = 7851641, upload-time = "2026-03-30T08:47:05.462Z" }, + { url = "https://files.pythonhosted.org/packages/46/69/abbfa360eb229a8623bab5f5a4f8105e445bd38ce81a89514ba55d281ad0/grpcio-1.80.0-cp311-cp311-win32.whl", hash = "sha256:51b4a7189b0bef2aa30adce3c78f09c83526cf3dddb24c6a96555e3b97340440", size = 4154368, upload-time = "2026-03-30T08:47:08.027Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d4/ae92206d01183b08613e846076115f5ac5991bae358d2a749fa864da5699/grpcio-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:02e64bb0bb2da14d947a49e6f120a75e947250aebe65f9629b62bb1f5c14e6e9", size = 4894235, upload-time = "2026-03-30T08:47:10.839Z" }, + { 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 = "grpcio-tools" +version = "1.80.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio", marker = "python_full_version >= '3.13'" }, + { name = "protobuf", marker = "python_full_version >= '3.13'" }, + { name = "setuptools", marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/c8/1223f29c84a143ae9a56c084fc96894de0ba84b6e8d60a26241abd81d278/grpcio_tools-1.80.0.tar.gz", hash = "sha256:26052b19c6ce0dcf52d1024496aea3e2bdfa864159f06dc7b97b22d041a94b26", size = 6133212, upload-time = "2026-03-30T08:52:39.077Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/54/1de67f5080da305a258758a8deb33f85666fa759f56785042a80b114a53f/grpcio_tools-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:727477b9afa4b53f5ec70cafb41c3965d893835e0d4ea9b542fe3d0d005602bf", size = 2549601, upload-time = "2026-03-30T08:50:09.498Z" }, + { url = "https://files.pythonhosted.org/packages/9a/b4/6d57ea199c5b880d182a2234aafa9a686f9c54c708ea7be75bd19d5aa825/grpcio_tools-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:85fe8d15f146c62cb76f38d963e256392d287442b9232717d30ae9e3bbda9bc3", size = 5712717, upload-time = "2026-03-30T08:50:15.028Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1a/5505ee2277d368b409c796c78f22ea34a2a517b7d16755247efd663dc7af/grpcio_tools-1.80.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:95f0fffb5ca00519f3b602f938169b4dfa04b165e03258323965a9dfe8cc4d80", size = 2595941, upload-time = "2026-03-30T08:50:17.299Z" }, + { url = "https://files.pythonhosted.org/packages/4e/39/7fc1d16d8b767805079d76365d73e82c88dfaf179034473dbc9fbccedb77/grpcio_tools-1.80.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:7a0106af212748823a6ebd8ffbd9043414216f47cae3835f3187de0a62c415d3", size = 2909304, upload-time = "2026-03-30T08:50:19.485Z" }, + { url = "https://files.pythonhosted.org/packages/97/d8/276ee759755d8f34f2ca5e9d2debd1a59f29f66059fb790bc369f2236c26/grpcio_tools-1.80.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:31fd01a4038b5dfc4ec79504a17061344f670f851833411717fef66920f13cd7", size = 2660269, upload-time = "2026-03-30T08:50:21.266Z" }, + { url = "https://files.pythonhosted.org/packages/51/04/a6bb47942ad52901d777a649324d3203cf19d487f1d446263637f7a5bf12/grpcio_tools-1.80.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:57da9e19607fac4a01c48ead333c0dd15d91ed38794dce1194eda308f73e2038", size = 3109798, upload-time = "2026-03-30T08:50:23.267Z" }, + { url = "https://files.pythonhosted.org/packages/be/50/7ee69b2919916739787d725f205b878e8d1619dd30422b8278e324664669/grpcio_tools-1.80.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:90968f751851abb8b145593609800fa70c837e1c93ba0792c480b1c8d8bc29ef", size = 3658930, upload-time = "2026-03-30T08:50:25.458Z" }, + { url = "https://files.pythonhosted.org/packages/92/61/6d50783092b0e8bbcb04152d5388bf50ecf3ea2f783d95288ff6c3bb00fa/grpcio_tools-1.80.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b69dc5d6376ab43406304d1e2fc61ccf960b287d4325d77c3d45448c37a9d2da", size = 3326562, upload-time = "2026-03-30T08:50:27.809Z" }, + { url = "https://files.pythonhosted.org/packages/ea/58/d272ba549f6b1f0d8504f5fc4cd0a296f2c495a64d6e987fe871c4151557/grpcio_tools-1.80.0-cp310-cp310-win32.whl", hash = "sha256:3e8dcfebe34cb54df095de3d5871a4562a85a29f26d0f8bb41ee2c3dcfb11c3c", size = 997620, upload-time = "2026-03-30T08:50:29.959Z" }, + { url = "https://files.pythonhosted.org/packages/70/5f/9f45a9946a0298711c72ca48b2c1f46a7d0c207a44cd3e4bb59d04556ba3/grpcio_tools-1.80.0-cp310-cp310-win_amd64.whl", hash = "sha256:fc622ed4ca400695f41c9eae3266276c6ba007e4c28164ce53b44e7ccc5e492b", size = 1162466, upload-time = "2026-03-30T08:50:32.242Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d7/225dc91e6cb4f8d4830f16a478a468e9c6f342dcdf8cacc3772cc1d1f607/grpcio_tools-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:1c43e5c768578fe0c6de3dbfaabe64af642951e1aa05c487cacedda63fa6c6c4", size = 2549937, upload-time = "2026-03-30T08:50:34.651Z" }, + { url = "https://files.pythonhosted.org/packages/97/3d/a3684cb7677f3bea8db434eae02a9ce30135d7a268cd473b1bc8041c4722/grpcio_tools-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a225348456575f3ac7851d8e23163195e76d2a905ee340cf73f33da62fba08aa", size = 5713099, upload-time = "2026-03-30T08:50:37.158Z" }, + { url = "https://files.pythonhosted.org/packages/b1/81/5665c697173ec346076358bfbfed0f7386825852494593ca14386478dfee/grpcio_tools-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a9396f02820d3f51c368c2c9dee15c55c77636c91be48a4d5c702e98d6fe0fdc", size = 2595776, upload-time = "2026-03-30T08:50:39.087Z" }, + { url = "https://files.pythonhosted.org/packages/03/4f/fb81384f08a8226fa079972ba88272ac6277581fc72e8ab234d74c7e065b/grpcio_tools-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:797c08460cae16b402326eac329aec720dccf45c9f9279b95a352792eb53cf0f", size = 2909144, upload-time = "2026-03-30T08:50:40.922Z" }, + { url = "https://files.pythonhosted.org/packages/4d/9c/c957618f1c2a3195ecf5e83b03edcb364c2c1391f74183cb76e5763fa536/grpcio_tools-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1872a867eb6217de19edb70a4ce4a374ced9d94293533dfd42fa649713f55bf4", size = 2660477, upload-time = "2026-03-30T08:50:42.766Z" }, + { url = "https://files.pythonhosted.org/packages/42/c7/23913da184febfd4eaf04de256a26bc5ff0411a5feb753e2adcff10fa86a/grpcio_tools-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:db122ba5ee357e3bb14e8944d69bbebcbdae91d5eace29ed4df3edc53cbc6528", size = 3110164, upload-time = "2026-03-30T08:50:44.761Z" }, + { url = "https://files.pythonhosted.org/packages/af/fa/b25ed85ebdb0396910eaa250b1346d75527d22fca586265416bd4330dcd5/grpcio_tools-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ddefd48c227e6f4d640fe576fac5fb2c4a8898196f513604c8ec7671b3b3d421", size = 3658988, upload-time = "2026-03-30T08:50:47.546Z" }, + { url = "https://files.pythonhosted.org/packages/60/85/2a55147cc9645e2ed777d1afcd2dc68cb34ba6f6c726bd4378ddb001a5ea/grpcio_tools-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:970ec058fa469dd6dae6ebc687501c5da670d95dead75f62f5b0933dce2c9794", size = 3326662, upload-time = "2026-03-30T08:50:49.59Z" }, + { url = "https://files.pythonhosted.org/packages/68/ed/b05bee2a992e6f9bda81909692ea920d0896cfa05c5c9dd77ba03f2d22fb/grpcio_tools-1.80.0-cp311-cp311-win32.whl", hash = "sha256:526b4402d47a0e9b31cd6087e42b7674784617916cc73c764e0bc35ed41b4ee5", size = 997969, upload-time = "2026-03-30T08:50:51.539Z" }, + { url = "https://files.pythonhosted.org/packages/b6/9a/cb50c8270e2f6285ff2761130ae257ac4e51789ded4b9d9710ce0381814d/grpcio_tools-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:ee101ecda7231770f6a5da1024a9a6ed587a7785f8fe23ab8283f4a1acb3ffe6", size = 1162742, upload-time = "2026-03-30T08:50:54.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b9/65929df8c9614792db900a8e45d4997fadbd1734c827da3f0eb1f2fe4866/grpcio_tools-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:d19d5a8244311947b96f749c417b32d144641c6953f1164824579e1f0a51d040", size = 2550856, upload-time = "2026-03-30T08:50:57.3Z" }, + { url = "https://files.pythonhosted.org/packages/28/17/af1557544d68d1aeca9d9ea53ed16524022d521fec6ba334ab3530e9c1a6/grpcio_tools-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fb599a3dc89ed1bb24489a2724b2f6dd4cddbbf0f7bdd69c073477bab0dc7554", size = 5710883, upload-time = "2026-03-30T08:51:00.077Z" }, + { url = "https://files.pythonhosted.org/packages/cc/48/aa9b4f7519ca972bc40d315d5c28f05ca28fa08de13d4e8b69f551b798ab/grpcio_tools-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:623ee31fc2ff7df9a987b4f3d139c30af17ce46a861ae0e25fb8c112daa32dd8", size = 2598004, upload-time = "2026-03-30T08:51:02.102Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b8/b01371c119924b3beca1fe3f047b1bc2cdc66b3d37f0f3acc9d10c567a43/grpcio_tools-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b46570a68378539ee2b75a5a43202561f8d753c832798b1047099e3c551cf5d6", size = 2909568, upload-time = "2026-03-30T08:51:04.159Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7c/1108f7bdb58475a7e701ec89b55eb494538b6e76acd211ba0d4cc5fd28e8/grpcio_tools-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51caf99c28999e7e0f97e9cea190c1405b7681a57bb2e0631205accd92b43fa4", size = 2660938, upload-time = "2026-03-30T08:51:06.126Z" }, + { url = "https://files.pythonhosted.org/packages/67/59/d1c0063d4cd3b85363c7044ff3e5159d6d5df96e2692a9a5312d9c8cb290/grpcio_tools-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cdaa1c9aa8d3a87891a96700cadd29beec214711d6522818d207277f6452567c", size = 3113814, upload-time = "2026-03-30T08:51:08.834Z" }, + { url = "https://files.pythonhosted.org/packages/76/21/18d34a4efe524c903cf66b0cfa5260d81f277b6ae668b647edf795df9ce5/grpcio_tools-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3399b5fd7b59bcffd59c6b9975a969d9f37a3c87f3e3d63c3a09c147907acb0d", size = 3662793, upload-time = "2026-03-30T08:51:11.094Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/cf2d9295a6bd593244ea703858f8fc2efd315046ca3ef7c6f9ebc5b810fa/grpcio_tools-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9c6abc08d3485b2aac99bb58afcd31dc6cd4316ce36cf263ff09cb6df15f287f", size = 3329149, upload-time = "2026-03-30T08:51:13.066Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1d/fc34b32167966df20d69429b71dfca83c48434b047a5ac4fd6cd91ca4eed/grpcio_tools-1.80.0-cp312-cp312-win32.whl", hash = "sha256:18c51e07652ac7386fcdbd11866f8d55a795de073337c12447b5805575339f74", size = 997519, upload-time = "2026-03-30T08:51:14.87Z" }, + { url = "https://files.pythonhosted.org/packages/91/98/6d6563cdf51085b75f8ec24605c6f2ce84197571878ca8ab4af949c6be2d/grpcio_tools-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac6fdd42d5bb18f0d903a067e2825be172deff70cf197164b6f65676cb506c9b", size = 1162407, upload-time = "2026-03-30T08:51:16.793Z" }, + { url = "https://files.pythonhosted.org/packages/44/d9/f7887a4805939e9a85d03744b66fc02575dc1df3c3e8b4d9ec000ee7a33d/grpcio_tools-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e7046837859bbfd10b01786056145480155c16b222c9e209215b68d3be13060e", size = 2550319, upload-time = "2026-03-30T08:51:19.117Z" }, + { url = "https://files.pythonhosted.org/packages/57/5a/c8a05b32bd7203f1b9f4c0151090a2d6179d6c97692d32f2066dc29c67a6/grpcio_tools-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a447f28958a8fe84ff0d9d3d9473868feb27ee4a9c9c805e66f5b670121cec59", size = 5709681, upload-time = "2026-03-30T08:51:21.991Z" }, + { url = "https://files.pythonhosted.org/packages/82/6b/794350ed645c12c310008f97068f6a6fd927150b0d0d08aad1d909e880b1/grpcio_tools-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:75f00450e08fe648ad8a1eeb25bc52219679d54cdd02f04dfdddc747309d83f6", size = 2596820, upload-time = "2026-03-30T08:51:24.323Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b2/b39e7b79f7c878135e0784a53cd7260ee77260c8c7f2c9e46bca8e05d017/grpcio_tools-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3db830eaff1f2c2797328f2fa86c9dcdbd7d81af573a68db81e27afa2182a611", size = 2909193, upload-time = "2026-03-30T08:51:27.025Z" }, + { url = "https://files.pythonhosted.org/packages/10/f3/abe089b058f87f9910c9a458409505cbeb0b3e1c2d993a79721d02ee6a32/grpcio_tools-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7982b5fe42f012686b667dda12916884de95c4b1c65ff64371fb7232a1474b23", size = 2660197, upload-time = "2026-03-30T08:51:29.392Z" }, + { url = "https://files.pythonhosted.org/packages/09/c3/3f7806ad8b731d8a89fe3c6ed496473abd1ef4c9c42c9e9a8836ce96e377/grpcio_tools-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6451b3f4eb52d12c7f32d04bf8e0185f80521f3f088ad04b8d222b3a4819c71e", size = 3113144, upload-time = "2026-03-30T08:51:31.671Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f5/415ef205e0b7e75d2a2005df6120145c4f02fda28d7b3715b55d924fe1a4/grpcio_tools-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:258bc30654a9a2236be4ca8e2ad443e2ac6db7c8cc20454d34cce60265922726", size = 3661897, upload-time = "2026-03-30T08:51:34.849Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d3/2ad54764c2a9547080dd8518f4a4dc7899c7e6e747a1b1de542ce6a12066/grpcio_tools-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:865a2b8e6334c838976ab02a322cbd55c863d2eaf3c1e1a0255883c63996772a", size = 3328786, upload-time = "2026-03-30T08:51:37.265Z" }, + { url = "https://files.pythonhosted.org/packages/eb/63/23ab7db01f9630ab4f3742a2fc9fbff38b0cfc30c976114f913950664a75/grpcio_tools-1.80.0-cp313-cp313-win32.whl", hash = "sha256:f760ac1722f33e774814c37b6aa0444143f612e85088ead7447a0e9cd306a1f1", size = 997087, upload-time = "2026-03-30T08:51:39.137Z" }, + { url = "https://files.pythonhosted.org/packages/9b/af/b1c1c4423fb49cb7c8e9d2c02196b038c44160b7028b425466743c6c81fa/grpcio_tools-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:7843b9ac6ff8ca508424d0dd968bd9a1a4559967e4a290f26be5bd6f04af2234", size = 1162167, upload-time = "2026-03-30T08:51:41.498Z" }, + { url = "https://files.pythonhosted.org/packages/0e/44/7beeee2348f9f412804f5bf80b7d13b81d522bf926a338ae3da46b2213b7/grpcio_tools-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:12f950470449dbeec78317dbc090add7a00eb6ca812af7b0538ab7441e0a42c3", size = 2550303, upload-time = "2026-03-30T08:51:44.373Z" }, + { url = "https://files.pythonhosted.org/packages/2d/aa/f77dd85409a1855f8c6319ffc69d81e8c3ffe122ee3a7136653e1991d8b6/grpcio_tools-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d3f9a376a29c9adf62bb56f7ff5bc81eb4abeaf53d1e7dde5015564832901a51", size = 5709778, upload-time = "2026-03-30T08:51:47.112Z" }, + { url = "https://files.pythonhosted.org/packages/9c/7c/ab7af4883ebdfdc228b853de89fed409703955e8d47285b321a5794856bd/grpcio_tools-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ba1ffbf2cff71533615e2c5a138ed5569611eec9ae7f9c67b8898e127b54ac0", size = 2597928, upload-time = "2026-03-30T08:51:49.494Z" }, + { url = "https://files.pythonhosted.org/packages/22/e8/4381a963d472e3ab6690ba067ed2b1f1abf8518b10f402678bd2dcb79a54/grpcio_tools-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:13f60f8d9397c514c6745a967d22b5c8c698347e88deebca1ff2e1b94555e450", size = 2909333, upload-time = "2026-03-30T08:51:52.124Z" }, + { url = "https://files.pythonhosted.org/packages/94/cb/356b5fdf79dd99455b425fb16302fe60995554ceb721afbf3cf770a19208/grpcio_tools-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:88d77bad5dd3cd5e6f952c4ecdd0ee33e0c02ecfc2e4b0cbee3391ac19e0a431", size = 2660217, upload-time = "2026-03-30T08:51:55.066Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d7/1752018cc2c36b2c5612051379e2e5f59f2dbe612de23e817d2f066a9487/grpcio_tools-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:017945c3e98a4ed1c4e21399781b4137fc08dfc1f802c8ace2e64ef52d32b142", size = 3113896, upload-time = "2026-03-30T08:51:57.3Z" }, + { url = "https://files.pythonhosted.org/packages/cc/17/695bbe454f70df35c03e22b48c5314683b913d3e6ed35ec90d065418c1ab/grpcio_tools-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a33e265d4db803495007a6c623eafb0f6b9bb123ff4a0af89e44567dad809b88", size = 3661950, upload-time = "2026-03-30T08:51:59.867Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d0/533d87629ec823c02c9169ee20228f734c264b209dcdf55268b5a14cde0a/grpcio_tools-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6c129da370c5f85f569be2e545317dda786a60dd51d7deea29b03b0c05f6aac3", size = 3328755, upload-time = "2026-03-30T08:52:02.942Z" }, + { url = "https://files.pythonhosted.org/packages/08/a1/504d7838770c73a9761e8a8ff4869dba1146b44f297ff0ac6641481942d3/grpcio_tools-1.80.0-cp314-cp314-win32.whl", hash = "sha256:25742de5958ae4325249a37e724e7c0e5120f8e302a24a977ebd1737b48a5e97", size = 1019620, upload-time = "2026-03-30T08:52:05.342Z" }, + { url = "https://files.pythonhosted.org/packages/f3/75/8b7cd281c5cdfb4ca2c308f7e9b2799bab2be6e7a9e9212ea5a82e2aecd4/grpcio_tools-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:bbf8eeef78fda1966f732f79c1c802fadd5cfd203d845d2af4d314d18569069c", size = 1194210, upload-time = "2026-03-30T08:52:08.105Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -975,19 +690,58 @@ 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.1.10" +version = "1.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/74/31/feeddfce1748c4a233ec1aa5b7396161c07ae1aa9b7bdbc9a72c3c7dd768/hf_xet-1.1.10.tar.gz", hash = "sha256:408aef343800a2102374a883f283ff29068055c111f003ff840733d3b715bb97", size = 487910, upload-time = "2025-09-12T20:10:27.12Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/92/ec9ad04d0b5728dca387a45af7bc98fbb0d73b2118759f5f6038b61a57e8/hf_xet-1.4.3.tar.gz", hash = "sha256:8ddedb73c8c08928c793df2f3401ec26f95be7f7e516a7bee2fbb546f6676113", size = 670477, upload-time = "2026-03-31T22:40:07.874Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/a2/343e6d05de96908366bdc0081f2d8607d61200be2ac802769c4284cc65bd/hf_xet-1.1.10-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:686083aca1a6669bc85c21c0563551cbcdaa5cf7876a91f3d074a030b577231d", size = 2761466, upload-time = "2025-09-12T20:10:22.836Z" }, - { url = "https://files.pythonhosted.org/packages/31/f9/6215f948ac8f17566ee27af6430ea72045e0418ce757260248b483f4183b/hf_xet-1.1.10-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:71081925383b66b24eedff3013f8e6bbd41215c3338be4b94ba75fd75b21513b", size = 2623807, upload-time = "2025-09-12T20:10:21.118Z" }, - { url = "https://files.pythonhosted.org/packages/15/07/86397573efefff941e100367bbda0b21496ffcdb34db7ab51912994c32a2/hf_xet-1.1.10-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6bceb6361c80c1cc42b5a7b4e3efd90e64630bcf11224dcac50ef30a47e435", size = 3186960, upload-time = "2025-09-12T20:10:19.336Z" }, - { url = "https://files.pythonhosted.org/packages/01/a7/0b2e242b918cc30e1f91980f3c4b026ff2eedaf1e2ad96933bca164b2869/hf_xet-1.1.10-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eae7c1fc8a664e54753ffc235e11427ca61f4b0477d757cc4eb9ae374b69f09c", size = 3087167, upload-time = "2025-09-12T20:10:17.255Z" }, - { url = "https://files.pythonhosted.org/packages/4a/25/3e32ab61cc7145b11eee9d745988e2f0f4fafda81b25980eebf97d8cff15/hf_xet-1.1.10-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0a0005fd08f002180f7a12d4e13b22be277725bc23ed0529f8add5c7a6309c06", size = 3248612, upload-time = "2025-09-12T20:10:24.093Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3d/ab7109e607ed321afaa690f557a9ada6d6d164ec852fd6bf9979665dc3d6/hf_xet-1.1.10-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f900481cf6e362a6c549c61ff77468bd59d6dd082f3170a36acfef2eb6a6793f", size = 3353360, upload-time = "2025-09-12T20:10:25.563Z" }, - { url = "https://files.pythonhosted.org/packages/ee/0e/471f0a21db36e71a2f1752767ad77e92d8cde24e974e03d662931b1305ec/hf_xet-1.1.10-cp37-abi3-win_amd64.whl", hash = "sha256:5f54b19cc347c13235ae7ee98b330c26dd65ef1df47e5316ffb1e87713ca7045", size = 2804691, upload-time = "2025-09-12T20:10:28.433Z" }, + { url = "https://files.pythonhosted.org/packages/72/43/724d307b34e353da0abd476e02f72f735cdd2bc86082dee1b32ea0bfee1d/hf_xet-1.4.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:7551659ba4f1e1074e9623996f28c3873682530aee0a846b7f2f066239228144", size = 3800935, upload-time = "2026-03-31T22:39:49.618Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d2/8bee5996b699262edb87dbb54118d287c0e1b2fc78af7cdc41857ba5e3c4/hf_xet-1.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bee693ada985e7045997f05f081d0e12c4c08bd7626dc397f8a7c487e6c04f7f", size = 3558942, upload-time = "2026-03-31T22:39:47.938Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a1/e993d09cbe251196fb60812b09a58901c468127b7259d2bf0f68bf6088eb/hf_xet-1.4.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21644b404bb0100fe3857892f752c4d09642586fd988e61501c95bbf44b393a3", size = 4207657, upload-time = "2026-03-31T22:39:39.69Z" }, + { url = "https://files.pythonhosted.org/packages/64/44/9eb6d21e5c34c63e5e399803a6932fa983cabdf47c0ecbcfe7ea97684b8c/hf_xet-1.4.3-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:987f09cfe418237812896a6736b81b1af02a3a6dcb4b4944425c4c4fca7a7cf8", size = 3986765, upload-time = "2026-03-31T22:39:37.936Z" }, + { url = "https://files.pythonhosted.org/packages/ea/7b/8ad6f16fdb82f5f7284a34b5ec48645bd575bdcd2f6f0d1644775909c486/hf_xet-1.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:60cf7fc43a99da0a853345cf86d23738c03983ee5249613a6305d3e57a5dca74", size = 4188162, upload-time = "2026-03-31T22:39:58.382Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c4/39d6e136cbeea9ca5a23aad4b33024319222adbdc059ebcda5fc7d9d5ff4/hf_xet-1.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2815a49a7a59f3e2edf0cf113ae88e8cb2ca2a221bf353fb60c609584f4884d4", size = 4424525, upload-time = "2026-03-31T22:40:00.225Z" }, + { url = "https://files.pythonhosted.org/packages/46/f2/adc32dae6bdbc367853118b9878139ac869419a4ae7ba07185dc31251b76/hf_xet-1.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:42ee323265f1e6a81b0e11094564fb7f7e0ec75b5105ffd91ae63f403a11931b", size = 3671610, upload-time = "2026-03-31T22:40:10.42Z" }, + { url = "https://files.pythonhosted.org/packages/e2/19/25d897dcc3f81953e0c2cde9ec186c7a0fee413eb0c9a7a9130d87d94d3a/hf_xet-1.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:27c976ba60079fb8217f485b9c5c7fcd21c90b0367753805f87cb9f3cdc4418a", size = 3528529, upload-time = "2026-03-31T22:40:09.106Z" }, + { url = "https://files.pythonhosted.org/packages/ec/36/3e8f85ca9fe09b8de2b2e10c63b3b3353d7dda88a0b3d426dffbe7b8313b/hf_xet-1.4.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5251d5ece3a81815bae9abab41cf7ddb7bcb8f56411bce0827f4a3071c92fdc6", size = 3801019, upload-time = "2026-03-31T22:39:56.651Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9c/defb6cb1de28bccb7bd8d95f6e60f72a3d3fa4cb3d0329c26fb9a488bfe7/hf_xet-1.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1feb0f3abeacee143367c326a128a2e2b60868ec12a36c225afb1d6c5a05e6d2", size = 3558746, upload-time = "2026-03-31T22:39:54.766Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bd/8d001191893178ff8e826e46ad5299446e62b93cd164e17b0ffea08832ec/hf_xet-1.4.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b301fc150290ca90b4fccd079829b84bb4786747584ae08b94b4577d82fb791", size = 4207692, upload-time = "2026-03-31T22:39:46.246Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/6790b402803250e9936435613d3a78b9aaeee7973439f0918848dde58309/hf_xet-1.4.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d972fbe95ddc0d3c0fc49b31a8a69f47db35c1e3699bf316421705741aab6653", size = 3986281, upload-time = "2026-03-31T22:39:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/51/56/ea62552fe53db652a9099eda600b032d75554d0e86c12a73824bfedef88b/hf_xet-1.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c5b48db1ee344a805a1b9bd2cda9b6b65fe77ed3787bd6e87ad5521141d317cd", size = 4187414, upload-time = "2026-03-31T22:40:04.951Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f5/bc1456d4638061bea997e6d2db60a1a613d7b200e0755965ec312dc1ef79/hf_xet-1.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:22bdc1f5fb8b15bf2831440b91d1c9bbceeb7e10c81a12e8d75889996a5c9da8", size = 4424368, upload-time = "2026-03-31T22:40:06.347Z" }, + { url = "https://files.pythonhosted.org/packages/e4/76/ab597bae87e1f06d18d3ecb8ed7f0d3c9a37037fc32ce76233d369273c64/hf_xet-1.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:0392c79b7cf48418cd61478c1a925246cf10639f4cd9d94368d8ca1e8df9ea07", size = 3672280, upload-time = "2026-03-31T22:40:16.401Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/2e462d34e23a09a74d73785dbed71cc5dbad82a72eee2ad60a72a554155d/hf_xet-1.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:681c92a07796325778a79d76c67011764ecc9042a8c3579332b61b63ae512075", size = 3528945, upload-time = "2026-03-31T22:40:14.995Z" }, + { url = "https://files.pythonhosted.org/packages/ac/9f/9c23e4a447b8f83120798f9279d0297a4d1360bdbf59ef49ebec78fe2545/hf_xet-1.4.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d0da85329eaf196e03e90b84c2d0aca53bd4573d097a75f99609e80775f98025", size = 3805048, upload-time = "2026-03-31T22:39:53.105Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f8/7aacb8e5f4a7899d39c787b5984e912e6c18b11be136ef13947d7a66d265/hf_xet-1.4.3-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e23717ce4186b265f69afa66e6f0069fe7efbf331546f5c313d00e123dc84583", size = 3562178, upload-time = "2026-03-31T22:39:51.295Z" }, + { url = "https://files.pythonhosted.org/packages/df/9a/a24b26dc8a65f0ecc0fe5be981a19e61e7ca963b85e062c083f3a9100529/hf_xet-1.4.3-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc360b70c815bf340ed56c7b8c63aacf11762a4b099b2fe2c9bd6d6068668c08", size = 4212320, upload-time = "2026-03-31T22:39:42.922Z" }, + { url = "https://files.pythonhosted.org/packages/53/60/46d493db155d2ee2801b71fb1b0fd67696359047fdd8caee2c914cc50c79/hf_xet-1.4.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:39f2d2e9654cd9b4319885733993807aab6de9dfbd34c42f0b78338d6617421f", size = 3991546, upload-time = "2026-03-31T22:39:41.335Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f5/067363e1c96c6b17256910830d1b54099d06287e10f4ec6ec4e7e08371fc/hf_xet-1.4.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:49ad8a8cead2b56051aa84d7fce3e1335efe68df3cf6c058f22a65513885baac", size = 4193200, upload-time = "2026-03-31T22:40:01.936Z" }, + { url = "https://files.pythonhosted.org/packages/42/4b/53951592882d9c23080c7644542fda34a3813104e9e11fa1a7d82d419cb8/hf_xet-1.4.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7716d62015477a70ea272d2d68cd7cad140f61c52ee452e133e139abfe2c17ba", size = 4429392, upload-time = "2026-03-31T22:40:03.492Z" }, + { url = "https://files.pythonhosted.org/packages/8a/21/75a6c175b4e79662ad8e62f46a40ce341d8d6b206b06b4320d07d55b188c/hf_xet-1.4.3-cp37-abi3-win_amd64.whl", hash = "sha256:6b591fcad34e272a5b02607485e4f2a1334aebf1bc6d16ce8eb1eb8978ac2021", size = 3677359, upload-time = "2026-03-31T22:40:13.619Z" }, + { url = "https://files.pythonhosted.org/packages/8a/7c/44314ecd0e89f8b2b51c9d9e5e7a60a9c1c82024ac471d415860557d3cd8/hf_xet-1.4.3-cp37-abi3-win_arm64.whl", hash = "sha256:7c2c7e20bcfcc946dc67187c203463f5e932e395845d098cc2a93f5b67ca0b47", size = 3533664, upload-time = "2026-03-31T22:40:12.152Z" }, +] + +[[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]] @@ -1019,88 +773,70 @@ wheels = [ ] [package.optional-dependencies] +http2 = [ + { name = "h2" }, +] socks = [ { name = "socksio" }, ] [[package]] name = "huggingface-hub" -version = "0.35.3" +version = "1.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "fsspec" }, - { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, { name = "packaging" }, { name = "pyyaml" }, - { name = "requests" }, { name = "tqdm" }, + { name = "typer" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/7e/a0a97de7c73671863ca6b3f61fa12518caf35db37825e43d63a70956738c/huggingface_hub-0.35.3.tar.gz", hash = "sha256:350932eaa5cc6a4747efae85126ee220e4ef1b54e29d31c3b45c5612ddf0b32a", size = 461798, upload-time = "2025-09-29T14:29:58.625Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/89/e7aa12d8a6b9259bed10671abb25ae6fa437c0f88a86ecbf59617bae7759/huggingface_hub-1.11.0.tar.gz", hash = "sha256:15fb3713c7f9cdff7b808a94fd91664f661ab142796bb48c9cd9493e8d166278", size = 761749, upload-time = "2026-04-16T13:07:39.73Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/a0/651f93d154cb72323358bf2bbae3e642bdb5d2f1bfc874d096f7cb159fa0/huggingface_hub-0.35.3-py3-none-any.whl", hash = "sha256:0e3a01829c19d86d03793e4577816fe3bdfc1602ac62c7fb220d593d351224ba", size = 564262, upload-time = "2025-09-29T14:29:55.813Z" }, + { url = "https://files.pythonhosted.org/packages/37/02/4f3f8997d1ea7fe0146b343e5e14bd065fa87af790d07e5576d31b31cc18/huggingface_hub-1.11.0-py3-none-any.whl", hash = "sha256:42a6de0afbfeb5e022222d36398f029679db4eb4778801aafda32257ae9131ab", size = 645499, upload-time = "2026-04-16T13:07:37.716Z" }, ] [[package]] -name = "hyppo" -version = "0.5.2" +name = "humanfriendly" +version = "10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "autograd" }, - { name = "future" }, - { name = "numba" }, - { name = "numpy" }, - { name = "pandas" }, - { name = "patsy" }, - { name = "scikit-learn" }, - { name = "scipy" }, - { name = "statsmodels" }, + { name = "pyreadline3", marker = "python_full_version < '3.13' and sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dd/a6/0d84fe8486a1447da8bdb8ebb249d525fd8c1d0fe038bceb003c6e0513f9/hyppo-0.5.2.tar.gz", hash = "sha256:4634d15516248a43d25c241ed18beeb79bb3210360f7253693b3f154fe8c9879", size = 125115, upload-time = "2025-05-24T18:33:27.418Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/c4/d46858cfac3c0aad314a1fc378beae5c8cac499b677650a34b5a6a3d4328/hyppo-0.5.2-py3-none-any.whl", hash = "sha256:5cc18f9e158fe2cf1804c9a1e979e807118ee89a303f29dc5cb8891d92d44ef3", size = 192272, upload-time = "2025-05-24T18:33:25.904Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, ] [[package]] -name = "idna" -version = "3.10" +name = "hyperframe" +version = "6.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +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/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { 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 = "iniconfig" -version = "2.1.0" +name = "idna" +version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] -name = "instructor" -version = "1.11.3" +name = "iniconfig" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "diskcache" }, - { name = "docstring-parser" }, - { name = "jinja2" }, - { name = "jiter" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-core" }, - { name = "requests" }, - { name = "rich" }, - { name = "tenacity" }, - { name = "typer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6a/af/428b5d7a6a6eca5738c51706795a395099c141779cd1bbb9a6e2b0d3a94d/instructor-1.11.3.tar.gz", hash = "sha256:6f58fea6fadfa228c411ecdedad4662230c456718f4a770a97a806dcb36b3287", size = 69879936, upload-time = "2025-09-09T15:44:31.548Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/5f/54783e5b1a497de204a0a59b5e22549f67f5f1aceaa08e00db21b1107ce4/instructor-1.11.3-py3-none-any.whl", hash = "sha256:9ecd7a3780a045506165debad2ddcc4a30e1057f06997973185f356b0a42c6e3", size = 155501, upload-time = "2025-09-09T15:44:26.139Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] @@ -1196,142 +932,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" }, ] -[[package]] -name = "kiwisolver" -version = "1.4.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/5d/8ce64e36d4e3aac5ca96996457dcf33e34e6051492399a3f1fec5657f30b/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b", size = 124159, upload-time = "2025-08-10T21:25:35.472Z" }, - { url = "https://files.pythonhosted.org/packages/96/1e/22f63ec454874378175a5f435d6ea1363dd33fb2af832c6643e4ccea0dc8/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f", size = 66578, upload-time = "2025-08-10T21:25:36.73Z" }, - { url = "https://files.pythonhosted.org/packages/41/4c/1925dcfff47a02d465121967b95151c82d11027d5ec5242771e580e731bd/kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf", size = 65312, upload-time = "2025-08-10T21:25:37.658Z" }, - { url = "https://files.pythonhosted.org/packages/d4/42/0f333164e6307a0687d1eb9ad256215aae2f4bd5d28f4653d6cd319a3ba3/kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9", size = 1628458, upload-time = "2025-08-10T21:25:39.067Z" }, - { url = "https://files.pythonhosted.org/packages/86/b6/2dccb977d651943995a90bfe3495c2ab2ba5cd77093d9f2318a20c9a6f59/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415", size = 1225640, upload-time = "2025-08-10T21:25:40.489Z" }, - { url = "https://files.pythonhosted.org/packages/50/2b/362ebd3eec46c850ccf2bfe3e30f2fc4c008750011f38a850f088c56a1c6/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b", size = 1244074, upload-time = "2025-08-10T21:25:42.221Z" }, - { url = "https://files.pythonhosted.org/packages/6f/bb/f09a1e66dab8984773d13184a10a29fe67125337649d26bdef547024ed6b/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154", size = 1293036, upload-time = "2025-08-10T21:25:43.801Z" }, - { url = "https://files.pythonhosted.org/packages/ea/01/11ecf892f201cafda0f68fa59212edaea93e96c37884b747c181303fccd1/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48", size = 2175310, upload-time = "2025-08-10T21:25:45.045Z" }, - { url = "https://files.pythonhosted.org/packages/7f/5f/bfe11d5b934f500cc004314819ea92427e6e5462706a498c1d4fc052e08f/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220", size = 2270943, upload-time = "2025-08-10T21:25:46.393Z" }, - { url = "https://files.pythonhosted.org/packages/3d/de/259f786bf71f1e03e73d87e2db1a9a3bcab64d7b4fd780167123161630ad/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586", size = 2440488, upload-time = "2025-08-10T21:25:48.074Z" }, - { url = "https://files.pythonhosted.org/packages/1b/76/c989c278faf037c4d3421ec07a5c452cd3e09545d6dae7f87c15f54e4edf/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634", size = 2246787, upload-time = "2025-08-10T21:25:49.442Z" }, - { url = "https://files.pythonhosted.org/packages/a2/55/c2898d84ca440852e560ca9f2a0d28e6e931ac0849b896d77231929900e7/kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611", size = 73730, upload-time = "2025-08-10T21:25:51.102Z" }, - { url = "https://files.pythonhosted.org/packages/e8/09/486d6ac523dd33b80b368247f238125d027964cfacb45c654841e88fb2ae/kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536", size = 65036, upload-time = "2025-08-10T21:25:52.063Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, - { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, - { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, - { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, - { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, - { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, - { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, - { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, - { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, - { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, - { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, - { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, - { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, - { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, - { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, - { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, - { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, - { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, - { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, - { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, - { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, - { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, - { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, - { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, - { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, - { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, - { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, - { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, - { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, - { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, - { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, - { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, - { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, - { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, - { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, - { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, - { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, - { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, - { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, - { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, - { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, - { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, - { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, - { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, - { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, - { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, - { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, - { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, - { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, - { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, - { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, - { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, - { url = "https://files.pythonhosted.org/packages/a2/63/fde392691690f55b38d5dd7b3710f5353bf7a8e52de93a22968801ab8978/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527", size = 60183, upload-time = "2025-08-10T21:27:37.669Z" }, - { url = "https://files.pythonhosted.org/packages/27/b1/6aad34edfdb7cced27f371866f211332bba215bfd918ad3322a58f480d8b/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771", size = 58675, upload-time = "2025-08-10T21:27:39.031Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1a/23d855a702bb35a76faed5ae2ba3de57d323f48b1f6b17ee2176c4849463/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e", size = 80277, upload-time = "2025-08-10T21:27:40.129Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5b/5239e3c2b8fb5afa1e8508f721bb77325f740ab6994d963e61b2b7abcc1e/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9", size = 77994, upload-time = "2025-08-10T21:27:41.181Z" }, - { url = "https://files.pythonhosted.org/packages/f9/1c/5d4d468fb16f8410e596ed0eac02d2c68752aa7dc92997fe9d60a7147665/kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb", size = 73744, upload-time = "2025-08-10T21:27:42.254Z" }, - { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, - { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, - { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, - { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, -] - -[[package]] -name = "llvmlite" -version = "0.45.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/99/8d/5baf1cef7f9c084fb35a8afbde88074f0d6a727bc63ef764fe0e7543ba40/llvmlite-0.45.1.tar.gz", hash = "sha256:09430bb9d0bb58fc45a45a57c7eae912850bedc095cd0810a57de109c69e1c32", size = 185600, upload-time = "2025-10-01T17:59:52.046Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/6d/585c84ddd9d2a539a3c3487792b3cf3f988e28ec4fa281bf8b0e055e1166/llvmlite-0.45.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:1b1af0c910af0978aa55fa4f60bbb3e9f39b41e97c2a6d94d199897be62ba07a", size = 43043523, upload-time = "2025-10-01T18:02:58.621Z" }, - { url = "https://files.pythonhosted.org/packages/ae/34/992bd12d3ff245e0801bcf6013961daa8c19c9b9c2e61cb4b8bce94566f9/llvmlite-0.45.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02a164db2d79088bbd6e0d9633b4fe4021d6379d7e4ac7cc85ed5f44b06a30c5", size = 37253122, upload-time = "2025-10-01T18:03:55.159Z" }, - { url = "https://files.pythonhosted.org/packages/a6/7b/6d7585998a5991fa74dc925aae57913ba8c7c2efff909de9d34cc1cd3c27/llvmlite-0.45.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2d47f34e4029e6df3395de34cc1c66440a8d72712993a6e6168db228686711b", size = 56288210, upload-time = "2025-10-01T18:00:41.978Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e2/a4abea058633bfc82eb08fd69ce242c118fdb9b0abad1fdcbe0bc6aedab5/llvmlite-0.45.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7319e5f9f90720578a7f56fbc805bdfb4bc071b507c7611f170d631c3c0f1e0", size = 55140958, upload-time = "2025-10-01T18:01:55.694Z" }, - { url = "https://files.pythonhosted.org/packages/74/c0/233468e96ed287b953239c3b24b1d69df47c6ba9262bfdca98eda7e83a04/llvmlite-0.45.1-cp310-cp310-win_amd64.whl", hash = "sha256:4edb62e685867799e336723cb9787ec6598d51d0b1ed9af0f38e692aa757e898", size = 38132232, upload-time = "2025-10-01T18:04:41.538Z" }, - { url = "https://files.pythonhosted.org/packages/04/ad/9bdc87b2eb34642c1cfe6bcb4f5db64c21f91f26b010f263e7467e7536a3/llvmlite-0.45.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:60f92868d5d3af30b4239b50e1717cb4e4e54f6ac1c361a27903b318d0f07f42", size = 43043526, upload-time = "2025-10-01T18:03:15.051Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ea/c25c6382f452a943b4082da5e8c1665ce29a62884e2ec80608533e8e82d5/llvmlite-0.45.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98baab513e19beb210f1ef39066288784839a44cd504e24fff5d17f1b3cf0860", size = 37253118, upload-time = "2025-10-01T18:04:06.783Z" }, - { url = "https://files.pythonhosted.org/packages/fe/af/85fc237de98b181dbbe8647324331238d6c52a3554327ccdc83ced28efba/llvmlite-0.45.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3adc2355694d6a6fbcc024d59bb756677e7de506037c878022d7b877e7613a36", size = 56288209, upload-time = "2025-10-01T18:01:00.168Z" }, - { url = "https://files.pythonhosted.org/packages/0a/df/3daf95302ff49beff4230065e3178cd40e71294968e8d55baf4a9e560814/llvmlite-0.45.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f3377a6db40f563058c9515dedcc8a3e562d8693a106a28f2ddccf2c8fcf6ca", size = 55140958, upload-time = "2025-10-01T18:02:11.199Z" }, - { url = "https://files.pythonhosted.org/packages/a4/56/4c0d503fe03bac820ecdeb14590cf9a248e120f483bcd5c009f2534f23f0/llvmlite-0.45.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9c272682d91e0d57f2a76c6d9ebdfccc603a01828cdbe3d15273bdca0c3363a", size = 38132232, upload-time = "2025-10-01T18:04:52.181Z" }, - { url = "https://files.pythonhosted.org/packages/e2/7c/82cbd5c656e8991bcc110c69d05913be2229302a92acb96109e166ae31fb/llvmlite-0.45.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:28e763aba92fe9c72296911e040231d486447c01d4f90027c8e893d89d49b20e", size = 43043524, upload-time = "2025-10-01T18:03:30.666Z" }, - { url = "https://files.pythonhosted.org/packages/9d/bc/5314005bb2c7ee9f33102c6456c18cc81745d7055155d1218f1624463774/llvmlite-0.45.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1a53f4b74ee9fd30cb3d27d904dadece67a7575198bd80e687ee76474620735f", size = 37253123, upload-time = "2025-10-01T18:04:18.177Z" }, - { url = "https://files.pythonhosted.org/packages/96/76/0f7154952f037cb320b83e1c952ec4a19d5d689cf7d27cb8a26887d7bbc1/llvmlite-0.45.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b3796b1b1e1c14dcae34285d2f4ea488402fbd2c400ccf7137603ca3800864f", size = 56288211, upload-time = "2025-10-01T18:01:24.079Z" }, - { url = "https://files.pythonhosted.org/packages/00/b1/0b581942be2683ceb6862d558979e87387e14ad65a1e4db0e7dd671fa315/llvmlite-0.45.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:779e2f2ceefef0f4368548685f0b4adde34e5f4b457e90391f570a10b348d433", size = 55140958, upload-time = "2025-10-01T18:02:30.482Z" }, - { url = "https://files.pythonhosted.org/packages/33/94/9ba4ebcf4d541a325fd8098ddc073b663af75cc8b065b6059848f7d4dce7/llvmlite-0.45.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e6c9949baf25d9aa9cd7cf0f6d011b9ca660dd17f5ba2b23bdbdb77cc86b116", size = 38132231, upload-time = "2025-10-01T18:05:03.664Z" }, - { url = "https://files.pythonhosted.org/packages/1d/e2/c185bb7e88514d5025f93c6c4092f6120c6cea8fe938974ec9860fb03bbb/llvmlite-0.45.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:d9ea9e6f17569a4253515cc01dade70aba536476e3d750b2e18d81d7e670eb15", size = 43043524, upload-time = "2025-10-01T18:03:43.249Z" }, - { url = "https://files.pythonhosted.org/packages/09/b8/b5437b9ecb2064e89ccf67dccae0d02cd38911705112dd0dcbfa9cd9a9de/llvmlite-0.45.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:c9f3cadee1630ce4ac18ea38adebf2a4f57a89bd2740ce83746876797f6e0bfb", size = 37253121, upload-time = "2025-10-01T18:04:30.557Z" }, - { url = "https://files.pythonhosted.org/packages/f7/97/ad1a907c0173a90dd4df7228f24a3ec61058bc1a9ff8a0caec20a0cc622e/llvmlite-0.45.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:57c48bf2e1083eedbc9406fb83c4e6483017879714916fe8be8a72a9672c995a", size = 56288210, upload-time = "2025-10-01T18:01:40.26Z" }, - { url = "https://files.pythonhosted.org/packages/32/d8/c99c8ac7a326e9735401ead3116f7685a7ec652691aeb2615aa732b1fc4a/llvmlite-0.45.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3aa3dfceda4219ae39cf18806c60eeb518c1680ff834b8b311bd784160b9ce40", size = 55140957, upload-time = "2025-10-01T18:02:46.244Z" }, - { url = "https://files.pythonhosted.org/packages/09/56/ed35668130e32dbfad2eb37356793b0a95f23494ab5be7d9bf5cb75850ee/llvmlite-0.45.1-cp313-cp313-win_amd64.whl", hash = "sha256:080e6f8d0778a8239cd47686d402cb66eb165e421efa9391366a9b7e5810a38b", size = 38132232, upload-time = "2025-10-01T18:05:14.477Z" }, -] - [[package]] name = "loguru" version = "0.7.3" @@ -1442,80 +1042,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] -[[package]] -name = "matplotlib" -version = "3.10.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "cycler" }, - { name = "fonttools" }, - { name = "kiwisolver" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pillow" }, - { name = "pyparsing" }, - { name = "python-dateutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a0/59/c3e6453a9676ffba145309a73c462bb407f4400de7de3f2b41af70720a3c/matplotlib-3.10.6.tar.gz", hash = "sha256:ec01b645840dd1996df21ee37f208cd8ba57644779fa20464010638013d3203c", size = 34804264, upload-time = "2025-08-30T00:14:25.137Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/dc/ab89f7a5efd0cbaaebf2c3cf1881f4cba20c8925bb43f64511059df76895/matplotlib-3.10.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bc7316c306d97463a9866b89d5cc217824e799fa0de346c8f68f4f3d27c8693d", size = 8247159, upload-time = "2025-08-30T00:12:30.507Z" }, - { url = "https://files.pythonhosted.org/packages/30/a5/ddaee1a383ab28174093644fff7438eddb87bf8dbd58f7b85f5cdd6b2485/matplotlib-3.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d00932b0d160ef03f59f9c0e16d1e3ac89646f7785165ce6ad40c842db16cc2e", size = 8108011, upload-time = "2025-08-30T00:12:32.771Z" }, - { url = "https://files.pythonhosted.org/packages/75/5b/a53f69bb0522db352b1135bb57cd9fe00fd7252072409392d991d3a755d0/matplotlib-3.10.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fa4c43d6bfdbfec09c733bca8667de11bfa4970e8324c471f3a3632a0301c15", size = 8680518, upload-time = "2025-08-30T00:12:34.387Z" }, - { url = "https://files.pythonhosted.org/packages/5f/31/e059ddce95f68819b005a2d6820b2d6ed0307827a04598891f00649bed2d/matplotlib-3.10.6-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea117a9c1627acaa04dbf36265691921b999cbf515a015298e54e1a12c3af837", size = 9514997, upload-time = "2025-08-30T00:12:36.272Z" }, - { url = "https://files.pythonhosted.org/packages/66/d5/28b408a7c0f07b41577ee27e4454fe329e78ca21fe46ae7a27d279165fb5/matplotlib-3.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08fc803293b4e1694ee325896030de97f74c141ccff0be886bb5915269247676", size = 9566440, upload-time = "2025-08-30T00:12:41.675Z" }, - { url = "https://files.pythonhosted.org/packages/2d/99/8325b3386b479b1d182ab1a7fd588fd393ff00a99dc04b7cf7d06668cf0f/matplotlib-3.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:2adf92d9b7527fbfb8818e050260f0ebaa460f79d61546374ce73506c9421d09", size = 8108186, upload-time = "2025-08-30T00:12:43.621Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/5d3665aa44c49005aaacaa68ddea6fcb27345961cd538a98bb0177934ede/matplotlib-3.10.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:905b60d1cb0ee604ce65b297b61cf8be9f4e6cfecf95a3fe1c388b5266bc8f4f", size = 8257527, upload-time = "2025-08-30T00:12:45.31Z" }, - { url = "https://files.pythonhosted.org/packages/8c/af/30ddefe19ca67eebd70047dabf50f899eaff6f3c5e6a1a7edaecaf63f794/matplotlib-3.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bac38d816637343e53d7185d0c66677ff30ffb131044a81898b5792c956ba76", size = 8119583, upload-time = "2025-08-30T00:12:47.236Z" }, - { url = "https://files.pythonhosted.org/packages/d3/29/4a8650a3dcae97fa4f375d46efcb25920d67b512186f8a6788b896062a81/matplotlib-3.10.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:942a8de2b5bfff1de31d95722f702e2966b8a7e31f4e68f7cd963c7cd8861cf6", size = 8692682, upload-time = "2025-08-30T00:12:48.781Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d3/b793b9cb061cfd5d42ff0f69d1822f8d5dbc94e004618e48a97a8373179a/matplotlib-3.10.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3276c85370bc0dfca051ec65c5817d1e0f8f5ce1b7787528ec8ed2d524bbc2f", size = 9521065, upload-time = "2025-08-30T00:12:50.602Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c5/53de5629f223c1c66668d46ac2621961970d21916a4bc3862b174eb2a88f/matplotlib-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9df5851b219225731f564e4b9e7f2ac1e13c9e6481f941b5631a0f8e2d9387ce", size = 9576888, upload-time = "2025-08-30T00:12:52.92Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8e/0a18d6d7d2d0a2e66585032a760d13662e5250c784d53ad50434e9560991/matplotlib-3.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:abb5d9478625dd9c9eb51a06d39aae71eda749ae9b3138afb23eb38824026c7e", size = 8115158, upload-time = "2025-08-30T00:12:54.863Z" }, - { url = "https://files.pythonhosted.org/packages/07/b3/1a5107bb66c261e23b9338070702597a2d374e5aa7004b7adfc754fbed02/matplotlib-3.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:886f989ccfae63659183173bb3fced7fd65e9eb793c3cc21c273add368536951", size = 7992444, upload-time = "2025-08-30T00:12:57.067Z" }, - { url = "https://files.pythonhosted.org/packages/ea/1a/7042f7430055d567cc3257ac409fcf608599ab27459457f13772c2d9778b/matplotlib-3.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31ca662df6a80bd426f871105fdd69db7543e28e73a9f2afe80de7e531eb2347", size = 8272404, upload-time = "2025-08-30T00:12:59.112Z" }, - { url = "https://files.pythonhosted.org/packages/a9/5d/1d5f33f5b43f4f9e69e6a5fe1fb9090936ae7bc8e2ff6158e7a76542633b/matplotlib-3.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1678bb61d897bb4ac4757b5ecfb02bfb3fddf7f808000fb81e09c510712fda75", size = 8128262, upload-time = "2025-08-30T00:13:01.141Z" }, - { url = "https://files.pythonhosted.org/packages/67/c3/135fdbbbf84e0979712df58e5e22b4f257b3f5e52a3c4aacf1b8abec0d09/matplotlib-3.10.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:56cd2d20842f58c03d2d6e6c1f1cf5548ad6f66b91e1e48f814e4fb5abd1cb95", size = 8697008, upload-time = "2025-08-30T00:13:03.24Z" }, - { url = "https://files.pythonhosted.org/packages/9c/be/c443ea428fb2488a3ea7608714b1bd85a82738c45da21b447dc49e2f8e5d/matplotlib-3.10.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:662df55604a2f9a45435566d6e2660e41efe83cd94f4288dfbf1e6d1eae4b0bb", size = 9530166, upload-time = "2025-08-30T00:13:05.951Z" }, - { url = "https://files.pythonhosted.org/packages/a9/35/48441422b044d74034aea2a3e0d1a49023f12150ebc58f16600132b9bbaf/matplotlib-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:08f141d55148cd1fc870c3387d70ca4df16dee10e909b3b038782bd4bda6ea07", size = 9593105, upload-time = "2025-08-30T00:13:08.356Z" }, - { url = "https://files.pythonhosted.org/packages/45/c3/994ef20eb4154ab84cc08d033834555319e4af970165e6c8894050af0b3c/matplotlib-3.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:590f5925c2d650b5c9d813c5b3b5fc53f2929c3f8ef463e4ecfa7e052044fb2b", size = 8122784, upload-time = "2025-08-30T00:13:10.367Z" }, - { url = "https://files.pythonhosted.org/packages/57/b8/5c85d9ae0e40f04e71bedb053aada5d6bab1f9b5399a0937afb5d6b02d98/matplotlib-3.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:f44c8d264a71609c79a78d50349e724f5d5fc3684ead7c2a473665ee63d868aa", size = 7992823, upload-time = "2025-08-30T00:13:12.24Z" }, - { url = "https://files.pythonhosted.org/packages/a0/db/18380e788bb837e724358287b08e223b32bc8dccb3b0c12fa8ca20bc7f3b/matplotlib-3.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:819e409653c1106c8deaf62e6de6b8611449c2cd9939acb0d7d4e57a3d95cc7a", size = 8273231, upload-time = "2025-08-30T00:13:13.881Z" }, - { url = "https://files.pythonhosted.org/packages/d3/0f/38dd49445b297e0d4f12a322c30779df0d43cb5873c7847df8a82e82ec67/matplotlib-3.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:59c8ac8382fefb9cb71308dde16a7c487432f5255d8f1fd32473523abecfecdf", size = 8128730, upload-time = "2025-08-30T00:13:15.556Z" }, - { url = "https://files.pythonhosted.org/packages/e5/b8/9eea6630198cb303d131d95d285a024b3b8645b1763a2916fddb44ca8760/matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84e82d9e0fd70c70bc55739defbd8055c54300750cbacf4740c9673a24d6933a", size = 8698539, upload-time = "2025-08-30T00:13:17.297Z" }, - { url = "https://files.pythonhosted.org/packages/71/34/44c7b1f075e1ea398f88aeabcc2907c01b9cc99e2afd560c1d49845a1227/matplotlib-3.10.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25f7a3eb42d6c1c56e89eacd495661fc815ffc08d9da750bca766771c0fd9110", size = 9529702, upload-time = "2025-08-30T00:13:19.248Z" }, - { url = "https://files.pythonhosted.org/packages/b5/7f/e5c2dc9950c7facaf8b461858d1b92c09dd0cf174fe14e21953b3dda06f7/matplotlib-3.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9c862d91ec0b7842920a4cfdaaec29662195301914ea54c33e01f1a28d014b2", size = 9593742, upload-time = "2025-08-30T00:13:21.181Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1d/70c28528794f6410ee2856cd729fa1f1756498b8d3126443b0a94e1a8695/matplotlib-3.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:1b53bd6337eba483e2e7d29c5ab10eee644bc3a2491ec67cc55f7b44583ffb18", size = 8122753, upload-time = "2025-08-30T00:13:23.44Z" }, - { url = "https://files.pythonhosted.org/packages/e8/74/0e1670501fc7d02d981564caf7c4df42974464625935424ca9654040077c/matplotlib-3.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:cbd5eb50b7058b2892ce45c2f4e92557f395c9991f5c886d1bb74a1582e70fd6", size = 7992973, upload-time = "2025-08-30T00:13:26.632Z" }, - { url = "https://files.pythonhosted.org/packages/b1/4e/60780e631d73b6b02bd7239f89c451a72970e5e7ec34f621eda55cd9a445/matplotlib-3.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:acc86dd6e0e695c095001a7fccff158c49e45e0758fdf5dcdbb0103318b59c9f", size = 8316869, upload-time = "2025-08-30T00:13:28.262Z" }, - { url = "https://files.pythonhosted.org/packages/f8/15/baa662374a579413210fc2115d40c503b7360a08e9cc254aa0d97d34b0c1/matplotlib-3.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e228cd2ffb8f88b7d0b29e37f68ca9aaf83e33821f24a5ccc4f082dd8396bc27", size = 8178240, upload-time = "2025-08-30T00:13:30.007Z" }, - { url = "https://files.pythonhosted.org/packages/c6/3f/3c38e78d2aafdb8829fcd0857d25aaf9e7dd2dfcf7ec742765b585774931/matplotlib-3.10.6-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:658bc91894adeab669cf4bb4a186d049948262987e80f0857216387d7435d833", size = 8711719, upload-time = "2025-08-30T00:13:31.72Z" }, - { url = "https://files.pythonhosted.org/packages/96/4b/2ec2bbf8cefaa53207cc56118d1fa8a0f9b80642713ea9390235d331ede4/matplotlib-3.10.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8913b7474f6dd83ac444c9459c91f7f0f2859e839f41d642691b104e0af056aa", size = 9541422, upload-time = "2025-08-30T00:13:33.611Z" }, - { url = "https://files.pythonhosted.org/packages/83/7d/40255e89b3ef11c7871020563b2dd85f6cb1b4eff17c0f62b6eb14c8fa80/matplotlib-3.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:091cea22e059b89f6d7d1a18e2c33a7376c26eee60e401d92a4d6726c4e12706", size = 9594068, upload-time = "2025-08-30T00:13:35.833Z" }, - { url = "https://files.pythonhosted.org/packages/f0/a9/0213748d69dc842537a113493e1c27daf9f96bd7cc316f933dc8ec4de985/matplotlib-3.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:491e25e02a23d7207629d942c666924a6b61e007a48177fdd231a0097b7f507e", size = 8200100, upload-time = "2025-08-30T00:13:37.668Z" }, - { url = "https://files.pythonhosted.org/packages/be/15/79f9988066ce40b8a6f1759a934ea0cde8dc4adc2262255ee1bc98de6ad0/matplotlib-3.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3d80d60d4e54cda462e2cd9a086d85cd9f20943ead92f575ce86885a43a565d5", size = 8042142, upload-time = "2025-08-30T00:13:39.426Z" }, - { url = "https://files.pythonhosted.org/packages/7c/58/e7b6d292beae6fb4283ca6fb7fa47d7c944a68062d6238c07b497dd35493/matplotlib-3.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:70aaf890ce1d0efd482df969b28a5b30ea0b891224bb315810a3940f67182899", size = 8273802, upload-time = "2025-08-30T00:13:41.006Z" }, - { url = "https://files.pythonhosted.org/packages/9f/f6/7882d05aba16a8cdd594fb9a03a9d3cca751dbb6816adf7b102945522ee9/matplotlib-3.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1565aae810ab79cb72e402b22facfa6501365e73ebab70a0fdfb98488d2c3c0c", size = 8131365, upload-time = "2025-08-30T00:13:42.664Z" }, - { url = "https://files.pythonhosted.org/packages/94/bf/ff32f6ed76e78514e98775a53715eca4804b12bdcf35902cdd1cf759d324/matplotlib-3.10.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3b23315a01981689aa4e1a179dbf6ef9fbd17143c3eea77548c2ecfb0499438", size = 9533961, upload-time = "2025-08-30T00:13:44.372Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c3/6bf88c2fc2da7708a2ff8d2eeb5d68943130f50e636d5d3dcf9d4252e971/matplotlib-3.10.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:30fdd37edf41a4e6785f9b37969de57aea770696cb637d9946eb37470c94a453", size = 9804262, upload-time = "2025-08-30T00:13:46.614Z" }, - { url = "https://files.pythonhosted.org/packages/0f/7a/e05e6d9446d2d577b459427ad060cd2de5742d0e435db3191fea4fcc7e8b/matplotlib-3.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bc31e693da1c08012c764b053e702c1855378e04102238e6a5ee6a7117c53a47", size = 9595508, upload-time = "2025-08-30T00:13:48.731Z" }, - { url = "https://files.pythonhosted.org/packages/39/fb/af09c463ced80b801629fd73b96f726c9f6124c3603aa2e480a061d6705b/matplotlib-3.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:05be9bdaa8b242bc6ff96330d18c52f1fc59c6fb3a4dd411d953d67e7e1baf98", size = 8252742, upload-time = "2025-08-30T00:13:50.539Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f9/b682f6db9396d9ab8f050c0a3bfbb5f14fb0f6518f08507c04cc02f8f229/matplotlib-3.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:f56a0d1ab05d34c628592435781d185cd99630bdfd76822cd686fb5a0aecd43a", size = 8124237, upload-time = "2025-08-30T00:13:54.3Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d2/b69b4a0923a3c05ab90527c60fdec899ee21ca23ede7f0fb818e6620d6f2/matplotlib-3.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:94f0b4cacb23763b64b5dace50d5b7bfe98710fed5f0cef5c08135a03399d98b", size = 8316956, upload-time = "2025-08-30T00:13:55.932Z" }, - { url = "https://files.pythonhosted.org/packages/28/e9/dc427b6f16457ffaeecb2fc4abf91e5adb8827861b869c7a7a6d1836fa73/matplotlib-3.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cc332891306b9fb39462673d8225d1b824c89783fee82840a709f96714f17a5c", size = 8178260, upload-time = "2025-08-30T00:14:00.942Z" }, - { url = "https://files.pythonhosted.org/packages/c4/89/1fbd5ad611802c34d1c7ad04607e64a1350b7fb9c567c4ec2c19e066ed35/matplotlib-3.10.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee1d607b3fb1590deb04b69f02ea1d53ed0b0bf75b2b1a5745f269afcbd3cdd3", size = 9541422, upload-time = "2025-08-30T00:14:02.664Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/65fec8716025b22c1d72d5a82ea079934c76a547696eaa55be6866bc89b1/matplotlib-3.10.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:376a624a218116461696b27b2bbf7a8945053e6d799f6502fc03226d077807bf", size = 9803678, upload-time = "2025-08-30T00:14:04.741Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b0/40fb2b3a1ab9381bb39a952e8390357c8be3bdadcf6d5055d9c31e1b35ae/matplotlib-3.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:83847b47f6524c34b4f2d3ce726bb0541c48c8e7692729865c3df75bfa0f495a", size = 9594077, upload-time = "2025-08-30T00:14:07.012Z" }, - { url = "https://files.pythonhosted.org/packages/76/34/c4b71b69edf5b06e635eee1ed10bfc73cf8df058b66e63e30e6a55e231d5/matplotlib-3.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c7e0518e0d223683532a07f4b512e2e0729b62674f1b3a1a69869f98e6b1c7e3", size = 8342822, upload-time = "2025-08-30T00:14:09.041Z" }, - { url = "https://files.pythonhosted.org/packages/e8/62/aeabeef1a842b6226a30d49dd13e8a7a1e81e9ec98212c0b5169f0a12d83/matplotlib-3.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:4dd83e029f5b4801eeb87c64efd80e732452781c16a9cf7415b7b63ec8f374d7", size = 8172588, upload-time = "2025-08-30T00:14:11.166Z" }, - { url = "https://files.pythonhosted.org/packages/17/6f/2551e45bea2938e0363ccdd54fa08dae7605ce782d4332497d31a7b97672/matplotlib-3.10.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:13fcd07ccf17e354398358e0307a1f53f5325dca22982556ddb9c52837b5af41", size = 8241220, upload-time = "2025-08-30T00:14:12.888Z" }, - { url = "https://files.pythonhosted.org/packages/54/7e/0f4c6e8b98105fdb162a4efde011af204ca47d7c05d735aff480ebfead1b/matplotlib-3.10.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:470fc846d59d1406e34fa4c32ba371039cd12c2fe86801159a965956f2575bd1", size = 8104624, upload-time = "2025-08-30T00:14:14.511Z" }, - { url = "https://files.pythonhosted.org/packages/27/27/c29696702b9317a6ade1ba6f8861e02d7423f18501729203d7a80b686f23/matplotlib-3.10.6-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7173f8551b88f4ef810a94adae3128c2530e0d07529f7141be7f8d8c365f051", size = 8682271, upload-time = "2025-08-30T00:14:17.273Z" }, - { url = "https://files.pythonhosted.org/packages/12/bb/02c35a51484aae5f49bd29f091286e7af5f3f677a9736c58a92b3c78baeb/matplotlib-3.10.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f2d684c3204fa62421bbf770ddfebc6b50130f9cad65531eeba19236d73bb488", size = 8252296, upload-time = "2025-08-30T00:14:19.49Z" }, - { url = "https://files.pythonhosted.org/packages/7d/85/41701e3092005aee9a2445f5ee3904d9dbd4a7df7a45905ffef29b7ef098/matplotlib-3.10.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6f4a69196e663a41d12a728fab8751177215357906436804217d6d9cf0d4d6cf", size = 8116749, upload-time = "2025-08-30T00:14:21.344Z" }, - { url = "https://files.pythonhosted.org/packages/16/53/8d8fa0ea32a8c8239e04d022f6c059ee5e1b77517769feccd50f1df43d6d/matplotlib-3.10.6-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d6ca6ef03dfd269f4ead566ec6f3fb9becf8dab146fb999022ed85ee9f6b3eb", size = 8693933, upload-time = "2025-08-30T00:14:22.942Z" }, -] - [[package]] name = "mdurl" version = "0.1.2" @@ -1525,6 +1051,62 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "mmh3" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/96/aa247e82878b123468f0079ce2ac77e948315bab91ce45d2934a62e0af95/mmh3-4.1.0.tar.gz", hash = "sha256:a1cf25348b9acd229dda464a094d6170f47d2850a1fcb762a3b6172d2ce6ca4a", size = 26357, upload-time = "2024-01-09T06:46:04.536Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/5a/8609dc74421858f7e94a89dc69221ab9b2c14d0d63a139b46ec190eedc44/mmh3-4.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be5ac76a8b0cd8095784e51e4c1c9c318c19edcd1709a06eb14979c8d850c31a", size = 39433, upload-time = "2024-01-09T06:44:25.903Z" }, + { url = "https://files.pythonhosted.org/packages/93/6c/e7a0f07c7082c76964b1ff46aa852f36e2ec6a9c3530dec0afa0b3162fc2/mmh3-4.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98a49121afdfab67cd80e912b36404139d7deceb6773a83620137aaa0da5714c", size = 29280, upload-time = "2024-01-09T06:44:27.035Z" }, + { url = "https://files.pythonhosted.org/packages/76/84/60ca728ec7d7e1779a98000d64941c6221786124b4f07bf105a627055890/mmh3-4.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5259ac0535874366e7d1a5423ef746e0d36a9e3c14509ce6511614bdc5a7ef5b", size = 30130, upload-time = "2024-01-09T06:44:28.502Z" }, + { url = "https://files.pythonhosted.org/packages/2a/22/f2ec190b491f712d9ef5ea6252204b6f05255ac9af54a7b505adc3128aed/mmh3-4.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5950827ca0453a2be357696da509ab39646044e3fa15cad364eb65d78797437", size = 68837, upload-time = "2024-01-09T06:44:29.959Z" }, + { url = "https://files.pythonhosted.org/packages/ae/b9/c1e8065671e1d2f4e280c9c57389e74964f4a5792cac26717ad592002c7d/mmh3-4.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1dd0f652ae99585b9dd26de458e5f08571522f0402155809fd1dc8852a613a39", size = 72275, upload-time = "2024-01-09T06:44:31.02Z" }, + { url = "https://files.pythonhosted.org/packages/6b/18/92bbdb102ab2b4e80084e927187d871758280eb067c649693e42bfc6d0d1/mmh3-4.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99d25548070942fab1e4a6f04d1626d67e66d0b81ed6571ecfca511f3edf07e6", size = 70919, upload-time = "2024-01-09T06:44:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cd/391ce1d1bb559871a5d3a6bbb30b82bf51d3e3b42c4e8589cccb201953da/mmh3-4.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53db8d9bad3cb66c8f35cbc894f336273f63489ce4ac416634932e3cbe79eb5b", size = 65885, upload-time = "2024-01-09T06:44:34.462Z" }, + { url = "https://files.pythonhosted.org/packages/03/87/4b01a43336bd506478850d1bc3d180648b2d26b4acf1fc4bf1df72bf562f/mmh3-4.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75da0f615eb55295a437264cc0b736753f830b09d102aa4c2a7d719bc445ec05", size = 67610, upload-time = "2024-01-09T06:44:35.589Z" }, + { url = "https://files.pythonhosted.org/packages/e8/12/b464149a1b7181c7ce431ebf3d24fa994863f2f1abc75b78d202dde966e0/mmh3-4.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b926b07fd678ea84b3a2afc1fa22ce50aeb627839c44382f3d0291e945621e1a", size = 74888, upload-time = "2024-01-09T06:44:36.532Z" }, + { url = "https://files.pythonhosted.org/packages/fc/3e/f4eb45a23fc17b970394c1fe74eba157514577ae2d63757684241651d754/mmh3-4.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c5b053334f9b0af8559d6da9dc72cef0a65b325ebb3e630c680012323c950bb6", size = 72969, upload-time = "2024-01-09T06:44:37.473Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3b/83934fd9494371357da0ca026d55ad427c199d611b97b6ffeecacfd8e720/mmh3-4.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5bf33dc43cd6de2cb86e0aa73a1cc6530f557854bbbe5d59f41ef6de2e353d7b", size = 80338, upload-time = "2024-01-09T06:44:38.523Z" }, + { url = "https://files.pythonhosted.org/packages/b6/c4/5bcd709ea7269173d7e925402f05e05cf12194ef53cc9912a5ad166f8ded/mmh3-4.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fa7eacd2b830727ba3dd65a365bed8a5c992ecd0c8348cf39a05cc77d22f4970", size = 76580, upload-time = "2024-01-09T06:44:39.505Z" }, + { url = "https://files.pythonhosted.org/packages/da/6a/4c0680d64475e551d7f4cc78bf0fd247c711ed2717f6bb311934993d1e69/mmh3-4.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:42dfd6742b9e3eec599f85270617debfa0bbb913c545bb980c8a4fa7b2d047da", size = 75325, upload-time = "2024-01-09T06:44:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/e2ed99e580b3dd121f6462147bd5f521c57b3c81c692aa2d416b0678c89f/mmh3-4.1.0-cp310-cp310-win32.whl", hash = "sha256:2974ad343f0d39dcc88e93ee6afa96cedc35a9883bc067febd7ff736e207fa47", size = 31235, upload-time = "2024-01-09T06:44:41.467Z" }, + { url = "https://files.pythonhosted.org/packages/73/2b/3aec865da7feb52830782d9fb7c54115cc18815680c244301adf9080622f/mmh3-4.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:74699a8984ded645c1a24d6078351a056f5a5f1fe5838870412a68ac5e28d865", size = 31271, upload-time = "2024-01-09T06:44:42.881Z" }, + { url = "https://files.pythonhosted.org/packages/17/2a/925439189ccf562bdcb839aed6263d718359f0c376d673beb3b83d3864ac/mmh3-4.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f0dc874cedc23d46fc488a987faa6ad08ffa79e44fb08e3cd4d4cf2877c00a00", size = 30147, upload-time = "2024-01-09T06:44:44.173Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d6/86beea107e7e9700df9522466346c23a2f54faa81337c86fd17002aa95a6/mmh3-4.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3280a463855b0eae64b681cd5b9ddd9464b73f81151e87bb7c91a811d25619e6", size = 39427, upload-time = "2024-01-09T06:44:45.686Z" }, + { url = "https://files.pythonhosted.org/packages/1c/08/65fa5489044e2afc304e8540c6c607d5d7b136ddc5cd8315c13de0adc34c/mmh3-4.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:97ac57c6c3301769e757d444fa7c973ceb002cb66534b39cbab5e38de61cd896", size = 29281, upload-time = "2024-01-09T06:44:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/b3/aa/98511d3ea3f6ba958136d913be3be3c1009be935a20ecc7b2763f0a605b6/mmh3-4.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7b6502cdb4dbd880244818ab363c8770a48cdccecf6d729ade0241b736b5ec0", size = 30130, upload-time = "2024-01-09T06:44:47.463Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/1a93f81643435b0e57f1046c4ffe46f0214693eaede0d9b0a1a236776e70/mmh3-4.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52ba2da04671a9621580ddabf72f06f0e72c1c9c3b7b608849b58b11080d8f14", size = 69072, upload-time = "2024-01-09T06:44:48.385Z" }, + { url = "https://files.pythonhosted.org/packages/45/9e/2ff70246aefd9cf146bc6a420c28ed475a0d1a325f31ee203be02f9215d4/mmh3-4.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a5fef4c4ecc782e6e43fbeab09cff1bac82c998a1773d3a5ee6a3605cde343e", size = 72470, upload-time = "2024-01-09T06:44:49.291Z" }, + { url = "https://files.pythonhosted.org/packages/dc/cb/57bc1fdbdbe6837aebfca982494e23e2498ee2a89585c9054713b22e4167/mmh3-4.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5135358a7e00991f73b88cdc8eda5203bf9de22120d10a834c5761dbeb07dd13", size = 71251, upload-time = "2024-01-09T06:44:50.839Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c2/46d7d2721b69fbdfd30231309e6395f62ff6744e5c00dd8113b9faa06fba/mmh3-4.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cff9ae76a54f7c6fe0167c9c4028c12c1f6de52d68a31d11b6790bb2ae685560", size = 66035, upload-time = "2024-01-09T06:44:52.407Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a4/7ba4bcc838818bcf018e26d118d5ddb605c23c4fad040dc4d811f1cfcb04/mmh3-4.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f02576a4d106d7830ca90278868bf0983554dd69183b7bbe09f2fcd51cf54f", size = 67844, upload-time = "2024-01-09T06:44:53.566Z" }, + { url = "https://files.pythonhosted.org/packages/71/ed/8e80d1038e7bb15eaf739711d1fc36f2341acb6b1b95fa77003f2799c91e/mmh3-4.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:073d57425a23721730d3ff5485e2da489dd3c90b04e86243dd7211f889898106", size = 76724, upload-time = "2024-01-09T06:44:54.51Z" }, + { url = "https://files.pythonhosted.org/packages/1c/22/a6a70ca81f0ce8fe2f3a68d89c1184c2d2d0fbe0ee305da50e972c5ff9fa/mmh3-4.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:71e32ddec7f573a1a0feb8d2cf2af474c50ec21e7a8263026e8d3b4b629805db", size = 75004, upload-time = "2024-01-09T06:44:55.517Z" }, + { url = "https://files.pythonhosted.org/packages/73/20/abe50b605760f1f5b6e0b436c650649e69ca478d0f41b154f300367c09e4/mmh3-4.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7cbb20b29d57e76a58b40fd8b13a9130db495a12d678d651b459bf61c0714cea", size = 82230, upload-time = "2024-01-09T06:44:56.538Z" }, + { url = "https://files.pythonhosted.org/packages/45/80/a1fc99d3ee50b573df0bfbb1ad518463af78d2ebca44bfca3b3f9473d651/mmh3-4.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:a42ad267e131d7847076bb7e31050f6c4378cd38e8f1bf7a0edd32f30224d5c9", size = 78679, upload-time = "2024-01-09T06:44:57.477Z" }, + { url = "https://files.pythonhosted.org/packages/9e/51/6c9ee2ddf3b386f45ff83b6926a5e826635757d91dab04cbf16eee05f9a7/mmh3-4.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a013979fc9390abadc445ea2527426a0e7a4495c19b74589204f9b71bcaafeb", size = 77382, upload-time = "2024-01-09T06:44:59.02Z" }, + { url = "https://files.pythonhosted.org/packages/ee/fa/4b377f244c27fac5f0343cc4dc0d2eb0a08049afc8d5322d07be7461a768/mmh3-4.1.0-cp311-cp311-win32.whl", hash = "sha256:1d3b1cdad7c71b7b88966301789a478af142bddcb3a2bee563f7a7d40519a00f", size = 31232, upload-time = "2024-01-09T06:45:01.285Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b0/500ef56c29b276d796bfdb47c16d34fa18a68945e4d730a6fa7d483583ed/mmh3-4.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0dc6dc32eb03727467da8e17deffe004fbb65e8b5ee2b502d36250d7a3f4e2ec", size = 31276, upload-time = "2024-01-09T06:45:03.417Z" }, + { url = "https://files.pythonhosted.org/packages/cc/84/94795e6e710c3861f8f355a12be9c9f4b8433a538c983e75bd4c00496a8a/mmh3-4.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9ae3a5c1b32dda121c7dc26f9597ef7b01b4c56a98319a7fe86c35b8bc459ae6", size = 30142, upload-time = "2024-01-09T06:45:05.347Z" }, + { url = "https://files.pythonhosted.org/packages/18/45/b4d41e86b00eed8c500adbe0007129861710e181c7f49c507ef6beae9496/mmh3-4.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0033d60c7939168ef65ddc396611077a7268bde024f2c23bdc283a19123f9e9c", size = 39495, upload-time = "2024-01-09T06:45:07.01Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d4/f041b8704cb8d1aad3717105daa582e29818b78a540622dfed84cd00d88f/mmh3-4.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d6af3e2287644b2b08b5924ed3a88c97b87b44ad08e79ca9f93d3470a54a41c5", size = 29334, upload-time = "2024-01-09T06:45:08.022Z" }, + { url = "https://files.pythonhosted.org/packages/cb/bb/8f75378e1a83b323f9ed06248333c383e7dac614c2f95e1419965cb91693/mmh3-4.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d82eb4defa245e02bb0b0dc4f1e7ee284f8d212633389c91f7fba99ba993f0a2", size = 30144, upload-time = "2024-01-09T06:45:09.437Z" }, + { url = "https://files.pythonhosted.org/packages/3e/50/5e36c1945bd83e780a37361fc1999fc4c5a59ecc10a373557fdf0e58eb1f/mmh3-4.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba245e94b8d54765e14c2d7b6214e832557e7856d5183bc522e17884cab2f45d", size = 69094, upload-time = "2024-01-09T06:45:10.531Z" }, + { url = "https://files.pythonhosted.org/packages/70/c7/6ae37e7519a938226469476b84bcea2650e2a2cc7a848e6a206ea98ecee3/mmh3-4.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb04e2feeabaad6231e89cd43b3d01a4403579aa792c9ab6fdeef45cc58d4ec0", size = 72611, upload-time = "2024-01-09T06:45:12.27Z" }, + { url = "https://files.pythonhosted.org/packages/5e/47/6613f69f57f1e5045e66b22fae9c2fb39ef754c455805d3917f6073e316e/mmh3-4.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e3b1a27def545ce11e36158ba5d5390cdbc300cfe456a942cc89d649cf7e3b2", size = 71462, upload-time = "2024-01-09T06:45:13.274Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0a/e423db18ce7b479c4b96381a112b443f0985c611de420f95c58a9f934080/mmh3-4.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce0ab79ff736d7044e5e9b3bfe73958a55f79a4ae672e6213e92492ad5e734d5", size = 66165, upload-time = "2024-01-09T06:45:15.003Z" }, + { url = "https://files.pythonhosted.org/packages/4c/7b/bfeb68bee5bddc8baf7ef630b93edc0a533202d84eb076dbb6c77e7e5fd5/mmh3-4.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b02268be6e0a8eeb8a924d7db85f28e47344f35c438c1e149878bb1c47b1cd3", size = 68088, upload-time = "2024-01-09T06:45:16.192Z" }, + { url = "https://files.pythonhosted.org/packages/d4/a6/b82e30143997c05776887f5177f724e3b714aa7e7346fbe2ec70f52abcd0/mmh3-4.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:deb887f5fcdaf57cf646b1e062d56b06ef2f23421c80885fce18b37143cba828", size = 76241, upload-time = "2024-01-09T06:45:17.191Z" }, + { url = "https://files.pythonhosted.org/packages/6c/60/a3d5872cf7610fcb13e36c472476020c5cf217b23c092bad452eb7784407/mmh3-4.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99dd564e9e2b512eb117bd0cbf0f79a50c45d961c2a02402787d581cec5448d5", size = 74538, upload-time = "2024-01-09T06:45:18.999Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d5/742173a94c78f4edab71c04097f6f9150c47f8fd034d592f5f34a9444719/mmh3-4.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:08373082dfaa38fe97aa78753d1efd21a1969e51079056ff552e687764eafdfe", size = 81793, upload-time = "2024-01-09T06:45:20.534Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/a1db0efe7c67b761d83be3d50e35ef26628ef56b3b8bc776d07412ee8b16/mmh3-4.1.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:54b9c6a2ea571b714e4fe28d3e4e2db37abfd03c787a58074ea21ee9a8fd1740", size = 78217, upload-time = "2024-01-09T06:45:21.761Z" }, + { url = "https://files.pythonhosted.org/packages/b3/78/1ff8da7c859cd09704e2f500588d171eda9688fcf6f29e028ef261262a16/mmh3-4.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a7b1edf24c69e3513f879722b97ca85e52f9032f24a52284746877f6a7304086", size = 77052, upload-time = "2024-01-09T06:45:22.824Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c7/cf16ace81fc9fbe54a75c914306252af26c6ea485366bb3b579bf6e3dbb8/mmh3-4.1.0-cp312-cp312-win32.whl", hash = "sha256:411da64b951f635e1e2284b71d81a5a83580cea24994b328f8910d40bed67276", size = 31277, upload-time = "2024-01-09T06:45:24.009Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0b/b3b1637dca9414451edf287fd91e667e7231d5ffd7498137fe011951fc0a/mmh3-4.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bebc3ecb6ba18292e3d40c8712482b4477abd6981c2ebf0e60869bd90f8ac3a9", size = 31318, upload-time = "2024-01-09T06:45:25.169Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6c/c0f06040c58112ccbd0df989055ede98f7c1a1f392dc6a3fc63ec6c124ec/mmh3-4.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:168473dd608ade6a8d2ba069600b35199a9af837d96177d3088ca91f2b3798e3", size = 30147, upload-time = "2024-01-09T06:45:26.214Z" }, +] + [[package]] name = "mpmath" version = "1.3.0" @@ -1534,150 +1116,13 @@ 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.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/63/7bdd4adc330abcca54c85728db2327130e49e52e8c3ce685cec44e0f2e9f/multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349", size = 77153, upload-time = "2025-10-06T14:48:26.409Z" }, - { url = "https://files.pythonhosted.org/packages/3f/bb/b6c35ff175ed1a3142222b78455ee31be71a8396ed3ab5280fbe3ebe4e85/multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e", size = 44993, upload-time = "2025-10-06T14:48:28.4Z" }, - { url = "https://files.pythonhosted.org/packages/e0/1f/064c77877c5fa6df6d346e68075c0f6998547afe952d6471b4c5f6a7345d/multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3", size = 44607, upload-time = "2025-10-06T14:48:29.581Z" }, - { url = "https://files.pythonhosted.org/packages/04/7a/bf6aa92065dd47f287690000b3d7d332edfccb2277634cadf6a810463c6a/multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046", size = 241847, upload-time = "2025-10-06T14:48:32.107Z" }, - { url = "https://files.pythonhosted.org/packages/94/39/297a8de920f76eda343e4ce05f3b489f0ab3f9504f2576dfb37b7c08ca08/multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32", size = 242616, upload-time = "2025-10-06T14:48:34.054Z" }, - { url = "https://files.pythonhosted.org/packages/39/3a/d0eee2898cfd9d654aea6cb8c4addc2f9756e9a7e09391cfe55541f917f7/multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73", size = 222333, upload-time = "2025-10-06T14:48:35.9Z" }, - { url = "https://files.pythonhosted.org/packages/05/48/3b328851193c7a4240815b71eea165b49248867bbb6153a0aee227a0bb47/multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc", size = 253239, upload-time = "2025-10-06T14:48:37.302Z" }, - { url = "https://files.pythonhosted.org/packages/b1/ca/0706a98c8d126a89245413225ca4a3fefc8435014de309cf8b30acb68841/multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62", size = 251618, upload-time = "2025-10-06T14:48:38.963Z" }, - { url = "https://files.pythonhosted.org/packages/5e/4f/9c7992f245554d8b173f6f0a048ad24b3e645d883f096857ec2c0822b8bd/multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84", size = 241655, upload-time = "2025-10-06T14:48:40.312Z" }, - { url = "https://files.pythonhosted.org/packages/31/79/26a85991ae67efd1c0b1fc2e0c275b8a6aceeb155a68861f63f87a798f16/multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0", size = 239245, upload-time = "2025-10-06T14:48:41.848Z" }, - { url = "https://files.pythonhosted.org/packages/14/1e/75fa96394478930b79d0302eaf9a6c69f34005a1a5251ac8b9c336486ec9/multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e", size = 233523, upload-time = "2025-10-06T14:48:43.749Z" }, - { url = "https://files.pythonhosted.org/packages/b2/5e/085544cb9f9c4ad2b5d97467c15f856df8d9bac410cffd5c43991a5d878b/multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4", size = 243129, upload-time = "2025-10-06T14:48:45.225Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c3/e9d9e2f20c9474e7a8fcef28f863c5cbd29bb5adce6b70cebe8bdad0039d/multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648", size = 248999, upload-time = "2025-10-06T14:48:46.703Z" }, - { url = "https://files.pythonhosted.org/packages/b5/3f/df171b6efa3239ae33b97b887e42671cd1d94d460614bfb2c30ffdab3b95/multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111", size = 243711, upload-time = "2025-10-06T14:48:48.146Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2f/9b5564888c4e14b9af64c54acf149263721a283aaf4aa0ae89b091d5d8c1/multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36", size = 237504, upload-time = "2025-10-06T14:48:49.447Z" }, - { url = "https://files.pythonhosted.org/packages/6c/3a/0bd6ca0f7d96d790542d591c8c3354c1e1b6bfd2024d4d92dc3d87485ec7/multidict-6.7.0-cp310-cp310-win32.whl", hash = "sha256:afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85", size = 41422, upload-time = "2025-10-06T14:48:50.789Z" }, - { url = "https://files.pythonhosted.org/packages/00/35/f6a637ea2c75f0d3b7c7d41b1189189acff0d9deeb8b8f35536bb30f5e33/multidict-6.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7", size = 46050, upload-time = "2025-10-06T14:48:51.938Z" }, - { url = "https://files.pythonhosted.org/packages/e7/b8/f7bf8329b39893d02d9d95cf610c75885d12fc0f402b1c894e1c8e01c916/multidict-6.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0", size = 43153, upload-time = "2025-10-06T14:48:53.146Z" }, - { url = "https://files.pythonhosted.org/packages/34/9e/5c727587644d67b2ed479041e4b1c58e30afc011e3d45d25bbe35781217c/multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc", size = 76604, upload-time = "2025-10-06T14:48:54.277Z" }, - { url = "https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721", size = 44715, upload-time = "2025-10-06T14:48:55.445Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6", size = 44332, upload-time = "2025-10-06T14:48:56.706Z" }, - { url = "https://files.pythonhosted.org/packages/31/61/0c2d50241ada71ff61a79518db85ada85fdabfcf395d5968dae1cbda04e5/multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c", size = 245212, upload-time = "2025-10-06T14:48:58.042Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e0/919666a4e4b57fff1b57f279be1c9316e6cdc5de8a8b525d76f6598fefc7/multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7", size = 246671, upload-time = "2025-10-06T14:49:00.004Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cc/d027d9c5a520f3321b65adea289b965e7bcbd2c34402663f482648c716ce/multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7", size = 225491, upload-time = "2025-10-06T14:49:01.393Z" }, - { url = "https://files.pythonhosted.org/packages/75/c4/bbd633980ce6155a28ff04e6a6492dd3335858394d7bb752d8b108708558/multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9", size = 257322, upload-time = "2025-10-06T14:49:02.745Z" }, - { url = "https://files.pythonhosted.org/packages/4c/6d/d622322d344f1f053eae47e033b0b3f965af01212de21b10bcf91be991fb/multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8", size = 254694, upload-time = "2025-10-06T14:49:04.15Z" }, - { url = "https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd", size = 246715, upload-time = "2025-10-06T14:49:05.967Z" }, - { url = "https://files.pythonhosted.org/packages/78/59/950818e04f91b9c2b95aab3d923d9eabd01689d0dcd889563988e9ea0fd8/multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb", size = 243189, upload-time = "2025-10-06T14:49:07.37Z" }, - { url = "https://files.pythonhosted.org/packages/7a/3d/77c79e1934cad2ee74991840f8a0110966d9599b3af95964c0cd79bb905b/multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6", size = 237845, upload-time = "2025-10-06T14:49:08.759Z" }, - { url = "https://files.pythonhosted.org/packages/63/1b/834ce32a0a97a3b70f86437f685f880136677ac00d8bce0027e9fd9c2db7/multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2", size = 246374, upload-time = "2025-10-06T14:49:10.574Z" }, - { url = "https://files.pythonhosted.org/packages/23/ef/43d1c3ba205b5dec93dc97f3fba179dfa47910fc73aaaea4f7ceb41cec2a/multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff", size = 253345, upload-time = "2025-10-06T14:49:12.331Z" }, - { url = "https://files.pythonhosted.org/packages/6b/03/eaf95bcc2d19ead522001f6a650ef32811aa9e3624ff0ad37c445c7a588c/multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b", size = 246940, upload-time = "2025-10-06T14:49:13.821Z" }, - { url = "https://files.pythonhosted.org/packages/e8/df/ec8a5fd66ea6cd6f525b1fcbb23511b033c3e9bc42b81384834ffa484a62/multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34", size = 242229, upload-time = "2025-10-06T14:49:15.603Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a2/59b405d59fd39ec86d1142630e9049243015a5f5291ba49cadf3c090c541/multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff", size = 41308, upload-time = "2025-10-06T14:49:16.871Z" }, - { url = "https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81", size = 46037, upload-time = "2025-10-06T14:49:18.457Z" }, - { url = "https://files.pythonhosted.org/packages/84/1f/68588e31b000535a3207fd3c909ebeec4fb36b52c442107499c18a896a2a/multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912", size = 43023, upload-time = "2025-10-06T14:49:19.648Z" }, - { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, - { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, - { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, - { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, - { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, - { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, - { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, - { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, - { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, - { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, - { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, - { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, - { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, - { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, - { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, - { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, - { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, - { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload-time = "2025-10-06T14:49:54.26Z" }, - { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload-time = "2025-10-06T14:49:55.82Z" }, - { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload-time = "2025-10-06T14:49:57.048Z" }, - { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload-time = "2025-10-06T14:49:58.368Z" }, - { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload-time = "2025-10-06T14:49:59.89Z" }, - { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload-time = "2025-10-06T14:50:01.485Z" }, - { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload-time = "2025-10-06T14:50:02.955Z" }, - { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload-time = "2025-10-06T14:50:04.446Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload-time = "2025-10-06T14:50:05.98Z" }, - { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload-time = "2025-10-06T14:50:07.511Z" }, - { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload-time = "2025-10-06T14:50:09.074Z" }, - { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload-time = "2025-10-06T14:50:10.714Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload-time = "2025-10-06T14:50:12.28Z" }, - { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload-time = "2025-10-06T14:50:14.16Z" }, - { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload-time = "2025-10-06T14:50:15.639Z" }, - { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload-time = "2025-10-06T14:50:17.066Z" }, - { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload-time = "2025-10-06T14:50:18.264Z" }, - { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload-time = "2025-10-06T14:50:19.853Z" }, - { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload-time = "2025-10-06T14:50:21.223Z" }, - { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload-time = "2025-10-06T14:50:22.871Z" }, - { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload-time = "2025-10-06T14:50:24.258Z" }, - { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload-time = "2025-10-06T14:50:25.716Z" }, - { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload-time = "2025-10-06T14:50:28.192Z" }, - { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload-time = "2025-10-06T14:50:29.82Z" }, - { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload-time = "2025-10-06T14:50:31.731Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload-time = "2025-10-06T14:50:33.26Z" }, - { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload-time = "2025-10-06T14:50:34.808Z" }, - { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload-time = "2025-10-06T14:50:36.436Z" }, - { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload-time = "2025-10-06T14:50:37.953Z" }, - { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload-time = "2025-10-06T14:50:39.574Z" }, - { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload-time = "2025-10-06T14:50:41.612Z" }, - { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload-time = "2025-10-06T14:50:43.972Z" }, - { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload-time = "2025-10-06T14:50:45.648Z" }, - { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload-time = "2025-10-06T14:50:47.154Z" }, - { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload-time = "2025-10-06T14:50:48.851Z" }, - { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload-time = "2025-10-06T14:50:50.16Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload-time = "2025-10-06T14:50:51.92Z" }, - { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload-time = "2025-10-06T14:50:53.275Z" }, - { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload-time = "2025-10-06T14:50:54.911Z" }, - { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload-time = "2025-10-06T14:50:56.369Z" }, - { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload-time = "2025-10-06T14:50:57.991Z" }, - { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload-time = "2025-10-06T14:50:59.589Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload-time = "2025-10-06T14:51:01.183Z" }, - { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload-time = "2025-10-06T14:51:02.794Z" }, - { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload-time = "2025-10-06T14:51:04.724Z" }, - { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload-time = "2025-10-06T14:51:06.306Z" }, - { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload-time = "2025-10-06T14:51:08.091Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload-time = "2025-10-06T14:51:10.365Z" }, - { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload-time = "2025-10-06T14:51:12.466Z" }, - { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload-time = "2025-10-06T14:51:14.48Z" }, - { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload-time = "2025-10-06T14:51:16.072Z" }, - { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload-time = "2025-10-06T14:51:17.544Z" }, - { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload-time = "2025-10-06T14:51:18.875Z" }, - { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload-time = "2025-10-06T14:51:20.225Z" }, - { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload-time = "2025-10-06T14:51:21.588Z" }, - { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload-time = "2025-10-06T14:51:22.93Z" }, - { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload-time = "2025-10-06T14:51:24.352Z" }, - { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload-time = "2025-10-06T14:51:25.822Z" }, - { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload-time = "2025-10-06T14:51:27.604Z" }, - { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload-time = "2025-10-06T14:51:29.664Z" }, - { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload-time = "2025-10-06T14:51:31.684Z" }, - { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload-time = "2025-10-06T14:51:33.699Z" }, - { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload-time = "2025-10-06T14:51:36.189Z" }, - { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload-time = "2025-10-06T14:51:41.291Z" }, - { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload-time = "2025-10-06T14:51:43.55Z" }, - { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload-time = "2025-10-06T14:51:45.265Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload-time = "2025-10-06T14:51:46.836Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload-time = "2025-10-06T14:51:48.541Z" }, - { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload-time = "2025-10-06T14:51:50.355Z" }, - { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload-time = "2025-10-06T14:51:51.883Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload-time = "2025-10-06T14:51:53.672Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload-time = "2025-10-06T14:51:55.415Z" }, - { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, -] - [[package]] name = "nano-vectordb" version = "0.0.4.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cb/ff/ed9ff1c4e5b0418687c17d02fdc453c212e7550c62622914ba0243c106bc/nano_vectordb-0.0.4.3.tar.gz", hash = "sha256:3d13074476f2b739e51261974ed44aa467725579966219734c03502c929ed3b5", size = 6332, upload-time = "2024-11-11T12:50:50.584Z" } wheels = [ @@ -1701,7 +1146,10 @@ name = "networkx" version = "3.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.12'", + "python_full_version >= '3.14' and platform_machine != 's390x'", + "python_full_version >= '3.14' and platform_machine == 's390x'", + "python_full_version == '3.13.*'", + "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } @@ -1724,42 +1172,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/60/90/81ac364ef94209c100e12579629dc92bf7a709a84af32f8c551b02c07e94/nltk-3.9.2-py3-none-any.whl", hash = "sha256:1e209d2b3009110635ed9709a67a1a3e33a10f799490fa71cf4bec218c11c88a", size = 1513404, upload-time = "2025-10-01T07:19:21.648Z" }, ] -[[package]] -name = "numba" -version = "0.62.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llvmlite" }, - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/20/33dbdbfe60e5fd8e3dbfde299d106279a33d9f8308346022316781368591/numba-0.62.1.tar.gz", hash = "sha256:7b774242aa890e34c21200a1fc62e5b5757d5286267e71103257f4e2af0d5161", size = 2749817, upload-time = "2025-09-29T10:46:31.551Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/27/a5a9a58f267ec3b72f609789b2a8eefd6156bd7117e41cc9b7cf5de30490/numba-0.62.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:a323df9d36a0da1ca9c592a6baaddd0176d9f417ef49a65bb81951dce69d941a", size = 2684281, upload-time = "2025-09-29T10:43:31.863Z" }, - { url = "https://files.pythonhosted.org/packages/3a/9d/ffc091c0bfd7b80f66df3887a7061b6af80c8c2649902444026ee1454391/numba-0.62.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1e1f4781d3f9f7c23f16eb04e76ca10b5a3516e959634bd226fc48d5d8e7a0a", size = 2687311, upload-time = "2025-09-29T10:43:54.441Z" }, - { url = "https://files.pythonhosted.org/packages/a1/13/9a27bcd0baeea236116070c7df458414336f25e9dd5a872b066cf36b74bf/numba-0.62.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:14432af305ea68627a084cd702124fd5d0c1f5b8a413b05f4e14757202d1cf6c", size = 3734548, upload-time = "2025-09-29T10:42:38.232Z" }, - { url = "https://files.pythonhosted.org/packages/a7/00/17a1ac4a60253c784ce59549375e047da98330b82de7df6ac7f4ecc90902/numba-0.62.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f180922adf159ae36c2fe79fb94ffaa74cf5cb3688cb72dba0a904b91e978507", size = 3441277, upload-time = "2025-09-29T10:43:06.124Z" }, - { url = "https://files.pythonhosted.org/packages/86/94/20ae0ff78612c4697eaf942a639db01dd4e2d90f634ac41fa3e015c961fc/numba-0.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:f41834909d411b4b8d1c68f745144136f21416547009c1e860cc2098754b4ca7", size = 2745647, upload-time = "2025-09-29T10:44:15.282Z" }, - { url = "https://files.pythonhosted.org/packages/dd/5f/8b3491dd849474f55e33c16ef55678ace1455c490555337899c35826836c/numba-0.62.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:f43e24b057714e480fe44bc6031de499e7cf8150c63eb461192caa6cc8530bc8", size = 2684279, upload-time = "2025-09-29T10:43:37.213Z" }, - { url = "https://files.pythonhosted.org/packages/bf/18/71969149bfeb65a629e652b752b80167fe8a6a6f6e084f1f2060801f7f31/numba-0.62.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:57cbddc53b9ee02830b828a8428757f5c218831ccc96490a314ef569d8342b7b", size = 2687330, upload-time = "2025-09-29T10:43:59.601Z" }, - { url = "https://files.pythonhosted.org/packages/0e/7d/403be3fecae33088027bc8a95dc80a2fda1e3beff3e0e5fc4374ada3afbe/numba-0.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:604059730c637c7885386521bb1b0ddcbc91fd56131a6dcc54163d6f1804c872", size = 3739727, upload-time = "2025-09-29T10:42:45.922Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c3/3d910d08b659a6d4c62ab3cd8cd93c4d8b7709f55afa0d79a87413027ff6/numba-0.62.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6c540880170bee817011757dc9049dba5a29db0c09b4d2349295991fe3ee55f", size = 3445490, upload-time = "2025-09-29T10:43:12.692Z" }, - { url = "https://files.pythonhosted.org/packages/5b/82/9d425c2f20d9f0a37f7cb955945a553a00fa06a2b025856c3550227c5543/numba-0.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:03de6d691d6b6e2b76660ba0f38f37b81ece8b2cc524a62f2a0cfae2bfb6f9da", size = 2745550, upload-time = "2025-09-29T10:44:20.571Z" }, - { url = "https://files.pythonhosted.org/packages/5e/fa/30fa6873e9f821c0ae755915a3ca444e6ff8d6a7b6860b669a3d33377ac7/numba-0.62.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:1b743b32f8fa5fff22e19c2e906db2f0a340782caf024477b97801b918cf0494", size = 2685346, upload-time = "2025-09-29T10:43:43.677Z" }, - { url = "https://files.pythonhosted.org/packages/a9/d5/504ce8dc46e0dba2790c77e6b878ee65b60fe3e7d6d0006483ef6fde5a97/numba-0.62.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:90fa21b0142bcf08ad8e32a97d25d0b84b1e921bc9423f8dda07d3652860eef6", size = 2688139, upload-time = "2025-09-29T10:44:04.894Z" }, - { url = "https://files.pythonhosted.org/packages/50/5f/6a802741176c93f2ebe97ad90751894c7b0c922b52ba99a4395e79492205/numba-0.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6ef84d0ac19f1bf80431347b6f4ce3c39b7ec13f48f233a48c01e2ec06ecbc59", size = 3796453, upload-time = "2025-09-29T10:42:52.771Z" }, - { url = "https://files.pythonhosted.org/packages/7e/df/efd21527d25150c4544eccc9d0b7260a5dec4b7e98b5a581990e05a133c0/numba-0.62.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9315cc5e441300e0ca07c828a627d92a6802bcbf27c5487f31ae73783c58da53", size = 3496451, upload-time = "2025-09-29T10:43:19.279Z" }, - { url = "https://files.pythonhosted.org/packages/80/44/79bfdab12a02796bf4f1841630355c82b5a69933b1d50eb15c7fa37dabe8/numba-0.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:44e3aa6228039992f058f5ebfcfd372c83798e9464297bdad8cc79febcf7891e", size = 2745552, upload-time = "2025-09-29T10:44:26.399Z" }, - { url = "https://files.pythonhosted.org/packages/22/76/501ea2c07c089ef1386868f33dff2978f43f51b854e34397b20fc55e0a58/numba-0.62.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:b72489ba8411cc9fdcaa2458d8f7677751e94f0109eeb53e5becfdc818c64afb", size = 2685766, upload-time = "2025-09-29T10:43:49.161Z" }, - { url = "https://files.pythonhosted.org/packages/80/68/444986ed95350c0611d5c7b46828411c222ce41a0c76707c36425d27ce29/numba-0.62.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:44a1412095534a26fb5da2717bc755b57da5f3053965128fe3dc286652cc6a92", size = 2688741, upload-time = "2025-09-29T10:44:10.07Z" }, - { url = "https://files.pythonhosted.org/packages/78/7e/bf2e3634993d57f95305c7cee4c9c6cb3c9c78404ee7b49569a0dfecfe33/numba-0.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8c9460b9e936c5bd2f0570e20a0a5909ee6e8b694fd958b210e3bde3a6dba2d7", size = 3804576, upload-time = "2025-09-29T10:42:59.53Z" }, - { url = "https://files.pythonhosted.org/packages/e8/b6/8a1723fff71f63bbb1354bdc60a1513a068acc0f5322f58da6f022d20247/numba-0.62.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:728f91a874192df22d74e3fd42c12900b7ce7190b1aad3574c6c61b08313e4c5", size = 3503367, upload-time = "2025-09-29T10:43:26.326Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ec/9d414e7a80d6d1dc4af0e07c6bfe293ce0b04ea4d0ed6c45dad9bd6e72eb/numba-0.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:bbf3f88b461514287df66bc8d0307e949b09f2b6f67da92265094e8fa1282dd8", size = 2745529, upload-time = "2025-09-29T10:44:31.738Z" }, -] - [[package]] name = "numpy" version = "1.26.4" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468, upload-time = "2024-02-05T23:48:01.194Z" }, @@ -1789,134 +1210,319 @@ wheels = [ ] [[package]] -name = "nvidia-cublas-cu12" -version = "12.8.4.1" +name = "numpy" +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and platform_machine != 's390x'", + "python_full_version >= '3.14' and platform_machine == 's390x'", + "python_full_version == '3.13.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799, upload-time = "2026-03-29T13:18:15.438Z" }, + { url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552, upload-time = "2026-03-29T13:18:18.606Z" }, + { url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566, upload-time = "2026-03-29T13:18:21.532Z" }, + { url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482, upload-time = "2026-03-29T13:18:23.634Z" }, + { url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376, upload-time = "2026-03-29T13:18:26.677Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137, upload-time = "2026-03-29T13:18:30.14Z" }, + { url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414, upload-time = "2026-03-29T13:18:33.733Z" }, + { url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397, upload-time = "2026-03-29T13:18:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499, upload-time = "2026-03-29T13:18:40.372Z" }, + { url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257, upload-time = "2026-03-29T13:18:42.95Z" }, + { url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775, upload-time = "2026-03-29T13:18:45.835Z" }, + { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, + { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, + { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, + { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, + { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, + { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, + { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, + { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, + { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, + { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, + { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, + { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, + { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, + { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, + { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491, upload-time = "2026-03-29T13:21:38.03Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830, upload-time = "2026-03-29T13:21:41.509Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927, upload-time = "2026-03-29T13:21:44.747Z" }, + { url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557, upload-time = "2026-03-29T13:21:47.406Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253, upload-time = "2026-03-29T13:21:50.753Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552, upload-time = "2026-03-29T13:21:54.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" }, +] + +[[package]] +name = "nvidia-cublas" +version = "13.1.0.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226, upload-time = "2025-10-09T08:59:04.818Z" }, + { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236, upload-time = "2025-10-09T08:59:32.536Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime" +version = "13.0.96" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu13" +version = "9.19.0.56" source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, +] wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, + { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201, upload-time = "2026-02-03T20:40:53.805Z" }, + { url = "https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf", size = 366066321, upload-time = "2026-02-03T20:44:52.837Z" }, ] [[package]] -name = "nvidia-cuda-cupti-cu12" -version = "12.8.90" +name = "nvidia-cufft" +version = "12.0.0.61" source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, ] [[package]] -name = "nvidia-cuda-nvrtc-cu12" -version = "12.8.93" +name = "nvidia-cufile" +version = "1.15.1.6" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, ] [[package]] -name = "nvidia-cuda-runtime-cu12" -version = "12.8.90" +name = "nvidia-curand" +version = "10.4.0.35" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, ] [[package]] -name = "nvidia-cudnn-cu12" -version = "9.10.2.21" +name = "nvidia-cusolver" +version = "12.0.4.66" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12" }, + { name = "nvidia-cublas" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, ] [[package]] -name = "nvidia-cufft-cu12" -version = "11.3.3.83" +name = "nvidia-cusparse" +version = "12.6.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, ] [[package]] -name = "nvidia-cufile-cu12" -version = "1.13.1.3" +name = "nvidia-cusparselt-cu13" +version = "0.8.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, + { url = "https://files.pythonhosted.org/packages/46/10/8dcd1175260706a2fc92a16a52e306b71d4c1ea0b0cc4a9484183399818a/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c", size = 220791277, upload-time = "2025-08-13T19:22:40.982Z" }, + { url = "https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd", size = 169884119, upload-time = "2025-08-13T19:23:41.967Z" }, ] [[package]] -name = "nvidia-curand-cu12" -version = "10.3.9.90" +name = "nvidia-nccl-cu13" +version = "2.28.9" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, + { url = "https://files.pythonhosted.org/packages/39/55/1920646a2e43ffd4fc958536b276197ed740e9e0c54105b4bb3521591fc7/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643", size = 196561677, upload-time = "2025-11-18T05:49:03.45Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42", size = 196493177, upload-time = "2025-11-18T05:49:17.677Z" }, ] [[package]] -name = "nvidia-cusolver-cu12" -version = "11.7.3.90" +name = "nvidia-nvjitlink" +version = "13.0.88" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-cublas-cu12" }, - { name = "nvidia-cusparse-cu12" }, - { name = "nvidia-nvjitlink-cu12" }, -] wheels = [ - { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, + { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933, upload-time = "2025-09-04T08:35:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748, upload-time = "2025-09-04T08:35:20.008Z" }, ] [[package]] -name = "nvidia-cusparse-cu12" -version = "12.5.8.93" +name = "nvidia-nvshmem-cu13" +version = "3.4.5" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, -] wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, ] [[package]] -name = "nvidia-cusparselt-cu12" -version = "0.7.1" +name = "nvidia-nvtx" +version = "13.0.85" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, ] [[package]] -name = "nvidia-nccl-cu12" -version = "2.27.3" +name = "onnxruntime" +version = "1.19.2" source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/5b/4e4fff7bad39adf89f735f2bc87248c81db71205b62bcc0d5ca5b606b3c3/nvidia_nccl_cu12-2.27.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adf27ccf4238253e0b826bce3ff5fa532d65fc42322c8bfdfaf28024c0fbe039", size = 322364134, upload-time = "2025-06-03T21:58:04.013Z" }, +resolution-markers = [ + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", ] - -[[package]] -name = "nvidia-nvjitlink-cu12" -version = "12.8.93" +dependencies = [ + { name = "coloredlogs", marker = "python_full_version < '3.13'" }, + { name = "flatbuffers", marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "packaging", marker = "python_full_version < '3.13'" }, + { name = "protobuf", marker = "python_full_version < '3.13'" }, + { name = "sympy", marker = "python_full_version < '3.13'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/18/272d3d7406909141d3c9943796e3e97cafa53f4342d9231c0cfd8cb05702/onnxruntime-1.19.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:84fa57369c06cadd3c2a538ae2a26d76d583e7c34bdecd5769d71ca5c0fc750e", size = 16776408, upload-time = "2024-09-04T06:37:02.431Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d3/eb93f4ae511cfc725d0c69e07008800f8ac018de19ea1e497b306f174ccc/onnxruntime-1.19.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdc471a66df0c1cdef774accef69e9f2ca168c851ab5e4f2f3341512c7ef4666", size = 11491779, upload-time = "2024-09-04T06:37:06.203Z" }, + { url = "https://files.pythonhosted.org/packages/ca/4b/ce5958074abe4b6e8d1da9c10e443e01a681558a9ec17e5cc7619438e094/onnxruntime-1.19.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e3a4ce906105d99ebbe817f536d50a91ed8a4d1592553f49b3c23c4be2560ae6", size = 13170428, upload-time = "2024-09-04T06:37:09.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0f/6df82dfe02467d12adbaa05c2bd17519c29c7df531ed600231f0c741ad22/onnxruntime-1.19.2-cp310-cp310-win32.whl", hash = "sha256:4b3d723cc154c8ddeb9f6d0a8c0d6243774c6b5930847cc83170bfe4678fafb3", size = 9591305, upload-time = "2024-09-04T06:37:11.482Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d8/68b63dc86b502169d017a86fe8bc718f4b0055ef1f6895bfaddd04f2eead/onnxruntime-1.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:17ed7382d2c58d4b7354fb2b301ff30b9bf308a1c7eac9546449cd122d21cae5", size = 11084902, upload-time = "2024-09-04T06:37:14.647Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ff/77bee5df55f034ee81d2e1bc58b2b8511b9c54f06ce6566cb562c5d95aa5/onnxruntime-1.19.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:d863e8acdc7232d705d49e41087e10b274c42f09e259016a46f32c34e06dc4fd", size = 16779187, upload-time = "2024-09-04T06:37:18.245Z" }, + { url = "https://files.pythonhosted.org/packages/f3/78/e29f5fb76e0f6524f3520e8e5b9d53282784b45d14068c5112db9f712b0a/onnxruntime-1.19.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dfe4f660a71b31caa81fc298a25f9612815215a47b286236e61d540350d7b6", size = 11496005, upload-time = "2024-09-04T06:37:20.998Z" }, + { url = "https://files.pythonhosted.org/packages/60/ce/be4152da5c1030ab5a159a4a792ed9abad6ba498d79ef0aeba593ff7b5bf/onnxruntime-1.19.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a36511dc07c5c964b916697e42e366fa43c48cdb3d3503578d78cef30417cb84", size = 13167809, upload-time = "2024-09-04T06:37:24.221Z" }, + { url = "https://files.pythonhosted.org/packages/e1/00/9740a074eb0e0a21ff13a2c4f32aecc5b21110b2c9b9177d8ac132b66e2d/onnxruntime-1.19.2-cp311-cp311-win32.whl", hash = "sha256:50cbb8dc69d6befad4746a69760e5b00cc3ff0a59c6c3fb27f8afa20e2cab7e7", size = 9591445, upload-time = "2024-09-04T06:37:26.766Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f5/9d995a685f97508b3254f17015b4a78641b0625e79480a7aed7a7a105d7c/onnxruntime-1.19.2-cp311-cp311-win_amd64.whl", hash = "sha256:1c3e5d415b78337fa0b1b75291e9ea9fb2a4c1f148eb5811e7212fed02cfffa8", size = 11085695, upload-time = "2024-09-04T06:37:29.473Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a5/2a02687a88fc8a2507bef65876c90e96b9f8de5ba1f810acbf67c140fc67/onnxruntime-1.19.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:68e7051bef9cfefcbb858d2d2646536829894d72a4130c24019219442b1dd2ed", size = 16790434, upload-time = "2024-09-04T06:37:32.77Z" }, + { url = "https://files.pythonhosted.org/packages/47/64/da42254ec14452cad2cdd4cf407094841c0a378c0d08944e9a36172197e9/onnxruntime-1.19.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d2d366fbcc205ce68a8a3bde2185fd15c604d9645888703785b61ef174265168", size = 11486028, upload-time = "2024-09-04T06:37:35.364Z" }, + { url = "https://files.pythonhosted.org/packages/b2/92/3574f6836f33b1b25f272293e72538c38451b12c2d9aa08630bb6bc0f057/onnxruntime-1.19.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:477b93df4db467e9cbf34051662a4b27c18e131fa1836e05974eae0d6e4cf29b", size = 13175054, upload-time = "2024-09-04T06:37:38.192Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c9/8c37e413a830cac7f7dc094fffbd0c998c8bcb66a6f0b0a3201a49bc742b/onnxruntime-1.19.2-cp312-cp312-win32.whl", hash = "sha256:9a174073dc5608fad05f7cf7f320b52e8035e73d80b0a23c80f840e5a97c0147", size = 9592681, upload-time = "2024-09-04T06:37:41.328Z" }, + { url = "https://files.pythonhosted.org/packages/44/c0/59768846533786a82cafb38d8d2f900ad666bc91f0ae634774d286fa3c47/onnxruntime-1.19.2-cp312-cp312-win_amd64.whl", hash = "sha256:190103273ea4507638ffc31d66a980594b237874b65379e273125150eb044857", size = 11086411, upload-time = "2024-09-04T06:37:44.123Z" }, +] + +[[package]] +name = "onnxruntime" +version = "1.24.4" source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, +resolution-markers = [ + "python_full_version >= '3.14' and platform_machine != 's390x'", + "python_full_version >= '3.14' and platform_machine == 's390x'", + "python_full_version == '3.13.*'", ] - -[[package]] -name = "nvidia-nvtx-cu12" -version = "12.8.90" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, +dependencies = [ + { name = "flatbuffers", marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "packaging", marker = "python_full_version >= '3.13'" }, + { name = "protobuf", marker = "python_full_version >= '3.13'" }, + { name = "sympy", marker = "python_full_version >= '3.13'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/69/6c40720201012c6af9aa7d4ecdd620e521bd806dc6269d636fdd5c5aeebe/onnxruntime-1.24.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bdfce8e9a6497cec584aab407b71bf697dac5e1b7b7974adc50bf7533bdb3a2", size = 17332131, upload-time = "2026-03-17T22:05:49.005Z" }, + { url = "https://files.pythonhosted.org/packages/38/e9/8c901c150ce0c368da38638f44152fb411059c0c7364b497c9e5c957321a/onnxruntime-1.24.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:046ff290045a387676941a02a8ae5c3ebec6b4f551ae228711968c4a69d8f6b7", size = 15152472, upload-time = "2026-03-17T22:03:26.176Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b6/7a4df417cdd01e8f067a509e123ac8b31af450a719fa7ed81787dd6057ec/onnxruntime-1.24.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e54ad52e61d2d4618dcff8fa1480ac66b24ee2eab73331322db1049f11ccf330", size = 17222993, upload-time = "2026-03-17T22:04:34.485Z" }, + { url = "https://files.pythonhosted.org/packages/dd/59/8febe015f391aa1757fa5ba82c759ea4b6c14ef970132efb5e316665ba61/onnxruntime-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b43b63eb24a2bc8fc77a09be67587a570967a412cccb837b6245ccb546691153", size = 12594863, upload-time = "2026-03-17T22:05:38.749Z" }, + { url = "https://files.pythonhosted.org/packages/32/84/4155fcd362e8873eb6ce305acfeeadacd9e0e59415adac474bea3d9281bb/onnxruntime-1.24.4-cp311-cp311-win_arm64.whl", hash = "sha256:e26478356dba25631fb3f20112e345f8e8bf62c499bb497e8a559f7d69cf7e7b", size = 12259895, upload-time = "2026-03-17T22:05:28.812Z" }, + { url = "https://files.pythonhosted.org/packages/d7/38/31db1b232b4ba960065a90c1506ad7a56995cd8482033184e97fadca17cc/onnxruntime-1.24.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cad1c2b3f455c55678ab2a8caa51fb420c25e6e3cf10f4c23653cdabedc8de78", size = 17341875, upload-time = "2026-03-17T22:05:51.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/60/c4d1c8043eb42f8a9aa9e931c8c293d289c48ff463267130eca97d13357f/onnxruntime-1.24.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a5c5a544b22f90859c88617ecb30e161ee3349fcc73878854f43d77f00558b5", size = 15172485, upload-time = "2026-03-17T22:03:32.182Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ab/5b68110e0460d73fad814d5bd11c7b1ddcce5c37b10177eb264d6a36e331/onnxruntime-1.24.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d640eb9f3782689b55cfa715094474cd5662f2f137be6a6f847a594b6e9705c", size = 17244912, upload-time = "2026-03-17T22:04:37.251Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f4/6b89e297b93704345f0f3f8c62229bee323ef25682a3f9b4f89a39324950/onnxruntime-1.24.4-cp312-cp312-win_amd64.whl", hash = "sha256:535b29475ca42b593c45fbb2152fbf1cdf3f287315bf650e6a724a0a1d065cdb", size = 12596856, upload-time = "2026-03-17T22:05:41.224Z" }, + { url = "https://files.pythonhosted.org/packages/43/06/8b8ec6e9e6a474fcd5d772453f627ad4549dfe3ab8c0bf70af5afcde551b/onnxruntime-1.24.4-cp312-cp312-win_arm64.whl", hash = "sha256:e6214096e14b7b52e3bee1903dc12dc7ca09cb65e26664668a4620cc5e6f9a90", size = 12270275, upload-time = "2026-03-17T22:05:31.132Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f0/8a21ec0a97e40abb7d8da1e8b20fb9e1af509cc6d191f6faa75f73622fb2/onnxruntime-1.24.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e99a48078baaefa2b50fe5836c319499f71f13f76ed32d0211f39109147a49e0", size = 17341922, upload-time = "2026-03-17T22:03:56.364Z" }, + { url = "https://files.pythonhosted.org/packages/8b/25/d7908de8e08cee9abfa15b8aa82349b79733ae5865162a3609c11598805d/onnxruntime-1.24.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4aaed1e5e1aaacf2343c838a30a7c3ade78f13eeb16817411f929d04040a13", size = 15172290, upload-time = "2026-03-17T22:03:37.124Z" }, + { url = "https://files.pythonhosted.org/packages/7f/72/105ec27a78c5aa0154a7c0cd8c41c19a97799c3b12fc30392928997e3be3/onnxruntime-1.24.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e30c972bc02e072911aabb6891453ec73795386c0af2b761b65444b8a4c4745f", size = 17244738, upload-time = "2026-03-17T22:04:40.625Z" }, + { url = "https://files.pythonhosted.org/packages/05/fb/a592736d968c2f58e12de4d52088dda8e0e724b26ad5c0487263adb45875/onnxruntime-1.24.4-cp313-cp313-win_amd64.whl", hash = "sha256:3b6ba8b0181a3aa88edab00eb01424ffc06f42e71095a91186c2249415fcff93", size = 12597435, upload-time = "2026-03-17T22:05:43.826Z" }, + { url = "https://files.pythonhosted.org/packages/ad/04/ae2479e9841b64bd2eb44f8a64756c62593f896514369a11243b1b86ca5c/onnxruntime-1.24.4-cp313-cp313-win_arm64.whl", hash = "sha256:71d6a5c1821d6e8586a024000ece458db8f2fc0ecd050435d45794827ce81e19", size = 12269852, upload-time = "2026-03-17T22:05:33.353Z" }, + { url = "https://files.pythonhosted.org/packages/b4/af/a479a536c4398ffaf49fbbe755f45d5b8726bdb4335ab31b537f3d7149b8/onnxruntime-1.24.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1700f559c8086d06b2a4d5de51e62cb4ff5e2631822f71a36db8c72383db71ee", size = 15176861, upload-time = "2026-03-17T22:03:40.143Z" }, + { url = "https://files.pythonhosted.org/packages/be/13/19f5da70c346a76037da2c2851ecbf1266e61d7f0dcdb887c667210d4608/onnxruntime-1.24.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c74e268dc808e61e63784d43f9ddcdaf50a776c2819e8bd1d1b11ef64bf7e36", size = 17247454, upload-time = "2026-03-17T22:04:46.643Z" }, + { url = "https://files.pythonhosted.org/packages/89/db/b30dbbd6037847b205ab75d962bc349bf1e46d02a65b30d7047a6893ffd6/onnxruntime-1.24.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:fbff2a248940e3398ae78374c5a839e49a2f39079b488bc64439fa0ec327a3e4", size = 17343300, upload-time = "2026-03-17T22:03:59.223Z" }, + { url = "https://files.pythonhosted.org/packages/61/88/1746c0e7959961475b84c776d35601a21d445f463c93b1433a409ec3e188/onnxruntime-1.24.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2b7969e72d8cb53ffc88ab6d49dd5e75c1c663bda7be7eb0ece192f127343d1", size = 15175936, upload-time = "2026-03-17T22:03:43.671Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ba/4699cde04a52cece66cbebc85bd8335a0d3b9ad485abc9a2e15946a1349d/onnxruntime-1.24.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14ed1f197fab812b695a5eaddb536c635e58a2fbbe50a517c78f082cc6ce9177", size = 17246432, upload-time = "2026-03-17T22:04:49.58Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/4590910841bb28bd3b4b388a9efbedf4e2d2cca99ddf0c863642b4e87814/onnxruntime-1.24.4-cp314-cp314-win_amd64.whl", hash = "sha256:311e309f573bf3c12aa5723e23823077f83d5e412a18499d4485c7eb41040858", size = 12903276, upload-time = "2026-03-17T22:05:46.349Z" }, + { url = "https://files.pythonhosted.org/packages/7f/6f/60e2c0acea1e1ac09b3e794b5a19c166eebf91c0b860b3e6db8e74983fda/onnxruntime-1.24.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f0b910e86b759a4732663ec61fd57ac42ee1b0066f68299de164220b660546d", size = 12594365, upload-time = "2026-03-17T22:05:35.795Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/0c05d10f8f6c40fe0912ebec0d5a33884aaa2af2053507e864dab0883208/onnxruntime-1.24.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa12ddc54c9c4594073abcaa265cd9681e95fb89dae982a6f508a794ca42e661", size = 15176889, upload-time = "2026-03-17T22:03:48.021Z" }, + { url = "https://files.pythonhosted.org/packages/6c/1d/1666dc64e78d8587d168fec4e3b7922b92eb286a2ddeebcf6acb55c7dc82/onnxruntime-1.24.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1cc6a518255f012134bc791975a6294806be9a3b20c4a54cca25194c90cf731", size = 17247021, upload-time = "2026-03-17T22:04:52.377Z" }, ] [[package]] name = "openai" -version = "1.109.1" +version = "2.32.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1928,9 +1534,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/a1/a303104dc55fc546a3f6914c842d3da471c64eec92043aef8f652eb6c524/openai-1.109.1.tar.gz", hash = "sha256:d173ed8dbca665892a6db099b4a2dfac624f94d20a93f46eb0b56aae940ed869", size = 564133, upload-time = "2025-09-24T13:00:53.075Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/59/bdcc6b759b8c42dd73afaf5bf8f902c04b37987a5514dbc1c64dba390fef/openai-2.32.0.tar.gz", hash = "sha256:c54b27a9e4cb8d51f0dd94972ffd1a04437efeb259a9e60d8922b8bd26fe55e0", size = 693286, upload-time = "2026-04-15T22:28:19.434Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/2a/7dd3d207ec669cacc1f186fd856a0f61dbc255d24f6fdc1a6715d6051b0f/openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315", size = 948627, upload-time = "2025-09-24T13:00:50.754Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c1/d6e64ccd0536bf616556f0cad2b6d94a8125f508d25cfd814b1d2db4e2f1/openai-2.32.0-py3-none-any.whl", hash = "sha256:4dcc9badeb4bf54ad0d187453742f290226d30150890b7890711bda4f32f192f", size = 1162570, upload-time = "2026-04-15T22:28:17.714Z" }, ] [[package]] @@ -1947,7 +1553,8 @@ name = "pandas" version = "2.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "python-dateutil" }, { name = "pytz" }, { name = "tzdata" }, @@ -2004,117 +1611,170 @@ wheels = [ ] [[package]] -name = "patsy" -version = "1.0.1" +name = "pillow" +version = "10.4.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, +resolution-markers = [ + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/81/74f6a65b848ffd16c18f920620ce999fe45fe27f01ab3911260ce4ed85e4/patsy-1.0.1.tar.gz", hash = "sha256:e786a9391eec818c054e359b737bbce692f051aee4c661f4141cc88fb459c0c4", size = 396010, upload-time = "2024-11-12T14:10:54.642Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/2b/b50d3d08ea0fc419c183a84210571eba005328efa62b6b98bc28e9ead32a/patsy-1.0.1-py2.py3-none-any.whl", hash = "sha256:751fb38f9e97e62312e921a1954b81e1bb2bcda4f5eeabaf94db251ee791509c", size = 232923, upload-time = "2024-11-12T14:10:52.85Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059, upload-time = "2024-07-01T09:48:43.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/69/a31cccd538ca0b5272be2a38347f8839b97a14be104ea08b0db92f749c74/pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", size = 3509271, upload-time = "2024-07-01T09:45:22.07Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9e/4143b907be8ea0bce215f2ae4f7480027473f8b61fcedfda9d851082a5d2/pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", size = 3375658, upload-time = "2024-07-01T09:45:25.292Z" }, + { url = "https://files.pythonhosted.org/packages/8a/25/1fc45761955f9359b1169aa75e241551e74ac01a09f487adaaf4c3472d11/pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", size = 4332075, upload-time = "2024-07-01T09:45:27.94Z" }, + { url = "https://files.pythonhosted.org/packages/5e/dd/425b95d0151e1d6c951f45051112394f130df3da67363b6bc75dc4c27aba/pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", size = 4444808, upload-time = "2024-07-01T09:45:30.305Z" }, + { url = "https://files.pythonhosted.org/packages/b1/84/9a15cc5726cbbfe7f9f90bfb11f5d028586595907cd093815ca6644932e3/pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", size = 4356290, upload-time = "2024-07-01T09:45:32.868Z" }, + { url = "https://files.pythonhosted.org/packages/b5/5b/6651c288b08df3b8c1e2f8c1152201e0b25d240e22ddade0f1e242fc9fa0/pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", size = 4525163, upload-time = "2024-07-01T09:45:35.279Z" }, + { url = "https://files.pythonhosted.org/packages/07/8b/34854bf11a83c248505c8cb0fcf8d3d0b459a2246c8809b967963b6b12ae/pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", size = 4463100, upload-time = "2024-07-01T09:45:37.74Z" }, + { url = "https://files.pythonhosted.org/packages/78/63/0632aee4e82476d9cbe5200c0cdf9ba41ee04ed77887432845264d81116d/pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", size = 4592880, upload-time = "2024-07-01T09:45:39.89Z" }, + { url = "https://files.pythonhosted.org/packages/df/56/b8663d7520671b4398b9d97e1ed9f583d4afcbefbda3c6188325e8c297bd/pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", size = 2235218, upload-time = "2024-07-01T09:45:42.771Z" }, + { url = "https://files.pythonhosted.org/packages/f4/72/0203e94a91ddb4a9d5238434ae6c1ca10e610e8487036132ea9bf806ca2a/pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", size = 2554487, upload-time = "2024-07-01T09:45:45.176Z" }, + { url = "https://files.pythonhosted.org/packages/bd/52/7e7e93d7a6e4290543f17dc6f7d3af4bd0b3dd9926e2e8a35ac2282bc5f4/pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1", size = 2243219, upload-time = "2024-07-01T09:45:47.274Z" }, + { url = "https://files.pythonhosted.org/packages/a7/62/c9449f9c3043c37f73e7487ec4ef0c03eb9c9afc91a92b977a67b3c0bbc5/pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", size = 3509265, upload-time = "2024-07-01T09:45:49.812Z" }, + { url = "https://files.pythonhosted.org/packages/f4/5f/491dafc7bbf5a3cc1845dc0430872e8096eb9e2b6f8161509d124594ec2d/pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", size = 3375655, upload-time = "2024-07-01T09:45:52.462Z" }, + { url = "https://files.pythonhosted.org/packages/73/d5/c4011a76f4207a3c151134cd22a1415741e42fa5ddecec7c0182887deb3d/pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", size = 4340304, upload-time = "2024-07-01T09:45:55.006Z" }, + { url = "https://files.pythonhosted.org/packages/ac/10/c67e20445a707f7a610699bba4fe050583b688d8cd2d202572b257f46600/pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", size = 4452804, upload-time = "2024-07-01T09:45:58.437Z" }, + { url = "https://files.pythonhosted.org/packages/a9/83/6523837906d1da2b269dee787e31df3b0acb12e3d08f024965a3e7f64665/pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", size = 4365126, upload-time = "2024-07-01T09:46:00.713Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e5/8c68ff608a4203085158cff5cc2a3c534ec384536d9438c405ed6370d080/pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", size = 4533541, upload-time = "2024-07-01T09:46:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7c/01b8dbdca5bc6785573f4cee96e2358b0918b7b2c7b60d8b6f3abf87a070/pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", size = 4471616, upload-time = "2024-07-01T09:46:05.356Z" }, + { url = "https://files.pythonhosted.org/packages/c8/57/2899b82394a35a0fbfd352e290945440e3b3785655a03365c0ca8279f351/pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", size = 4600802, upload-time = "2024-07-01T09:46:08.145Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d7/a44f193d4c26e58ee5d2d9db3d4854b2cfb5b5e08d360a5e03fe987c0086/pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", size = 2235213, upload-time = "2024-07-01T09:46:10.211Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d0/5866318eec2b801cdb8c82abf190c8343d8a1cd8bf5a0c17444a6f268291/pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", size = 2554498, upload-time = "2024-07-01T09:46:12.685Z" }, + { url = "https://files.pythonhosted.org/packages/d4/c8/310ac16ac2b97e902d9eb438688de0d961660a87703ad1561fd3dfbd2aa0/pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", size = 2243219, upload-time = "2024-07-01T09:46:14.83Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/0353013dc30c02a8be34eb91d25e4e4cf594b59e5a55ea1128fde1e5f8ea/pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", size = 3509350, upload-time = "2024-07-01T09:46:17.177Z" }, + { url = "https://files.pythonhosted.org/packages/e7/cf/5c558a0f247e0bf9cec92bff9b46ae6474dd736f6d906315e60e4075f737/pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", size = 3374980, upload-time = "2024-07-01T09:46:19.169Z" }, + { url = "https://files.pythonhosted.org/packages/84/48/6e394b86369a4eb68b8a1382c78dc092245af517385c086c5094e3b34428/pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", size = 4343799, upload-time = "2024-07-01T09:46:21.883Z" }, + { url = "https://files.pythonhosted.org/packages/3b/f3/a8c6c11fa84b59b9df0cd5694492da8c039a24cd159f0f6918690105c3be/pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", size = 4459973, upload-time = "2024-07-01T09:46:24.321Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1b/c14b4197b80150fb64453585247e6fb2e1d93761fa0fa9cf63b102fde822/pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", size = 4370054, upload-time = "2024-07-01T09:46:26.825Z" }, + { url = "https://files.pythonhosted.org/packages/55/77/40daddf677897a923d5d33329acd52a2144d54a9644f2a5422c028c6bf2d/pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", size = 4539484, upload-time = "2024-07-01T09:46:29.355Z" }, + { url = "https://files.pythonhosted.org/packages/40/54/90de3e4256b1207300fb2b1d7168dd912a2fb4b2401e439ba23c2b2cabde/pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", size = 4477375, upload-time = "2024-07-01T09:46:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/13/24/1bfba52f44193860918ff7c93d03d95e3f8748ca1de3ceaf11157a14cf16/pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", size = 4608773, upload-time = "2024-07-01T09:46:33.73Z" }, + { url = "https://files.pythonhosted.org/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690, upload-time = "2024-07-01T09:46:36.587Z" }, + { url = "https://files.pythonhosted.org/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951, upload-time = "2024-07-01T09:46:38.777Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427, upload-time = "2024-07-01T09:46:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/c3/00/706cebe7c2c12a6318aabe5d354836f54adff7156fd9e1bd6c89f4ba0e98/pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", size = 3525685, upload-time = "2024-07-01T09:46:45.194Z" }, + { url = "https://files.pythonhosted.org/packages/cf/76/f658cbfa49405e5ecbfb9ba42d07074ad9792031267e782d409fd8fe7c69/pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", size = 3374883, upload-time = "2024-07-01T09:46:47.331Z" }, + { url = "https://files.pythonhosted.org/packages/46/2b/99c28c4379a85e65378211971c0b430d9c7234b1ec4d59b2668f6299e011/pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", size = 4339837, upload-time = "2024-07-01T09:46:49.647Z" }, + { url = "https://files.pythonhosted.org/packages/f1/74/b1ec314f624c0c43711fdf0d8076f82d9d802afd58f1d62c2a86878e8615/pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", size = 4455562, upload-time = "2024-07-01T09:46:51.811Z" }, + { url = "https://files.pythonhosted.org/packages/4a/2a/4b04157cb7b9c74372fa867096a1607e6fedad93a44deeff553ccd307868/pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", size = 4366761, upload-time = "2024-07-01T09:46:53.961Z" }, + { url = "https://files.pythonhosted.org/packages/ac/7b/8f1d815c1a6a268fe90481232c98dd0e5fa8c75e341a75f060037bd5ceae/pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", size = 4536767, upload-time = "2024-07-01T09:46:56.664Z" }, + { url = "https://files.pythonhosted.org/packages/e5/77/05fa64d1f45d12c22c314e7b97398ffb28ef2813a485465017b7978b3ce7/pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", size = 4477989, upload-time = "2024-07-01T09:46:58.977Z" }, + { url = "https://files.pythonhosted.org/packages/12/63/b0397cfc2caae05c3fb2f4ed1b4fc4fc878f0243510a7a6034ca59726494/pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", size = 4610255, upload-time = "2024-07-01T09:47:01.189Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f9/cfaa5082ca9bc4a6de66ffe1c12c2d90bf09c309a5f52b27759a596900e7/pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", size = 2235603, upload-time = "2024-07-01T09:47:03.918Z" }, + { url = "https://files.pythonhosted.org/packages/01/6a/30ff0eef6e0c0e71e55ded56a38d4859bf9d3634a94a88743897b5f96936/pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", size = 2554972, upload-time = "2024-07-01T09:47:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/48/2c/2e0a52890f269435eee38b21c8218e102c621fe8d8df8b9dd06fabf879ba/pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", size = 2243375, upload-time = "2024-07-01T09:47:09.065Z" }, + { url = "https://files.pythonhosted.org/packages/38/30/095d4f55f3a053392f75e2eae45eba3228452783bab3d9a920b951ac495c/pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", size = 3493889, upload-time = "2024-07-01T09:48:04.815Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e8/4ff79788803a5fcd5dc35efdc9386af153569853767bff74540725b45863/pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", size = 3346160, upload-time = "2024-07-01T09:48:07.206Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ac/4184edd511b14f760c73f5bb8a5d6fd85c591c8aff7c2229677a355c4179/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", size = 3435020, upload-time = "2024-07-01T09:48:09.66Z" }, + { url = "https://files.pythonhosted.org/packages/da/21/1749cd09160149c0a246a81d646e05f35041619ce76f6493d6a96e8d1103/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", size = 3490539, upload-time = "2024-07-01T09:48:12.529Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f5/f71fe1888b96083b3f6dfa0709101f61fc9e972c0c8d04e9d93ccef2a045/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", size = 3476125, upload-time = "2024-07-01T09:48:14.891Z" }, + { url = "https://files.pythonhosted.org/packages/96/b9/c0362c54290a31866c3526848583a2f45a535aa9d725fd31e25d318c805f/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", size = 3579373, upload-time = "2024-07-01T09:48:17.601Z" }, + { url = "https://files.pythonhosted.org/packages/52/3b/ce7a01026a7cf46e5452afa86f97a5e88ca97f562cafa76570178ab56d8d/pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", size = 2554661, upload-time = "2024-07-01T09:48:20.293Z" }, ] [[package]] name = "pillow" -version = "11.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554, upload-time = "2025-07-01T09:13:39.342Z" }, - { url = "https://files.pythonhosted.org/packages/7c/c8/67c12ab069ef586a25a4a79ced553586748fad100c77c0ce59bb4983ac98/pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad", size = 4686548, upload-time = "2025-07-01T09:13:41.835Z" }, - { url = "https://files.pythonhosted.org/packages/2f/bd/6741ebd56263390b382ae4c5de02979af7f8bd9807346d068700dd6d5cf9/pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0", size = 5859742, upload-time = "2025-07-03T13:09:47.439Z" }, - { url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b", size = 7633087, upload-time = "2025-07-03T13:09:51.796Z" }, - { url = "https://files.pythonhosted.org/packages/59/9d/9b7076aaf30f5dd17e5e5589b2d2f5a5d7e30ff67a171eb686e4eecc2adf/pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50", size = 5963350, upload-time = "2025-07-01T09:13:43.865Z" }, - { url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae", size = 6631840, upload-time = "2025-07-01T09:13:46.161Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e6/6ff7077077eb47fde78739e7d570bdcd7c10495666b6afcd23ab56b19a43/pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9", size = 6074005, upload-time = "2025-07-01T09:13:47.829Z" }, - { url = "https://files.pythonhosted.org/packages/c3/3a/b13f36832ea6d279a697231658199e0a03cd87ef12048016bdcc84131601/pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e", size = 6708372, upload-time = "2025-07-01T09:13:52.145Z" }, - { url = "https://files.pythonhosted.org/packages/6c/e4/61b2e1a7528740efbc70b3d581f33937e38e98ef3d50b05007267a55bcb2/pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6", size = 6277090, upload-time = "2025-07-01T09:13:53.915Z" }, - { url = "https://files.pythonhosted.org/packages/a9/d3/60c781c83a785d6afbd6a326ed4d759d141de43aa7365725cbcd65ce5e54/pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f", size = 6985988, upload-time = "2025-07-01T09:13:55.699Z" }, - { url = "https://files.pythonhosted.org/packages/9f/28/4f4a0203165eefb3763939c6789ba31013a2e90adffb456610f30f613850/pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f", size = 2422899, upload-time = "2025-07-01T09:13:57.497Z" }, - { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, - { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, - { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, - { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, - { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, - { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, - { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, - { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, - { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, - { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, - { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, - { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, - { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, - { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, - { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, - { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, - { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, - { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, - { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, - { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, - { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, - { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, - { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, - { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, - { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, - { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, - { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, - { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, - { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, - { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, - { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, - { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, - { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, - { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, - { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, - { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, - { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, - { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, - { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, - { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, - { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, - { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, - { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, - { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, - { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, - { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, - { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, - { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, - { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, - { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, - { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, - { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, - { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556, upload-time = "2025-07-01T09:16:09.961Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625, upload-time = "2025-07-01T09:16:11.913Z" }, - { url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207, upload-time = "2025-07-03T13:11:10.201Z" }, - { url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25", size = 6583939, upload-time = "2025-07-03T13:11:15.68Z" }, - { url = "https://files.pythonhosted.org/packages/3b/8e/5c9d410f9217b12320efc7c413e72693f48468979a013ad17fd690397b9a/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27", size = 4957166, upload-time = "2025-07-01T09:16:13.74Z" }, - { url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a", size = 5581482, upload-time = "2025-07-01T09:16:16.107Z" }, - { url = "https://files.pythonhosted.org/packages/d9/28/1000353d5e61498aaeaaf7f1e4b49ddb05f2c6575f9d4f9f914a3538b6e1/pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f", size = 6984596, upload-time = "2025-07-01T09:16:18.07Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, - { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, - { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, - { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, - { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, +version = "12.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and platform_machine != 's390x'", + "python_full_version >= '3.14' and platform_machine == 's390x'", + "python_full_version == '3.13.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/aa/d0b28e1c811cd4d5f5c2bfe2e022292bd255ae5744a3b9ac7d6c8f72dd75/pillow-12.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f", size = 5354355, upload-time = "2026-04-01T14:42:15.402Z" }, + { url = "https://files.pythonhosted.org/packages/27/8e/1d5b39b8ae2bd7650d0c7b6abb9602d16043ead9ebbfef4bc4047454da2a/pillow-12.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97", size = 4695871, upload-time = "2026-04-01T14:42:18.234Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c5/dcb7a6ca6b7d3be41a76958e90018d56c8462166b3ef223150360850c8da/pillow-12.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff", size = 6269734, upload-time = "2026-04-01T14:42:20.608Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f1/aa1bb13b2f4eba914e9637893c73f2af8e48d7d4023b9d3750d4c5eb2d0c/pillow-12.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec", size = 8076080, upload-time = "2026-04-01T14:42:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/a1/2a/8c79d6a53169937784604a8ae8d77e45888c41537f7f6f65ed1f407fe66d/pillow-12.2.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136", size = 6382236, upload-time = "2026-04-01T14:42:25.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/42/bbcb6051030e1e421d103ce7a8ecadf837aa2f39b8f82ef1a8d37c3d4ebc/pillow-12.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c", size = 7070220, upload-time = "2026-04-01T14:42:28.68Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e1/c2a7d6dd8cfa6b231227da096fd2d58754bab3603b9d73bf609d3c18b64f/pillow-12.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3", size = 6493124, upload-time = "2026-04-01T14:42:31.579Z" }, + { url = "https://files.pythonhosted.org/packages/5f/41/7c8617da5d32e1d2f026e509484fdb6f3ad7efaef1749a0c1928adbb099e/pillow-12.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa", size = 7194324, upload-time = "2026-04-01T14:42:34.615Z" }, + { url = "https://files.pythonhosted.org/packages/2d/de/a777627e19fd6d62f84070ee1521adde5eeda4855b5cf60fe0b149118bca/pillow-12.2.0-cp310-cp310-win32.whl", hash = "sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032", size = 6376363, upload-time = "2026-04-01T14:42:37.19Z" }, + { url = "https://files.pythonhosted.org/packages/e7/34/fc4cb5204896465842767b96d250c08410f01f2f28afc43b257de842eed5/pillow-12.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5", size = 7083523, upload-time = "2026-04-01T14:42:39.62Z" }, + { url = "https://files.pythonhosted.org/packages/2d/a0/32852d36bc7709f14dc3f64f929a275e958ad8c19a6deba9610d458e28b3/pillow-12.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024", size = 2463318, upload-time = "2026-04-01T14:42:42.063Z" }, + { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" }, + { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" }, + { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" }, + { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" }, + { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" }, + { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" }, + { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" }, + { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" }, + { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, + { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, + { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, + { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, + { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, + { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, + { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, + { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, + { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, + { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, + { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, + { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, + { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, + { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, + { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, + { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, + { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, + { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, + { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, + { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, + { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, + { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, + { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, + { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, + { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, + { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, + { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, + { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, + { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" }, + { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" }, + { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" }, + { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" }, + { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, ] [[package]] @@ -2127,183 +1787,110 @@ wheels = [ ] [[package]] -name = "pot" -version = "0.9.6.post1" +name = "portalocker" +version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "scipy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/8b/5f939eaf1fbeb7ff914fe540d659486951a056e5537b8f454362045b6c72/pot-0.9.6.post1.tar.gz", hash = "sha256:9b6cc14a8daecfe1268268168cf46548f9130976b22b24a9e8ec62a734be6c43", size = 604243, upload-time = "2025-09-22T12:51:14.894Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/65/3ed0362444818585d62521f9bf5e6166b8626a714354bc2c8ea5fbdbcbe6/pot-0.9.6.post1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2127b310a13f03951be450812e7dfdf62c5484bc6219bd0e0639f0347b3b60dd", size = 595401, upload-time = "2025-09-22T12:50:23.421Z" }, - { url = "https://files.pythonhosted.org/packages/07/9b/5145c4264953f03f054d4dc4ce1d8f337eb5827896f9e6a51267432ab86d/pot-0.9.6.post1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef7d50dbc851d8b69a6c5305fcad197f149047093e5f4555aed1ea77d1d7823b", size = 464517, upload-time = "2025-09-22T12:50:25.003Z" }, - { url = "https://files.pythonhosted.org/packages/83/23/9724a5a1ebfd4769377d5293208465ef8e803fbcf85350d5d38af349cbea/pot-0.9.6.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1de9cf2af8920c5902f1ee779cf2bf388d5677618735ce91f65d7f8e0ead629e", size = 450810, upload-time = "2025-09-22T12:50:26.28Z" }, - { url = "https://files.pythonhosted.org/packages/df/e9/f8f343588d2a18cd0c77fcf6b6f275642dea3cdf4f0e28e16c6e78198aec/pot-0.9.6.post1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b17c1373366f8ebd745d159793f415660ec45e69048305bb8597267d900145ab", size = 1459588, upload-time = "2025-09-22T12:50:27.739Z" }, - { url = "https://files.pythonhosted.org/packages/5b/7d/1529014aebb9d5fd54538115886d005d371a624b1ecaf5c2525b45ad0f77/pot-0.9.6.post1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48924f34d61b909e68651f3fe9fc1a892c69ae38d3c52bc832f95a28569c0e0e", size = 1478099, upload-time = "2025-09-22T12:50:29.201Z" }, - { url = "https://files.pythonhosted.org/packages/4e/87/84cfc49d4d0eb3e7b6cfc8352f0e73f62d456f6ce875da612b919a6bff6f/pot-0.9.6.post1-cp310-cp310-win32.whl", hash = "sha256:06e21b4dcebc2e8e318a96889243580ea64364830d05d53c4d038afedbe072cc", size = 443775, upload-time = "2025-09-22T12:50:30.84Z" }, - { url = "https://files.pythonhosted.org/packages/c4/21/9731ac0b125f755bb513a4ee081dca0ca5335e9059fb3332dd7c50d28415/pot-0.9.6.post1-cp310-cp310-win_amd64.whl", hash = "sha256:d35bb0169ef242fc2ce4f610572a5d11ac11d646698cbdf8cbb45d828f3c514b", size = 458481, upload-time = "2025-09-22T12:50:32.431Z" }, - { url = "https://files.pythonhosted.org/packages/f6/fc/3f4014bd6713c5b4c8a329b12c52842443b2284f52213a80e697b76b9f20/pot-0.9.6.post1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7fd8482a0262e5c875c05cf52e9c087e7c8bc473ef05d175887ad16e3c0443b7", size = 599499, upload-time = "2025-09-22T12:50:33.796Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4e/b22b789ee3a81c11c6f39ff08ed6a2e797a2a75a831fae996f4057db4771/pot-0.9.6.post1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c0bfac9daec0095061279a709f52be740e09363a62fe4c7edc843a4a0f6144c6", size = 466484, upload-time = "2025-09-22T12:50:34.973Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ae/2b35b96562bd72baf6de9583458878738f4508eef70d6fa9dd5867760d6a/pot-0.9.6.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:703853f7ba0ae2afed8203ea3478e87ef5f39d55cd75b1a39bb622867d1d5628", size = 453014, upload-time = "2025-09-22T12:50:36.157Z" }, - { url = "https://files.pythonhosted.org/packages/44/7e/f49d0593338a3b7f2c88c4cd6f1285c084e8ce05d52d42ac6f89f4f7ec0c/pot-0.9.6.post1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68268b4dd926976cf0604d466a57dff2ca44372e8ae9c879ba1f3d2a51e3be3d", size = 1494875, upload-time = "2025-09-22T12:50:37.903Z" }, - { url = "https://files.pythonhosted.org/packages/15/91/844c8437caaca6d6a71b38623df75c43642a116d399316adb1d0a9280c85/pot-0.9.6.post1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7568ddc957d3a16739bd24f9e07ce655166d27ebbc8786aad692cc5ba5d4c59", size = 1514551, upload-time = "2025-09-22T12:50:39.616Z" }, - { url = "https://files.pythonhosted.org/packages/ac/de/34a50565c37c0b71725a8075ff1ad2de62213d2e119276b546ef20356ac2/pot-0.9.6.post1-cp311-cp311-win32.whl", hash = "sha256:9649b736ea5dddad3a89d55a4a3bb0078610307ba64cac2efebe6bfcf8cfe785", size = 443490, upload-time = "2025-09-22T12:50:41.162Z" }, - { url = "https://files.pythonhosted.org/packages/a7/fa/453730c1b10094ab4d2ecd0b5fbfcdfe0305419cf01e32a2d31efd333559/pot-0.9.6.post1-cp311-cp311-win_amd64.whl", hash = "sha256:e161e49a22d5a925993baace4679f4e32fc2ade8f45ad73cf8417e13df5bd337", size = 458509, upload-time = "2025-09-22T12:50:43.597Z" }, - { url = "https://files.pythonhosted.org/packages/b9/28/13622807461f9f6082a8cd6768f9b4a810bc3a8fda474b81572da94b4d23/pot-0.9.6.post1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f7c542fc20662e35c24dd82eeff8a737220757434d7f0038664a7322221452f7", size = 599240, upload-time = "2025-09-22T12:50:44.848Z" }, - { url = "https://files.pythonhosted.org/packages/c6/5c/b4e017560531f53d06798c681b0d0a9488bb8116bc98da9d399a3d096391/pot-0.9.6.post1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c1755516a7354cbd6110ad2e5f341b98b9968240c2f0f67b0ff5e3ebcb3105bd", size = 464695, upload-time = "2025-09-22T12:50:46.341Z" }, - { url = "https://files.pythonhosted.org/packages/07/9f/57e49b3f7173359741053c5e2766a45dcf649d767c2e967ef93526c9045f/pot-0.9.6.post1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3207362d3e3b5aaa783f452aa85f66e83edbefb5764f34662860af54ac72ee6", size = 454726, upload-time = "2025-09-22T12:50:47.953Z" }, - { url = "https://files.pythonhosted.org/packages/30/60/fa72dd6094f7dbe6b38e2c6907af8cd0f18c6bd107e0cf4874deddaba883/pot-0.9.6.post1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05f6659c5657e6d7e9f98f4a82e0ed64f88e9fce69b2e557416d156343919ba3", size = 1503391, upload-time = "2025-09-22T12:50:49.336Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3f/cc519c1176116271b6282268a705162fa042c16cc922bc56039445c9d697/pot-0.9.6.post1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f1b0148ae17bec0ed12264c6da3a05e13913b716e2a8c9043242b5d8349d8df", size = 1528170, upload-time = "2025-09-22T12:50:50.625Z" }, - { url = "https://files.pythonhosted.org/packages/f5/01/0132c94404cd0b1b2f21c4a49698db9dcd6107c47c02b22df1ed38206b2a/pot-0.9.6.post1-cp312-cp312-win32.whl", hash = "sha256:571e543cc2b0a462365002203595baf2b89c3d064cce4fce70fd1231e832c21f", size = 440577, upload-time = "2025-09-22T12:50:51.716Z" }, - { url = "https://files.pythonhosted.org/packages/c1/6d/23229c0e198a4f7fb27750b3ef8497e6ebed23fe531ed64b5194da8b2b02/pot-0.9.6.post1-cp312-cp312-win_amd64.whl", hash = "sha256:b1d8bd9a334c72baa37f9a2b268de5366c23c0f9c9e3d6dc25d150137ec2823c", size = 455404, upload-time = "2025-09-22T12:50:52.956Z" }, - { url = "https://files.pythonhosted.org/packages/53/17/e4aebb8deef58b0d40ac339d952d12c63559801b50ae43c622d49bebda7e/pot-0.9.6.post1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:659fff750a162f58b52b33a64c4ac358f4ff44e9dff0841052c088e1b6a54430", size = 596485, upload-time = "2025-09-22T12:50:54.309Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b9/3646c153b13f999ac30112dcf85c5f233af79b0d98c37b52dda9a624c91b/pot-0.9.6.post1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4f54830e9f9cb78b1ff7abd5c5bf162625ed6aea903241267c64ea9f0fb73ddb", size = 463244, upload-time = "2025-09-22T12:50:56.004Z" }, - { url = "https://files.pythonhosted.org/packages/53/e9/c7092f7aec8cb32739ad66ba1f1259626546e4893b61b905ce2da3987235/pot-0.9.6.post1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e9fd4b1fafacd37debdb984687ddb26f5c43d1429401847d388a6f1bd1f10e98", size = 453215, upload-time = "2025-09-22T12:50:57.515Z" }, - { url = "https://files.pythonhosted.org/packages/0c/a1/f0187ab15aa1538ece07b28f3a7938b8592ef01fbe37b1a8f9c2f8f47f4d/pot-0.9.6.post1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec097ec0ef8bb93fee8cdb187b6a0a9653613cba7b06bb603247930e2c629cdc", size = 1496245, upload-time = "2025-09-22T12:50:58.848Z" }, - { url = "https://files.pythonhosted.org/packages/29/fa/85af71553b7e990fc37da8d5f2e7294ec66297e62cba419efeec11518e5a/pot-0.9.6.post1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:299f11f172908d799793ef18b2bc82452305350d2528d243e255a17876e98a57", size = 1521691, upload-time = "2025-09-22T12:51:00.203Z" }, - { url = "https://files.pythonhosted.org/packages/19/ae/96b2bce173b3d2d3d0faf8b7362fe79e60e1a6a939c9459b2f7b64e625d8/pot-0.9.6.post1-cp313-cp313-win32.whl", hash = "sha256:8a1d95310faae9c75355d9e2fac8dfac41316a2450061eefc982ee498a687a34", size = 439760, upload-time = "2025-09-22T12:51:01.601Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b1/8ca34418e7c4a2ec666e2204539577287223c4e78ab80b1c746cedb559c3/pot-0.9.6.post1-cp313-cp313-win_amd64.whl", hash = "sha256:a43e2b61389bd32f5b488da2488999ed55867e95fedb25dd64f9f390e40b4fab", size = 454228, upload-time = "2025-09-22T12:51:03.215Z" }, -] - -[[package]] -name = "propcache" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/c8/d70cd26d845c6d85479d8f5a11a0fd7151e9bc4794cc5e6eb5a790f12df8/propcache-0.4.0.tar.gz", hash = "sha256:c1ad731253eb738f9cadd9fa1844e019576c70bca6a534252e97cf33a57da529", size = 45187, upload-time = "2025-10-04T21:57:39.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/7b/4bd85fea3dc58b6f246abf0e6c9e44adca26f6817e6c136780315d723b82/propcache-0.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:779aaae64089e2f4992e993faea801925395d26bb5de4a47df7ef7f942c14f80", size = 79437, upload-time = "2025-10-04T21:54:49.766Z" }, - { url = "https://files.pythonhosted.org/packages/e0/91/379ecc1ab37fe33648c7cb2d2252f58969adac1edcd6ec74682d7fb2d920/propcache-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566552ed9b003030745e5bc7b402b83cf3cecae1bade95262d78543741786db5", size = 45369, upload-time = "2025-10-04T21:54:51.688Z" }, - { url = "https://files.pythonhosted.org/packages/de/8e/2e002e59e359bbc6ababbb7da168226f93e0533429ea1e93989a7eedcb2a/propcache-0.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:944de70384c62d16d4a00c686b422aa75efbc67c4addaebefbb56475d1c16034", size = 47191, upload-time = "2025-10-04T21:54:52.915Z" }, - { url = "https://files.pythonhosted.org/packages/b5/9a/f56eef9932dc3cbc63df4716f09fbaefec7a475608b643842784a01351b6/propcache-0.4.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e878553543ece1f8006d0ba4d096b40290580db173bfb18e16158045b9371335", size = 201000, upload-time = "2025-10-04T21:54:54.556Z" }, - { url = "https://files.pythonhosted.org/packages/6e/84/e7ad1e09c13f0574dbad261441f6a7f1fb8cc1e2fcb23ec4d4b3e4c7dc67/propcache-0.4.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8659f995b19185179474b18de8755689e1f71e1334d05c14e1895caa4e409cf7", size = 209175, upload-time = "2025-10-04T21:54:55.996Z" }, - { url = "https://files.pythonhosted.org/packages/4a/74/0b785ac0fbb44a5a7c267efc409b7a62d7a03b17c6442ecb52fd29152314/propcache-0.4.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aa8cc5c94e682dce91cb4d12d7b81c01641f4ef5b3b3dc53325d43f0e3b9f2e", size = 214874, upload-time = "2025-10-04T21:54:57.831Z" }, - { url = "https://files.pythonhosted.org/packages/b4/fd/e8d795def2b1d8dc1dc4731d36da1f6111d7c73212909e79462172d0434c/propcache-0.4.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da584d917a1a17f690fc726617fd2c3f3006ea959dae5bb07a5630f7b16f9f5f", size = 196686, upload-time = "2025-10-04T21:54:59.218Z" }, - { url = "https://files.pythonhosted.org/packages/79/c2/dc992c712c3a1bfaa11d13ff177dbdf9b8b272e7bd443601e37f35728338/propcache-0.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:892a072e5b19c3f324a4f8543c9f7e8fc2b0aa08579e46f69bdf0cfc1b440454", size = 192000, upload-time = "2025-10-04T21:55:00.645Z" }, - { url = "https://files.pythonhosted.org/packages/b3/de/bb108dbdfae594148b033ff283d9fa6e4b0906a99f2c03b98b526883149d/propcache-0.4.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c20d796210720455086ef3f85adc413d1e41d374742f9b439354f122bbc3b528", size = 190310, upload-time = "2025-10-04T21:55:02.107Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7b/1bdb5d44ba4c87d270bcf11354950f8f7fbc9ace1fbe7745e683fcb57b5a/propcache-0.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df7107a91126a495880576610ae989f19106e1900dd5218d08498391fa43b31d", size = 199646, upload-time = "2025-10-04T21:55:03.55Z" }, - { url = "https://files.pythonhosted.org/packages/e5/04/44beda877f779f49f5b8c0ff4817a62b5f90a2dfac1ec5311df15a9dfceb/propcache-0.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0b04ac2120c161416c866d0b6a4259e47e92231ff166b518cc0efb95777367c3", size = 200507, upload-time = "2025-10-04T21:55:04.914Z" }, - { url = "https://files.pythonhosted.org/packages/d4/62/a13ad0a63e06f3695fcaeaeeeb62e2cc685181a1248b23a2bc877c8b7111/propcache-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fa29c71ffa8d6a37324258737d09475f84715a6e8c350f67f0bc8e5e44993", size = 192787, upload-time = "2025-10-04T21:55:06.385Z" }, - { url = "https://files.pythonhosted.org/packages/9b/07/386246b3b4a6b11208bcbf57580210fb8c923ab26759389fe594e5615cd7/propcache-0.4.0-cp310-cp310-win32.whl", hash = "sha256:01c0ebc172ca28e9d62876832befbf7f36080eee6ed9c9e00243de2a8089ad57", size = 38004, upload-time = "2025-10-04T21:55:07.692Z" }, - { url = "https://files.pythonhosted.org/packages/a4/f2/e1fcb9694f590bc443ae5044f982546bb01cbaa3cdf05286e9473a9874bf/propcache-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:84f847e64f4d1a232e50460eebc1196642ee9b4c983612f41cd2d44fd2fe7c71", size = 41516, upload-time = "2025-10-04T21:55:08.854Z" }, - { url = "https://files.pythonhosted.org/packages/15/f4/d211744d41d72fbb89d3ee53963c1dc26892c49f53ae3c49fbc15cfb2548/propcache-0.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:2166466a666a5bebc332cd209cad77d996fad925ca7e8a2a6310ba9e851ae641", size = 38122, upload-time = "2025-10-04T21:55:10.044Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c4/72b8d41bdbae8aea9c25b869d7cdc3ab5f281f979d8aea30f4646ad12743/propcache-0.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a6a36b94c09711d6397d79006ca47901539fbc602c853d794c39abd6a326549", size = 80035, upload-time = "2025-10-04T21:55:11.266Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f8/f87115733e221408a363f3a9753419cf2d4be7a8a7ec9dc0788325cd23f1/propcache-0.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da47070e1340a1639aca6b1c18fe1f1f3d8d64d3a1f9ddc67b94475f44cd40f3", size = 45622, upload-time = "2025-10-04T21:55:12.41Z" }, - { url = "https://files.pythonhosted.org/packages/5d/cc/391f883248faa2efdf6886bdb12ac8edf20eac0863770d8d925450d8cc76/propcache-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de536cf796abc5b58d11c0ad56580215d231d9554ea4bb6b8b1b3bed80aa3234", size = 47517, upload-time = "2025-10-04T21:55:13.819Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/5593b59999f42d1044c5ab5f238be1f9d537ab91b0c910727986d520a6e9/propcache-0.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5c82af8e329c3cdc3e717dd3c7b2ff1a218b6de611f6ce76ee34967570a9de9", size = 214540, upload-time = "2025-10-04T21:55:15.206Z" }, - { url = "https://files.pythonhosted.org/packages/bb/5d/028cdc0eaa1a66ee2ec339a08b5e6ec15e7e71dac86103bebe53ba10dc0f/propcache-0.4.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:abe04e7aa5ab2e4056fcf3255ebee2071e4a427681f76d4729519e292c46ecc1", size = 221603, upload-time = "2025-10-04T21:55:16.704Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f8/e30aee5f59ea21647faef9c82bd67fa510295c34908a7a38571def555881/propcache-0.4.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:075ca32384294434344760fdcb95f7833e1d7cf7c4e55f0e726358140179da35", size = 227749, upload-time = "2025-10-04T21:55:18.082Z" }, - { url = "https://files.pythonhosted.org/packages/d7/85/0757dfc73931bea63b18d26b2c5e7bf13113ca60fe0e5f19905f104bcf6a/propcache-0.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:626ec13592928b677f48ff5861040b604b635e93d8e2162fb638397ea83d07e8", size = 209792, upload-time = "2025-10-04T21:55:19.475Z" }, - { url = "https://files.pythonhosted.org/packages/d2/45/35a6a6241f46948c0ac2418d5bf50cfbcd9735739f42028a1c11e9066a72/propcache-0.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:02e071548b6a376e173b0102c3f55dc16e7d055b5307d487e844c320e38cacf2", size = 207979, upload-time = "2025-10-04T21:55:21.164Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d1/5930396e75c9ed477958eac1496e6fb08794d823e9b14a459f1c0e20f338/propcache-0.4.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2af6de831a26f42a3f94592964becd8d7f238551786d7525807f02e53defbd13", size = 201923, upload-time = "2025-10-04T21:55:22.5Z" }, - { url = "https://files.pythonhosted.org/packages/98/72/675455f22bcefeda16907461f9a9a4a93709ff2095e8cf799bdb6c78e030/propcache-0.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c6dba1a3b8949e08c4280071c86e38cb602f02e0ed6659234108c7a7cd710", size = 212117, upload-time = "2025-10-04T21:55:23.858Z" }, - { url = "https://files.pythonhosted.org/packages/13/27/c533302ff80a49a848c3dbd01bb18f87b06826602b3b37043ff00d6b5005/propcache-0.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:783e91595cf9b66c2deda17f2e8748ae8591aa9f7c65dcab038872bfe83c5bb1", size = 216594, upload-time = "2025-10-04T21:55:25.169Z" }, - { url = "https://files.pythonhosted.org/packages/63/91/8250fbb601fd16c427e5f469132f27e175c6692dbfa784ef1266dc652e55/propcache-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c3f4b125285d354a627eb37f3ea7c13b8842c7c0d47783581d0df0e272dbf5f0", size = 204863, upload-time = "2025-10-04T21:55:26.511Z" }, - { url = "https://files.pythonhosted.org/packages/34/c4/fd945a9a25845aafb6094b9fa6a88286e4e1c55686e60172c60fe669e0d1/propcache-0.4.0-cp311-cp311-win32.whl", hash = "sha256:71c45f02ffbb8a21040ae816ceff7f6cd749ffac29fc0f9daa42dc1a9652d577", size = 37948, upload-time = "2025-10-04T21:55:27.719Z" }, - { url = "https://files.pythonhosted.org/packages/42/02/f30e7304661ffe8d51ff4050e06765ac2df6d95cf23c999dfe5a0cd0eb4c/propcache-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:7d51f70f77950f8efafed4383865d3533eeee52d8a0dd1c35b65f24de41de4e0", size = 41511, upload-time = "2025-10-04T21:55:29.15Z" }, - { url = "https://files.pythonhosted.org/packages/a5/f2/edd329d86085438a1ba32cf4cf45fc982d18343bed1f16b218b516c3340d/propcache-0.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:858eaabd2191dd0da5272993ad08a748b5d3ae1aefabea8aee619b45c2af4a64", size = 37957, upload-time = "2025-10-04T21:55:30.31Z" }, - { url = "https://files.pythonhosted.org/packages/b3/cf/3f88344261d69f8021256f20e82e820c5df3aba96e5ba9b5fdd3685d3a9f/propcache-0.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:381c84a445efb8c9168f1393a5a7c566de22edc42bfe207a142fff919b37f5d9", size = 79846, upload-time = "2025-10-04T21:55:31.447Z" }, - { url = "https://files.pythonhosted.org/packages/be/fa/0286fc92764eead9dcfee639b67828daa32e61dd0f1618831547141eb28b/propcache-0.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5a531d29d7b873b12730972237c48b1a4e5980b98cf21b3f09fa4710abd3a8c3", size = 45850, upload-time = "2025-10-04T21:55:32.637Z" }, - { url = "https://files.pythonhosted.org/packages/c7/83/57840656f972f8a67992eee40781e4066657776dcb889f49df0e8eecb112/propcache-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cd6e22255ed73efeaaeb1765505a66a48a9ec9ebc919fce5ad490fe5e33b1555", size = 47171, upload-time = "2025-10-04T21:55:33.819Z" }, - { url = "https://files.pythonhosted.org/packages/9f/8e/e0a0bd376c3440476b924eca517589ee535bb4520420d178268bf88558ba/propcache-0.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9a8d277dc218ddf04ec243a53ac309b1afcebe297c0526a8f82320139b56289", size = 225306, upload-time = "2025-10-04T21:55:35.312Z" }, - { url = "https://files.pythonhosted.org/packages/84/fe/76884442da1bab6d4353ba1c43fdc4a770c3b3973f3ac7620a7205402fdd/propcache-0.4.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:399c73201d88c856a994916200d7cba41d7687096f8eb5139eb68f02785dc3f7", size = 230013, upload-time = "2025-10-04T21:55:37.005Z" }, - { url = "https://files.pythonhosted.org/packages/f4/b7/322af273bd1136bb7e13628821fb855c9f61d64651c73fea71dded68dda5/propcache-0.4.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a1d5e474d43c238035b74ecf997f655afa67f979bae591ac838bb3fbe3076392", size = 238331, upload-time = "2025-10-04T21:55:38.713Z" }, - { url = "https://files.pythonhosted.org/packages/84/5e/036d2b105927ae7f179346c9911d16c345f4dba5a19a063f23a8d28acfbd/propcache-0.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f589652ee38de96aa58dd219335604e09666092bc250c1d9c26a55bcef9932", size = 221461, upload-time = "2025-10-04T21:55:40.034Z" }, - { url = "https://files.pythonhosted.org/packages/63/0d/babd038efb12a87a46ab070438c52daeac6bed0a930693a418feef8cb8a6/propcache-0.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5227da556b2939da6125cda1d5eecf9e412e58bc97b41e2f192605c3ccbb7c2", size = 216707, upload-time = "2025-10-04T21:55:41.455Z" }, - { url = "https://files.pythonhosted.org/packages/ab/68/dd075a037381581f16e7e504a6da9c1d7e415e945dd8ed67905d608f0687/propcache-0.4.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:92bc43a1ab852310721ce856f40a3a352254aa6f5e26f0fad870b31be45bba2e", size = 212591, upload-time = "2025-10-04T21:55:42.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/43/22698f28fc8e04c32b109cb9cb81305a4873b77c907b17484566b6133aef/propcache-0.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:83ae2f5343f6f06f4c91ae530d95f56b415f768f9c401a5ee2a10459cf74370b", size = 220188, upload-time = "2025-10-04T21:55:44.53Z" }, - { url = "https://files.pythonhosted.org/packages/96/7a/27886e4a4c69598a38fbeeed64f9b8ddfa6f08fe3452035845a1fe90336f/propcache-0.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:077a32977399dc05299b16e793210341a0b511eb0a86d1796873e83ce47334cc", size = 226736, upload-time = "2025-10-04T21:55:46.348Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c7/313c632b5888db3c9f4cb262420dcd5e57cf858d939d6ad9c3b1b90c12af/propcache-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94a278c45e6463031b5a8278e40a07edf2bcc3b5379510e22b6c1a6e6498c194", size = 216363, upload-time = "2025-10-04T21:55:47.768Z" }, - { url = "https://files.pythonhosted.org/packages/7a/5d/5aaf82bd1542aedb47d10483b84f49ee8f00d970a58e27534cd241e9c5ac/propcache-0.4.0-cp312-cp312-win32.whl", hash = "sha256:4c491462e1dc80f9deb93f428aad8d83bb286de212837f58eb48e75606e7726c", size = 37945, upload-time = "2025-10-04T21:55:49.104Z" }, - { url = "https://files.pythonhosted.org/packages/4c/67/47ffff6eb176f383f56319f31c0e1bcf7500cb94ffb7582efc600c6b3c73/propcache-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cdb0cecafb528ab15ed89cdfed183074d15912d046d3e304955513b50a34b907", size = 41530, upload-time = "2025-10-04T21:55:50.261Z" }, - { url = "https://files.pythonhosted.org/packages/f3/7e/61b70306b9d7527286ce887a8ff28c304ab2514e5893eea36b5bdf7a21af/propcache-0.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:b2f29697d1110e8cdf7a39cc630498df0082d7898b79b731c1c863f77c6e8cfc", size = 37662, upload-time = "2025-10-04T21:55:51.35Z" }, - { url = "https://files.pythonhosted.org/packages/cd/dd/f405b0fe84d29d356895bc048404d3321a2df849281cf3f932158c9346ac/propcache-0.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2d01fd53e89cb3d71d20b8c225a8c70d84660f2d223afc7ed7851a4086afe6d", size = 77565, upload-time = "2025-10-04T21:55:52.907Z" }, - { url = "https://files.pythonhosted.org/packages/c0/48/dfb2c45e1b0d92228c9c66fa929af7316c15cbe69a7e438786aaa60c1b3c/propcache-0.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7dfa60953169d2531dd8ae306e9c27c5d4e5efe7a2ba77049e8afdaece062937", size = 44602, upload-time = "2025-10-04T21:55:54.406Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/b15e88b4463df45a7793fb04e2b5497334f8fcc24e281c221150a0af9aff/propcache-0.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:227892597953611fce2601d49f1d1f39786a6aebc2f253c2de775407f725a3f6", size = 46168, upload-time = "2025-10-04T21:55:55.537Z" }, - { url = "https://files.pythonhosted.org/packages/40/ac/983e69cce8800251aab85858069cf9359b22222a9cda47591e03e2f24eec/propcache-0.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e0a5bc019014531308fb67d86066d235daa7551baf2e00e1ea7b00531f6ea85", size = 207997, upload-time = "2025-10-04T21:55:57.022Z" }, - { url = "https://files.pythonhosted.org/packages/ae/9c/5586a7a54e7e0b9a87fdd8ba935961f398c0e6eaecd57baaa8eca468a236/propcache-0.4.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6ebc6e2e65c31356310ddb6519420eaa6bb8c30fbd809d0919129c89dcd70f4c", size = 210948, upload-time = "2025-10-04T21:55:58.397Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ba/644e367f8a86461d45bd023ace521180938e76515040550af9b44085e99a/propcache-0.4.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1927b78dd75fc31a7fdc76cc7039e39f3170cb1d0d9a271e60f0566ecb25211a", size = 217988, upload-time = "2025-10-04T21:56:00.251Z" }, - { url = "https://files.pythonhosted.org/packages/24/0e/1e21af74b4732d002b0452605bdf31d6bf990fd8b720cb44e27a97d80db5/propcache-0.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b113feeda47f908562d9a6d0e05798ad2f83d4473c0777dafa2bc7756473218", size = 204442, upload-time = "2025-10-04T21:56:01.93Z" }, - { url = "https://files.pythonhosted.org/packages/fd/30/ae2eec96995a8a760acb9a0b6c92b9815f1fc885c7d8481237ccb554eab0/propcache-0.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4596c12aa7e3bb2abf158ea8f79eb0fb4851606695d04ab846b2bb386f5690a1", size = 199371, upload-time = "2025-10-04T21:56:03.25Z" }, - { url = "https://files.pythonhosted.org/packages/45/1d/a18fac8cb04f8379ccb79cf15aac31f4167a270d1cd1111f33c0d38ce4fb/propcache-0.4.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6d1f67dad8cc36e8abc2207a77f3f952ac80be7404177830a7af4635a34cbc16", size = 196638, upload-time = "2025-10-04T21:56:04.619Z" }, - { url = "https://files.pythonhosted.org/packages/48/45/3549a2b6f74dce6f21b2664d078bd26ceb876aae9c58f3c017cf590f0ee3/propcache-0.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6229ad15366cd8b6d6b4185c55dd48debf9ca546f91416ba2e5921ad6e210a6", size = 203651, upload-time = "2025-10-04T21:56:06.153Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f0/90ea14d518c919fc154332742a9302db3004af4f1d3df688676959733283/propcache-0.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2a4bf309d057327f1f227a22ac6baf34a66f9af75e08c613e47c4d775b06d6c7", size = 205726, upload-time = "2025-10-04T21:56:07.955Z" }, - { url = "https://files.pythonhosted.org/packages/f6/de/8efc1dbafeb42108e7af744822cdca944b990869e9da70e79efb21569d6b/propcache-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c2e274f3d1cbb2ddcc7a55ce3739af0f8510edc68a7f37981b2258fa1eedc833", size = 199576, upload-time = "2025-10-04T21:56:09.43Z" }, - { url = "https://files.pythonhosted.org/packages/d7/38/4d79fe3477b050398fb8d8f59301ed116d8c6ea3c4dbf09498c679103f90/propcache-0.4.0-cp313-cp313-win32.whl", hash = "sha256:f114a3e1f8034e2957d34043b7a317a8a05d97dfe8fddb36d9a2252c0117dbbc", size = 37474, upload-time = "2025-10-04T21:56:10.74Z" }, - { url = "https://files.pythonhosted.org/packages/36/9b/a283daf665a1945cff1b03d1104e7c9ee92bb7b6bbcc6518b24fcdac8bd0/propcache-0.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ba68c57cde9c667f6b65b98bc342dfa7240b1272ffb2c24b32172ee61b6d281", size = 40685, upload-time = "2025-10-04T21:56:11.896Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f7/def8fc0b4d7a89f1628f337cb122bb9a946c5ed97760f2442b27b7fa5a69/propcache-0.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb77a85253174bf73e52c968b689d64be62d71e8ac33cabef4ca77b03fb4ef92", size = 37046, upload-time = "2025-10-04T21:56:13.021Z" }, - { url = "https://files.pythonhosted.org/packages/ca/6b/f6e8b36b58d17dfb6c505b9ae1163fcf7a4cf98825032fdc77bba4ab5c4a/propcache-0.4.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c0e1c218fff95a66ad9f2f83ad41a67cf4d0a3f527efe820f57bde5fda616de4", size = 81274, upload-time = "2025-10-04T21:56:14.206Z" }, - { url = "https://files.pythonhosted.org/packages/8e/c5/1fd0baa222b8faf53ba04dd4f34de33ea820b80e34f87c7960666bae5f4f/propcache-0.4.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5710b1c01472542bb024366803812ca13e8774d21381bcfc1f7ae738eeb38acc", size = 46232, upload-time = "2025-10-04T21:56:15.337Z" }, - { url = "https://files.pythonhosted.org/packages/cb/6b/7aa5324983cab7666ed58fc32c68a0430468a18e02e3f04e7a879c002414/propcache-0.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d7f008799682e8826ce98f25e8bc43532d2cd26c187a1462499fa8d123ae054f", size = 48239, upload-time = "2025-10-04T21:56:16.768Z" }, - { url = "https://files.pythonhosted.org/packages/24/0f/58c192301c0436762ed5fed5a3edadb0ae399cb73528fb9c1b5cb8e53523/propcache-0.4.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0596d2ae99d74ca436553eb9ce11fe4163dc742fcf8724ebe07d7cb0db679bb1", size = 275804, upload-time = "2025-10-04T21:56:18.066Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b9/092ee32064ebfabedae4251952787e63e551075af1a1205e8061b3ed5838/propcache-0.4.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab9c1bd95ebd1689f0e24f2946c495808777e9e8df7bb3c1dfe3e9eb7f47fe0d", size = 273996, upload-time = "2025-10-04T21:56:19.801Z" }, - { url = "https://files.pythonhosted.org/packages/43/82/becf618ed28e732f3bba3df172cd290a1afbd99f291074f747fd5bd031bb/propcache-0.4.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a8ef2ea819549ae2e8698d2ec229ae948d7272feea1cb2878289f767b6c585a4", size = 280266, upload-time = "2025-10-04T21:56:21.136Z" }, - { url = "https://files.pythonhosted.org/packages/51/be/b370930249a9332a81b5c4c550dac614b7e11b6c160080777e903d57e197/propcache-0.4.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71a400b2f0b079438cc24f9a27f02eff24d8ef78f2943f949abc518b844ade3d", size = 263186, upload-time = "2025-10-04T21:56:22.787Z" }, - { url = "https://files.pythonhosted.org/packages/33/b6/546fd3e31770aed3aed1c01b120944c689edb510aeb7a25472edc472ce23/propcache-0.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c2735d3305e6cecab6e53546909edf407ad3da5b9eeaf483f4cf80142bb21be", size = 260721, upload-time = "2025-10-04T21:56:24.22Z" }, - { url = "https://files.pythonhosted.org/packages/80/70/3751930d16e5984490c73ca65b80777e4b26e7a0015f2d41f31d75959a71/propcache-0.4.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:72b51340047ac43b3cf388eebd362d052632260c9f73a50882edbb66e589fd44", size = 247516, upload-time = "2025-10-04T21:56:25.577Z" }, - { url = "https://files.pythonhosted.org/packages/59/90/4bc96ce6476f67e2e6b72469f328c92b53259a0e4d1d5386d71a36e9258c/propcache-0.4.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:184c779363740d6664982ad05699f378f7694220e2041996f12b7c2a4acdcad0", size = 262675, upload-time = "2025-10-04T21:56:27.065Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d1/f16d096869c5f1c93d67fc37488c0c814add0560574f6877653a10239cde/propcache-0.4.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a60634a9de41f363923c6adfb83105d39e49f7a3058511563ed3de6748661af6", size = 263379, upload-time = "2025-10-04T21:56:28.517Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2a/da5cd1bc1c6412939c457ea65bbe7e034045c395d98ff8ff880d06ec4553/propcache-0.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8119244d122241a9c4566bce49bb20408a6827044155856735cf14189a7da", size = 257694, upload-time = "2025-10-04T21:56:30.051Z" }, - { url = "https://files.pythonhosted.org/packages/a5/11/938e67c07189b662a6c72551d48285a02496de885408392447c25657dd47/propcache-0.4.0-cp313-cp313t-win32.whl", hash = "sha256:515b610a364c8cdd2b72c734cc97dece85c416892ea8d5c305624ac8734e81db", size = 41321, upload-time = "2025-10-04T21:56:31.406Z" }, - { url = "https://files.pythonhosted.org/packages/f4/6e/72b11a4dcae68c728b15126cc5bc830bf275c84836da2633412b768d07e0/propcache-0.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7ea86eb32e74f9902df57e8608e8ac66f1e1e1d24d1ed2ddeb849888413b924d", size = 44846, upload-time = "2025-10-04T21:56:32.5Z" }, - { url = "https://files.pythonhosted.org/packages/94/09/0ef3c025e0621e703ef71b69e0085181a3124bcc1beef29e0ffef59ed7f4/propcache-0.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c1443fa4bb306461a3a8a52b7de0932a2515b100ecb0ebc630cc3f87d451e0a9", size = 39689, upload-time = "2025-10-04T21:56:33.686Z" }, - { url = "https://files.pythonhosted.org/packages/60/89/7699d8e9f8c222bbef1fae26afd72d448353f164a52125d5f87dd9fec2c7/propcache-0.4.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de8e310d24b5a61de08812dd70d5234da1458d41b059038ee7895a9e4c8cae79", size = 77977, upload-time = "2025-10-04T21:56:34.836Z" }, - { url = "https://files.pythonhosted.org/packages/77/c5/2758a498199ce46d6d500ba4391a8594df35400cc85738aa9f0c9b8366db/propcache-0.4.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:55a54de5266bc44aa274915cdf388584fa052db8748a869e5500ab5993bac3f4", size = 44715, upload-time = "2025-10-04T21:56:36.075Z" }, - { url = "https://files.pythonhosted.org/packages/0d/da/5a44e10282a28c2dd576e5e1a2c7bb8145587070ddab7375fb643f7129d7/propcache-0.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:88d50d662c917ec2c9d3858920aa7b9d5bfb74ab9c51424b775ccbe683cb1b4e", size = 46463, upload-time = "2025-10-04T21:56:37.227Z" }, - { url = "https://files.pythonhosted.org/packages/d5/5a/b2c314f655f46c10c204dc0d69e19fadfb1cc4d40ab33f403698a35c3281/propcache-0.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae3adf88a66f5863cf79394bc359da523bb27a2ed6ba9898525a6a02b723bfc5", size = 206980, upload-time = "2025-10-04T21:56:38.828Z" }, - { url = "https://files.pythonhosted.org/packages/7c/4e/f6643ec2cd5527b92c93488f9b67a170494736bb1c5460136399d709ce5a/propcache-0.4.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7f088e21d15b3abdb9047e4b7b7a0acd79bf166893ac2b34a72ab1062feb219e", size = 211385, upload-time = "2025-10-04T21:56:40.2Z" }, - { url = "https://files.pythonhosted.org/packages/71/41/362766a346c3f8d3bbeb7899e1ff40f18844e0fe37e9f6f536553cf6b6be/propcache-0.4.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4efbaf10793fd574c76a5732c75452f19d93df6e0f758c67dd60552ebd8614b", size = 215315, upload-time = "2025-10-04T21:56:41.574Z" }, - { url = "https://files.pythonhosted.org/packages/ff/98/17385d51816d56fa6acc035d8625fbf833b6a795d7ef7fb37ea3f62db6c9/propcache-0.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:681a168d06284602d56e97f09978057aa88bcc4177352b875b3d781df4efd4cb", size = 201416, upload-time = "2025-10-04T21:56:42.947Z" }, - { url = "https://files.pythonhosted.org/packages/7a/83/801178ca1c29e217564ee507ff2a49d3f24a4dd85c9b9d681fd1d62b15f2/propcache-0.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a7f06f077fc4ef37e8a37ca6bbb491b29e29db9fb28e29cf3896aad10dbd4137", size = 197726, upload-time = "2025-10-04T21:56:44.313Z" }, - { url = "https://files.pythonhosted.org/packages/d2/38/c8743917bca92b7e5474366b6b04c7b3982deac32a0fe4b705f2e92c09bb/propcache-0.4.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:082a643479f49a6778dcd68a80262fc324b14fd8e9b1a5380331fe41adde1738", size = 192819, upload-time = "2025-10-04T21:56:45.702Z" }, - { url = "https://files.pythonhosted.org/packages/0b/74/3de3ef483e8615aaaf62026fcdcb20cbfc4535ea14871b12f72d52c1d6dc/propcache-0.4.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:26692850120241a99bb4a4eec675cd7b4fdc431144f0d15ef69f7f8599f6165f", size = 202492, upload-time = "2025-10-04T21:56:47.388Z" }, - { url = "https://files.pythonhosted.org/packages/46/86/a130dd85199d651a6986ba6bf1ce297b7bbcafc01c8e139e6ba2b8218a20/propcache-0.4.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:33ad7d37b9a386f97582f5d042cc7b8d4b3591bb384cf50866b749a17e4dba90", size = 204106, upload-time = "2025-10-04T21:56:49.139Z" }, - { url = "https://files.pythonhosted.org/packages/b2/f7/44eab58659d71d21995146c94139e63882bac280065b3a9ed10376897bcc/propcache-0.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fd82d4a5b7583588f103b0771e43948532f1292105f13ee6f3b300933c4ca", size = 198043, upload-time = "2025-10-04T21:56:50.561Z" }, - { url = "https://files.pythonhosted.org/packages/96/14/df37be1bf1423d2dda201a4cdb1c5cb44048d34e31a97df227cc25b0a55c/propcache-0.4.0-cp314-cp314-win32.whl", hash = "sha256:213eb0d3bc695a70cffffe11a1c2e1c2698d89ffd8dba35a49bc44a035d45c93", size = 38036, upload-time = "2025-10-04T21:56:51.868Z" }, - { url = "https://files.pythonhosted.org/packages/99/96/9cea65d6c50224737e80c57a3f3db4ca81bc7b1b52bc73346df8c50db400/propcache-0.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:087e2d3d7613e1b59b2ffca0daabd500c1a032d189c65625ee05ea114afcad0b", size = 41156, upload-time = "2025-10-04T21:56:53.242Z" }, - { url = "https://files.pythonhosted.org/packages/52/4d/91523dcbe23cc127b097623a6ba177da51fca6b7c979082aa49745b527b7/propcache-0.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:94b0f7407d18001dbdcbb239512e753b1b36725a6e08a4983be1c948f5435f79", size = 37976, upload-time = "2025-10-04T21:56:54.351Z" }, - { url = "https://files.pythonhosted.org/packages/ec/f7/7118a944cb6cdb548c9333cf311bda120f9793ecca54b2ca4a3f7e58723e/propcache-0.4.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b730048ae8b875e2c0af1a09ca31b303fc7b5ed27652beec03fa22b29545aec9", size = 81270, upload-time = "2025-10-04T21:56:55.516Z" }, - { url = "https://files.pythonhosted.org/packages/ab/f9/04a8bc9977ea201783f3ccb04106f44697f635f70439a208852d4d08554d/propcache-0.4.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f495007ada16a4e16312b502636fafff42a9003adf1d4fb7541e0a0870bc056f", size = 46224, upload-time = "2025-10-04T21:56:56.695Z" }, - { url = "https://files.pythonhosted.org/packages/0f/3d/808b074034156f130a0047304d811a5a5df3bb0976c9adfb9383718fd888/propcache-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:659a0ea6d9017558ed7af00fb4028186f64d0ba9adfc70a4d2c85fcd3d026321", size = 48246, upload-time = "2025-10-04T21:56:57.926Z" }, - { url = "https://files.pythonhosted.org/packages/66/eb/e311f3a59ddc93078cb079b12699af9fd844142c4b4d382b386ee071d921/propcache-0.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d74aa60b1ec076d4d5dcde27c9a535fc0ebb12613f599681c438ca3daa68acac", size = 275562, upload-time = "2025-10-04T21:56:59.221Z" }, - { url = "https://files.pythonhosted.org/packages/f4/05/a146094d6a00bb2f2036dd2a2f4c2b2733ff9574b59ce53bd8513edfca5d/propcache-0.4.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34000e31795bdcda9826e0e70e783847a42e3dcd0d6416c5d3cb717905ebaec0", size = 273627, upload-time = "2025-10-04T21:57:00.582Z" }, - { url = "https://files.pythonhosted.org/packages/91/95/a6d138f6e3d5f6c9b34dbd336b964a1293f2f1a79cafbe70ae3403d7cc46/propcache-0.4.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bcb5bfac5b9635e6fc520c8af6efc7a0a56f12a1fe9e9d3eb4328537e316dd6a", size = 279778, upload-time = "2025-10-04T21:57:01.944Z" }, - { url = "https://files.pythonhosted.org/packages/ac/09/19594a20da0519bfa00deef8cf35dda6c9a5b51bba947f366e85ea59b3de/propcache-0.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ea11fceb31fa95b0fa2007037f19e922e2caceb7dc6c6cac4cb56e2d291f1a2", size = 262833, upload-time = "2025-10-04T21:57:03.326Z" }, - { url = "https://files.pythonhosted.org/packages/b5/92/60d2ddc7662f7b2720d3b628ad8ce888015f4ab5c335b7b1b50183194e68/propcache-0.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cd8684f628fe285ea5c86f88e1c30716239dc9d6ac55e7851a4b7f555b628da3", size = 260456, upload-time = "2025-10-04T21:57:05.159Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e2/4c2e25c77cf43add2e05a86c4fcf51107edc4d92318e5c593bbdc2515d57/propcache-0.4.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:790286d3d542c0ef9f6d0280d1049378e5e776dcba780d169298f664c39394db", size = 247284, upload-time = "2025-10-04T21:57:06.566Z" }, - { url = "https://files.pythonhosted.org/packages/dc/3e/c273ab8edc80683ec8b15b486e95c03096ef875d99e4b0ab0a36c1e42c94/propcache-0.4.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:009093c9b5dbae114a5958e6a649f8a5d94dd6866b0f82b60395eb92c58002d4", size = 262368, upload-time = "2025-10-04T21:57:08.231Z" }, - { url = "https://files.pythonhosted.org/packages/ac/a9/3fa231f65a9f78614c5aafa9cee788d7f55c22187cc2f33e86c7c16d0262/propcache-0.4.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:728d98179e92d77096937fdfecd2c555a3d613abe56c9909165c24196a3b5012", size = 263010, upload-time = "2025-10-04T21:57:09.641Z" }, - { url = "https://files.pythonhosted.org/packages/38/a0/f4f5d368e60c9dc04d3158eaf1ca0ad899b40ac3d29c015bf62735225a6f/propcache-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a9725d96a81e17e48a0fe82d0c3de2f5e623d7163fec70a6c7df90753edd1bec", size = 257298, upload-time = "2025-10-04T21:57:11.125Z" }, - { url = "https://files.pythonhosted.org/packages/c7/30/f78d6758dc36a98f1cddc39b3185cefde616cc58248715b7c65495491cb1/propcache-0.4.0-cp314-cp314t-win32.whl", hash = "sha256:0964c55c95625193defeb4fd85f8f28a9a754ed012cab71127d10e3dc66b1373", size = 42484, upload-time = "2025-10-04T21:57:12.652Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ad/de0640e9b56d2caa796c4266d7d1e6cc4544cc327c25b7ced5c59893b625/propcache-0.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:24403152e41abf09488d3ae9c0c3bf7ff93e2fb12b435390718f21810353db28", size = 46229, upload-time = "2025-10-04T21:57:14.034Z" }, - { url = "https://files.pythonhosted.org/packages/da/bf/5aed62dddbf2bbe62a3564677436261909c9dd63a0fa1fb6cf0629daa13c/propcache-0.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0363a696a9f24b37a04ed5e34c2e07ccbe92798c998d37729551120a1bb744c4", size = 40329, upload-time = "2025-10-04T21:57:15.198Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/794c114f6041bbe2de23eb418ef58a0f45de27224d5540f5dbb266a73d72/propcache-0.4.0-py3-none-any.whl", hash = "sha256:015b2ca2f98ea9e08ac06eecc409d5d988f78c5fd5821b2ad42bc9afcd6b1557", size = 13183, upload-time = "2025-10-04T21:57:38.054Z" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891, upload-time = "2024-07-13T23:15:34.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/fb/a70a4214956182e0d7a9099ab17d50bfcba1056188e9b14f35b9e2b62a0d/portalocker-2.10.1-py3-none-any.whl", hash = "sha256:53a5984ebc86a025552264b459b46a2086e269b21823cb572f8f28ee759e45bf", size = 18423, upload-time = "2024-07-13T23:15:32.602Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, ] [[package]] name = "psutil" -version = "7.1.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, - { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, - { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, - { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, - { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, - { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, - { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, - { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, - { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, - { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, - { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, - { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, - { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { 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 = "py-rust-stemmers" +version = "0.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/63/4fbc14810c32d2a884e2e94e406a7d5bf8eee53e1103f558433817230342/py_rust_stemmers-0.1.5.tar.gz", hash = "sha256:e9c310cfb5c2470d7c7c8a0484725965e7cab8b1237e106a0863d5741da3e1f7", size = 9388, upload-time = "2025-02-19T13:56:28.708Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/28/2247e06de9896ac5d0fe9c6c16e611fd39549cb3197e25f12ca4437f12e7/py_rust_stemmers-0.1.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bfbd9034ae00419ff2154e33b8f5b4c4d99d1f9271f31ed059e5c7e9fa005844", size = 286084, upload-time = "2025-02-19T13:54:52.061Z" }, + { url = "https://files.pythonhosted.org/packages/95/d9/5d1743a160eb9e0bc4c162360278166474e5d168e318c0d5e1bc32b18c96/py_rust_stemmers-0.1.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7162ae66df2bb0fc39b350c24a049f5f5151c03c046092ba095c2141ec223a2", size = 272020, upload-time = "2025-02-19T13:54:53.957Z" }, + { url = "https://files.pythonhosted.org/packages/98/21/a94c32ffa38417bad41d6e72cb89a32eac45cc8c6bed1a7b2b0f88bf3626/py_rust_stemmers-0.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da6de2b694af6227ba8c5a0447d4e0ef69991e63ee558b969f90c415f33e54d0", size = 310546, upload-time = "2025-02-19T13:54:55.462Z" }, + { url = "https://files.pythonhosted.org/packages/2c/43/95449704e43be071555448507ab9242f5edebe75fe5ff5fb9674bef0fd9f/py_rust_stemmers-0.1.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a3abbd6d26722951a04550fff55460c0f26819169c23286e11ea25c645be6140", size = 315236, upload-time = "2025-02-19T13:54:56.577Z" }, + { url = "https://files.pythonhosted.org/packages/a7/77/fbd2bd6d3bb5a3395e09b990fa7598be4093d7b8958e2cadfae3d14dcc5b/py_rust_stemmers-0.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:019221c57a7bcc51097fa3f124b62d0577b5b6167184ee51abd3aea822d78f69", size = 324419, upload-time = "2025-02-19T13:54:58.373Z" }, + { url = "https://files.pythonhosted.org/packages/f4/8d/3566e9b067d3551d72320193aa9377a1ddabaf7d4624dd0a10f4c496d6f5/py_rust_stemmers-0.1.5-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:8dd5824194c279ee07f2675a55b3d728dfeec69a4b3c27329fab9b2ff5063c91", size = 324792, upload-time = "2025-02-19T13:54:59.547Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ce/9b4bdb548974c7e79f188057efb2a3426b2df8c9a3d8ac0d5a81b5f1a297/py_rust_stemmers-0.1.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7cf4d69bf20cec373ba0e89df3d98549b1a0cfb130dbd859a50ed772dd044546", size = 488012, upload-time = "2025-02-19T13:55:00.943Z" }, + { url = "https://files.pythonhosted.org/packages/fd/3e/ea9d8328af1c0661adb47daeb460185285e0e5e26aeca84df5cbde2e4e58/py_rust_stemmers-0.1.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b42eb52609ac958e7fcc441395457dc5183397e8014e954f4aed78de210837b9", size = 575579, upload-time = "2025-02-19T13:55:02.915Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ba/49ea71077a5a52017a0a30c47e944c0a4ee33a88c5eaf2d96a06e74771d6/py_rust_stemmers-0.1.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c836aeb53409a44f38b153106374fe780099a7c976c582c5ae952061ff5d2fed", size = 493265, upload-time = "2025-02-19T13:55:04.966Z" }, + { url = "https://files.pythonhosted.org/packages/d2/a7/26404770230634cec952b9f80444eba76bf8b514b1f3b550494566001893/py_rust_stemmers-0.1.5-cp310-none-win_amd64.whl", hash = "sha256:39550089f7a021a3a97fec2ff0d4ad77e471f0a65c0f100919555e60a4daabf0", size = 209394, upload-time = "2025-02-19T13:55:06.742Z" }, + { url = "https://files.pythonhosted.org/packages/36/9b/6b11f843c01d110db58a68ec4176cb77b37f03268831742a7241f4810fe4/py_rust_stemmers-0.1.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:e644987edaf66919f5a9e4693336930f98d67b790857890623a431bb77774c84", size = 286085, upload-time = "2025-02-19T13:55:08.484Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d1/e16b587dc0ebc42916b1caad994bc37fbb19ad2c7e3f5f3a586ba2630c16/py_rust_stemmers-0.1.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:910d87d39ba75da1fe3d65df88b926b4b454ada8d73893cbd36e258a8a648158", size = 272019, upload-time = "2025-02-19T13:55:10.268Z" }, + { url = "https://files.pythonhosted.org/packages/41/66/8777f125720acb896b336e6f8153e3ec39754563bc9b89523cfe06ba63da/py_rust_stemmers-0.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31ff4fb9417cec35907c18a6463e3d5a4941a5aa8401f77fbb4156b3ada69e3f", size = 310547, upload-time = "2025-02-19T13:55:11.521Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f5/b79249c787c59b9ce2c5d007c0a0dc0fc1ecccfcf98a546c131cca55899e/py_rust_stemmers-0.1.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07b3b8582313ef8a7f544acf2c887f27c3dd48c5ddca028fa0f498de7380e24f", size = 315238, upload-time = "2025-02-19T13:55:13.39Z" }, + { url = "https://files.pythonhosted.org/packages/62/4c/c05c266ed74c063ae31dc5633ed63c48eb3b78034afcc80fe755d0cb09e7/py_rust_stemmers-0.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:804944eeb5c5559443d81f30c34d6e83c6292d72423f299e42f9d71b9d240941", size = 324420, upload-time = "2025-02-19T13:55:15.292Z" }, + { url = "https://files.pythonhosted.org/packages/7f/65/feb83af28095397466e6e031989ff760cc89b01e7da169e76d4cf16a2252/py_rust_stemmers-0.1.5-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c52c5c326de78c70cfc71813fa56818d1bd4894264820d037d2be0e805b477bd", size = 324791, upload-time = "2025-02-19T13:55:16.45Z" }, + { url = "https://files.pythonhosted.org/packages/20/3e/162be2f9c1c383e66e510218d9d4946c8a84ee92c64f6d836746540e915f/py_rust_stemmers-0.1.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8f374c0f26ef35fb87212686add8dff394bcd9a1364f14ce40fe11504e25e30", size = 488014, upload-time = "2025-02-19T13:55:18.486Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ee/ed09ce6fde1eefe50aa13a8a8533aa7ebe3cc096d1a43155cc71ba28d298/py_rust_stemmers-0.1.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0ae0540453843bc36937abb54fdbc0d5d60b51ef47aa9667afd05af9248e09eb", size = 575581, upload-time = "2025-02-19T13:55:19.669Z" }, + { url = "https://files.pythonhosted.org/packages/7b/31/2a48960a072e54d7cc244204d98854d201078e1bb5c68a7843a3f6d21ced/py_rust_stemmers-0.1.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85944262c248ea30444155638c9e148a3adc61fe51cf9a3705b4055b564ec95d", size = 493269, upload-time = "2025-02-19T13:55:21.532Z" }, + { url = "https://files.pythonhosted.org/packages/91/33/872269c10ca35b00c5376159a2a0611a0f96372be16b616b46b3d59d09fe/py_rust_stemmers-0.1.5-cp311-none-win_amd64.whl", hash = "sha256:147234020b3eefe6e1a962173e41d8cf1dbf5d0689f3cd60e3022d1ac5c2e203", size = 209399, upload-time = "2025-02-19T13:55:22.639Z" }, + { url = "https://files.pythonhosted.org/packages/43/e1/ea8ac92454a634b1bb1ee0a89c2f75a4e6afec15a8412527e9bbde8c6b7b/py_rust_stemmers-0.1.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:29772837126a28263bf54ecd1bc709dd569d15a94d5e861937813ce51e8a6df4", size = 286085, upload-time = "2025-02-19T13:55:23.871Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/fe1cc3d36a19c1ce39792b1ed151ddff5ee1d74c8801f0e93ff36e65f885/py_rust_stemmers-0.1.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d62410ada44a01e02974b85d45d82f4b4c511aae9121e5f3c1ba1d0bea9126b", size = 272021, upload-time = "2025-02-19T13:55:25.685Z" }, + { url = "https://files.pythonhosted.org/packages/0a/38/b8f94e5e886e7ab181361a0911a14fb923b0d05b414de85f427e773bf445/py_rust_stemmers-0.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b28ef729a4c83c7d9418be3c23c0372493fcccc67e86783ff04596ef8a208cdf", size = 310547, upload-time = "2025-02-19T13:55:26.891Z" }, + { url = "https://files.pythonhosted.org/packages/a9/08/62e97652d359b75335486f4da134a6f1c281f38bd3169ed6ecfb276448c3/py_rust_stemmers-0.1.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a979c3f4ff7ad94a0d4cf566ca7bfecebb59e66488cc158e64485cf0c9a7879f", size = 315237, upload-time = "2025-02-19T13:55:28.116Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b9/fc0278432f288d2be4ee4d5cc80fd8013d604506b9b0503e8b8cae4ba1c3/py_rust_stemmers-0.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c3593d895453fa06bf70a7b76d6f00d06def0f91fc253fe4260920650c5e078", size = 324419, upload-time = "2025-02-19T13:55:29.211Z" }, + { url = "https://files.pythonhosted.org/packages/6b/5b/74e96eaf622fe07e83c5c389d101540e305e25f76a6d0d6fb3d9e0506db8/py_rust_stemmers-0.1.5-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:96ccc7fd042ffc3f7f082f2223bb7082ed1423aa6b43d5d89ab23e321936c045", size = 324792, upload-time = "2025-02-19T13:55:30.948Z" }, + { url = "https://files.pythonhosted.org/packages/4f/f7/b76816d7d67166e9313915ad486c21d9e7da0ac02703e14375bb1cb64b5a/py_rust_stemmers-0.1.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef18cfced2c9c676e0d7d172ba61c3fab2aa6969db64cc8f5ca33a7759efbefe", size = 488014, upload-time = "2025-02-19T13:55:32.066Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ed/7d9bed02f78d85527501f86a867cd5002d97deb791b9a6b1b45b00100010/py_rust_stemmers-0.1.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:541d4b5aa911381e3d37ec483abb6a2cf2351b4f16d5e8d77f9aa2722956662a", size = 575582, upload-time = "2025-02-19T13:55:34.005Z" }, + { url = "https://files.pythonhosted.org/packages/93/40/eafd1b33688e8e8ae946d1ef25c4dc93f5b685bd104b9c5573405d7e1d30/py_rust_stemmers-0.1.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ffd946a36e9ac17ca96821963663012e04bc0ee94d21e8b5ae034721070b436c", size = 493267, upload-time = "2025-02-19T13:55:35.294Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6a/15135b69e4fd28369433eb03264d201b1b0040ba534b05eddeb02a276684/py_rust_stemmers-0.1.5-cp312-none-win_amd64.whl", hash = "sha256:6ed61e1207f3b7428e99b5d00c055645c6415bb75033bff2d06394cbe035fd8e", size = 209395, upload-time = "2025-02-19T13:55:36.519Z" }, + { url = "https://files.pythonhosted.org/packages/80/b8/030036311ec25952bf3083b6c105be5dee052a71aa22d5fbeb857ebf8c1c/py_rust_stemmers-0.1.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:398b3a843a9cd4c5d09e726246bc36f66b3d05b0a937996814e91f47708f5db5", size = 286086, upload-time = "2025-02-19T13:55:37.581Z" }, + { url = "https://files.pythonhosted.org/packages/ed/be/0465dcb3a709ee243d464e89231e3da580017f34279d6304de291d65ccb0/py_rust_stemmers-0.1.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4e308fc7687901f0c73603203869908f3156fa9c17c4ba010a7fcc98a7a1c5f2", size = 272019, upload-time = "2025-02-19T13:55:39.183Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b6/76ca5b1f30cba36835938b5d9abee0c130c81833d51b9006264afdf8df3c/py_rust_stemmers-0.1.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f9efc4da5e734bdd00612e7506de3d0c9b7abc4b89d192742a0569d0d1fe749", size = 310545, upload-time = "2025-02-19T13:55:40.339Z" }, + { url = "https://files.pythonhosted.org/packages/56/8f/5be87618cea2fe2e70e74115a20724802bfd06f11c7c43514b8288eb6514/py_rust_stemmers-0.1.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc2cc8d2b36bc05b8b06506199ac63d437360ae38caefd98cd19e479d35afd42", size = 315236, upload-time = "2025-02-19T13:55:41.55Z" }, + { url = "https://files.pythonhosted.org/packages/00/02/ea86a316aee0f0a9d1449ad4dbffff38f4cf0a9a31045168ae8b95d8bdf8/py_rust_stemmers-0.1.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a231dc6f0b2a5f12a080dfc7abd9e6a4ea0909290b10fd0a4620e5a0f52c3d17", size = 324419, upload-time = "2025-02-19T13:55:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/1612c22545dcc0abe2f30fc08f30a2332f2224dd536fa1508444a9ca0e39/py_rust_stemmers-0.1.5-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5845709d48afc8b29e248f42f92431155a3d8df9ba30418301c49c6072b181b0", size = 324794, upload-time = "2025-02-19T13:55:43.896Z" }, + { url = "https://files.pythonhosted.org/packages/66/18/8a547584d7edac9e7ac9c7bdc53228d6f751c0f70a317093a77c386c8ddc/py_rust_stemmers-0.1.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e48bfd5e3ce9d223bfb9e634dc1425cf93ee57eef6f56aa9a7120ada3990d4be", size = 488014, upload-time = "2025-02-19T13:55:45.088Z" }, + { url = "https://files.pythonhosted.org/packages/3b/87/4619c395b325e26048a6e28a365afed754614788ba1f49b2eefb07621a03/py_rust_stemmers-0.1.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:35d32f6e7bdf6fd90e981765e32293a8be74def807147dea9fdc1f65d6ce382f", size = 575582, upload-time = "2025-02-19T13:55:46.436Z" }, + { url = "https://files.pythonhosted.org/packages/98/6e/214f1a889142b7df6d716e7f3fea6c41e87bd6c29046aa57e175d452b104/py_rust_stemmers-0.1.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:191ea8bf922c984631ffa20bf02ef0ad7eec0465baeaed3852779e8f97c7e7a3", size = 493269, upload-time = "2025-02-19T13:55:49.057Z" }, + { url = "https://files.pythonhosted.org/packages/e1/b9/c5185df277576f995ae34418eb2b2ac12f30835412270f9e05c52face521/py_rust_stemmers-0.1.5-cp313-none-win_amd64.whl", hash = "sha256:e564c9efdbe7621704e222b53bac265b0e4fbea788f07c814094f0ec6b80adcf", size = 209397, upload-time = "2025-02-19T13:55:50.853Z" }, + { url = "https://files.pythonhosted.org/packages/ca/fa/796ba1ae243bac9bdcf89c7605d642d21e07ae4f6b77a3c968d546371353/py_rust_stemmers-0.1.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f8c6596f04e7a6df2a5cc18854d31b133d2a69a8c494fa49853fe174d8739d14", size = 286746, upload-time = "2025-02-19T13:56:22.871Z" }, + { url = "https://files.pythonhosted.org/packages/4a/66/3c547373839d615217cd94c47ae1965366fa37642ef1bc4f8d32a5884a84/py_rust_stemmers-0.1.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:154c27f5d576fabf2bacf53620f014562af4c6cf9eb09ba7477830f2be868902", size = 272130, upload-time = "2025-02-19T13:56:25.114Z" }, + { url = "https://files.pythonhosted.org/packages/d8/8f/381502753e8917e874daefad0000f61d6069dffaba91acbdb864a74cae10/py_rust_stemmers-0.1.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec42b66927b62fd57328980b6c7004fe85e8fad89c952e8718da68b805a119e3", size = 310955, upload-time = "2025-02-19T13:56:26.368Z" }, + { url = "https://files.pythonhosted.org/packages/3a/15/b1894b9741f7a48f0b4cbea458f7d4141a6df6a1b26bec05fcde96703ce1/py_rust_stemmers-0.1.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57b061c3b4af9e409d009d729b21bc53dabe47116c955ccf0b642a5a2d438f93", size = 324879, upload-time = "2025-02-19T13:56:27.462Z" }, ] [[package]] @@ -2424,41 +2011,48 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] -name = "pynndescent" -version = "0.5.13" +name = "pymorphy3" +version = "2.0.6" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "joblib" }, - { name = "llvmlite" }, - { name = "numba" }, - { name = "scikit-learn" }, - { name = "scipy" }, + { name = "dawg2-python" }, + { name = "pymorphy3-dicts-ru" }, + { name = "setuptools", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/63/3a1eabd3a7e6e060b69a87fe9c28fe89f75f4d49e55f0caf2e29c943c003/pymorphy3-2.0.6.tar.gz", hash = "sha256:1603df3bc9e116967c990607f5b97d42fb1c572d6839b851af3501e51d7f5493", size = 97681, upload-time = "2025-10-09T16:06:18.718Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/4b/59bac03278033e293d1405ed42fb6c6252c25f40c50f509c615caeaa3b71/pymorphy3-2.0.6-py3-none-any.whl", hash = "sha256:0254317c02ce3ea17e080b7fc9d675e44662b3a5296bae68605b7a41d25b36c3", size = 53900, upload-time = "2025-10-09T16:06:17.721Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/58/560a4db5eb3794d922fe55804b10326534ded3d971e1933c1eef91193f5e/pynndescent-0.5.13.tar.gz", hash = "sha256:d74254c0ee0a1eeec84597d5fe89fedcf778593eeabe32c2f97412934a9800fb", size = 2975955, upload-time = "2024-06-17T15:48:32.914Z" } + +[[package]] +name = "pymorphy3-dicts-ru" +version = "2.4.417150.4580142" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/13/02ffe6893a777add5c8a43f212f31a3f6a03e7d44a484cf7b5ac5381fddb/pymorphy3-dicts-ru-2.4.417150.4580142.tar.gz", hash = "sha256:39ab379d4ca905bafed50f5afc3a3de6f9643605776fbcabc4d3088d4ed382b0", size = 8381569, upload-time = "2022-01-08T22:17:37.581Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/53/d23a97e0a2c690d40b165d1062e2c4ccc796be458a1ce59f6ba030434663/pynndescent-0.5.13-py3-none-any.whl", hash = "sha256:69aabb8f394bc631b6ac475a1c7f3994c54adf3f51cd63b2730fefba5771b949", size = 56850, upload-time = "2024-06-17T15:48:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/b0/67/469e9e52d046863f5959928794d3067d455a77f580bf4a662630a43eb426/pymorphy3_dicts_ru-2.4.417150.4580142-py2.py3-none-any.whl", hash = "sha256:718bac64c73c10c16073a199402657283d9b64c04188b694f6d3e9b0d85440f4", size = 8442043, upload-time = "2022-01-08T22:17:34.282Z" }, ] [[package]] -name = "pyparsing" -version = "3.2.5" +name = "pyreadline3" +version = "3.5.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, + { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, ] [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -2469,9 +2063,9 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] @@ -2490,16 +2084,16 @@ wheels = [ [[package]] name = "pytest-cov" -version = "7.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] [[package]] @@ -2532,6 +2126,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, + { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, + { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, + { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -2596,6 +2212,52 @@ 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.12.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and platform_machine != 's390x'", + "python_full_version >= '3.14' and platform_machine == 's390x'", + "python_full_version == '3.13.*'", +] +dependencies = [ + { name = "grpcio", marker = "python_full_version >= '3.13'" }, + { name = "grpcio-tools", marker = "python_full_version >= '3.13'" }, + { name = "httpx", extra = ["http2"], marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "portalocker", marker = "python_full_version >= '3.13'" }, + { name = "pydantic", marker = "python_full_version >= '3.13'" }, + { name = "urllib3", marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/5e/ec560881e086f893947c8798949c72de5cfae9453fd05c2250f8dfeaa571/qdrant_client-1.12.1.tar.gz", hash = "sha256:35e8e646f75b7b883b3d2d0ee4c69c5301000bba41c82aa546e985db0f1aeb72", size = 237441, upload-time = "2024-10-29T17:31:09.698Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/c0/eef4fe9dad6d41333f7dc6567fa8144ffc1837c8a0edfc2317d50715335f/qdrant_client-1.12.1-py3-none-any.whl", hash = "sha256:b2d17ce18e9e767471368380dd3bbc4a0e3a0e2061fedc9af3542084b48451e0", size = 267171, upload-time = "2024-10-29T17:31:07.758Z" }, +] + +[[package]] +name = "qdrant-client" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "grpcio", marker = "python_full_version < '3.13'" }, + { name = "httpx", extra = ["http2"], marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "portalocker", marker = "python_full_version < '3.13'" }, + { name = "protobuf", marker = "python_full_version < '3.13'" }, + { name = "pydantic", marker = "python_full_version < '3.13'" }, + { name = "urllib3", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/dd/f8a8261b83946af3cd65943c93c4f83e044f01184e8525404989d22a81a5/qdrant_client-1.17.1.tar.gz", hash = "sha256:22f990bbd63485ed97ba551a4c498181fcb723f71dcab5d6e4e43fe1050a2bc0", size = 344979, upload-time = "2026-03-13T17:13:44.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/69/77d1a971c4b933e8c79403e99bcbb790463da5e48333cc4fd5d412c63c98/qdrant_client-1.17.1-py3-none-any.whl", hash = "sha256:6cda4064adfeaf211c751f3fbc00edbbdb499850918c7aff4855a9a759d56cbd", size = 389947, upload-time = "2026-03-13T17:13:43.156Z" }, +] + [[package]] name = "razdel" version = "0.5.0" @@ -2607,114 +2269,128 @@ wheels = [ [[package]] name = "regex" -version = "2025.9.18" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/d3/eaa0d28aba6ad1827ad1e716d9a93e1ba963ada61887498297d3da715133/regex-2025.9.18.tar.gz", hash = "sha256:c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4", size = 400917, upload-time = "2025-09-19T00:38:35.79Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d8/7e06171db8e55f917c5b8e89319cea2d86982e3fc46b677f40358223dece/regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788", size = 484829, upload-time = "2025-09-19T00:35:05.215Z" }, - { url = "https://files.pythonhosted.org/packages/8d/70/bf91bb39e5bedf75ce730ffbaa82ca585584d13335306d637458946b8b9f/regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4", size = 288993, upload-time = "2025-09-19T00:35:08.154Z" }, - { url = "https://files.pythonhosted.org/packages/fe/89/69f79b28365eda2c46e64c39d617d5f65a2aa451a4c94de7d9b34c2dc80f/regex-2025.9.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87f681bfca84ebd265278b5daa1dcb57f4db315da3b5d044add7c30c10442e61", size = 286624, upload-time = "2025-09-19T00:35:09.717Z" }, - { url = "https://files.pythonhosted.org/packages/44/31/81e62955726c3a14fcc1049a80bc716765af6c055706869de5e880ddc783/regex-2025.9.18-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34d674cbba70c9398074c8a1fcc1a79739d65d1105de2a3c695e2b05ea728251", size = 780473, upload-time = "2025-09-19T00:35:11.013Z" }, - { url = "https://files.pythonhosted.org/packages/fb/23/07072b7e191fbb6e213dc03b2f5b96f06d3c12d7deaded84679482926fc7/regex-2025.9.18-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:385c9b769655cb65ea40b6eea6ff763cbb6d69b3ffef0b0db8208e1833d4e746", size = 849290, upload-time = "2025-09-19T00:35:12.348Z" }, - { url = "https://files.pythonhosted.org/packages/b3/f0/aec7f6a01f2a112210424d77c6401b9015675fb887ced7e18926df4ae51e/regex-2025.9.18-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8900b3208e022570ae34328712bef6696de0804c122933414014bae791437ab2", size = 897335, upload-time = "2025-09-19T00:35:14.058Z" }, - { url = "https://files.pythonhosted.org/packages/cc/90/2e5f9da89d260de7d0417ead91a1bc897f19f0af05f4f9323313b76c47f2/regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c204e93bf32cd7a77151d44b05eb36f469d0898e3fba141c026a26b79d9914a0", size = 789946, upload-time = "2025-09-19T00:35:15.403Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d5/1c712c7362f2563d389be66bae131c8bab121a3fabfa04b0b5bfc9e73c51/regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3acc471d1dd7e5ff82e6cacb3b286750decd949ecd4ae258696d04f019817ef8", size = 780787, upload-time = "2025-09-19T00:35:17.061Z" }, - { url = "https://files.pythonhosted.org/packages/4f/92/c54cdb4aa41009632e69817a5aa452673507f07e341076735a2f6c46a37c/regex-2025.9.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6479d5555122433728760e5f29edb4c2b79655a8deb681a141beb5c8a025baea", size = 773632, upload-time = "2025-09-19T00:35:18.57Z" }, - { url = "https://files.pythonhosted.org/packages/db/99/75c996dc6a2231a8652d7ad0bfbeaf8a8c77612d335580f520f3ec40e30b/regex-2025.9.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:431bd2a8726b000eb6f12429c9b438a24062a535d06783a93d2bcbad3698f8a8", size = 844104, upload-time = "2025-09-19T00:35:20.259Z" }, - { url = "https://files.pythonhosted.org/packages/1c/f7/25aba34cc130cb6844047dbfe9716c9b8f9629fee8b8bec331aa9241b97b/regex-2025.9.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0cc3521060162d02bd36927e20690129200e5ac9d2c6d32b70368870b122db25", size = 834794, upload-time = "2025-09-19T00:35:22.002Z" }, - { url = "https://files.pythonhosted.org/packages/51/eb/64e671beafa0ae29712268421597596d781704973551312b2425831d4037/regex-2025.9.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a021217b01be2d51632ce056d7a837d3fa37c543ede36e39d14063176a26ae29", size = 778535, upload-time = "2025-09-19T00:35:23.298Z" }, - { url = "https://files.pythonhosted.org/packages/26/33/c0ebc0b07bd0bf88f716cca240546b26235a07710ea58e271cfe390ae273/regex-2025.9.18-cp310-cp310-win32.whl", hash = "sha256:4a12a06c268a629cb67cc1d009b7bb0be43e289d00d5111f86a2efd3b1949444", size = 264115, upload-time = "2025-09-19T00:35:25.206Z" }, - { url = "https://files.pythonhosted.org/packages/59/39/aeb11a4ae68faaec2498512cadae09f2d8a91f1f65730fe62b9bffeea150/regex-2025.9.18-cp310-cp310-win_amd64.whl", hash = "sha256:47acd811589301298c49db2c56bde4f9308d6396da92daf99cba781fa74aa450", size = 276143, upload-time = "2025-09-19T00:35:26.785Z" }, - { url = "https://files.pythonhosted.org/packages/29/04/37f2d3fc334a1031fc2767c9d89cec13c2e72207c7e7f6feae8a47f4e149/regex-2025.9.18-cp310-cp310-win_arm64.whl", hash = "sha256:16bd2944e77522275e5ee36f867e19995bcaa533dcb516753a26726ac7285442", size = 268473, upload-time = "2025-09-19T00:35:28.39Z" }, - { url = "https://files.pythonhosted.org/packages/58/61/80eda662fc4eb32bfedc331f42390974c9e89c7eac1b79cd9eea4d7c458c/regex-2025.9.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:51076980cd08cd13c88eb7365427ae27f0d94e7cebe9ceb2bb9ffdae8fc4d82a", size = 484832, upload-time = "2025-09-19T00:35:30.011Z" }, - { url = "https://files.pythonhosted.org/packages/a6/d9/33833d9abddf3f07ad48504ddb53fe3b22f353214bbb878a72eee1e3ddbf/regex-2025.9.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:828446870bd7dee4e0cbeed767f07961aa07f0ea3129f38b3ccecebc9742e0b8", size = 288994, upload-time = "2025-09-19T00:35:31.733Z" }, - { url = "https://files.pythonhosted.org/packages/2a/b3/526ee96b0d70ea81980cbc20c3496fa582f775a52e001e2743cc33b2fa75/regex-2025.9.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28821d5637866479ec4cc23b8c990f5bc6dd24e5e4384ba4a11d38a526e1414", size = 286619, upload-time = "2025-09-19T00:35:33.221Z" }, - { url = "https://files.pythonhosted.org/packages/65/4f/c2c096b02a351b33442aed5895cdd8bf87d372498d2100927c5a053d7ba3/regex-2025.9.18-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726177ade8e481db669e76bf99de0b278783be8acd11cef71165327abd1f170a", size = 792454, upload-time = "2025-09-19T00:35:35.361Z" }, - { url = "https://files.pythonhosted.org/packages/24/15/b562c9d6e47c403c4b5deb744f8b4bf6e40684cf866c7b077960a925bdff/regex-2025.9.18-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5cca697da89b9f8ea44115ce3130f6c54c22f541943ac8e9900461edc2b8bd4", size = 858723, upload-time = "2025-09-19T00:35:36.949Z" }, - { url = "https://files.pythonhosted.org/packages/f2/01/dba305409849e85b8a1a681eac4c03ed327d8de37895ddf9dc137f59c140/regex-2025.9.18-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dfbde38f38004703c35666a1e1c088b778e35d55348da2b7b278914491698d6a", size = 905899, upload-time = "2025-09-19T00:35:38.723Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d0/c51d1e6a80eab11ef96a4cbad17fc0310cf68994fb01a7283276b7e5bbd6/regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2f422214a03fab16bfa495cfec72bee4aaa5731843b771860a471282f1bf74f", size = 798981, upload-time = "2025-09-19T00:35:40.416Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5e/72db90970887bbe02296612bd61b0fa31e6d88aa24f6a4853db3e96c575e/regex-2025.9.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a295916890f4df0902e4286bc7223ee7f9e925daa6dcdec4192364255b70561a", size = 781900, upload-time = "2025-09-19T00:35:42.077Z" }, - { url = "https://files.pythonhosted.org/packages/50/ff/596be45eea8e9bc31677fde243fa2904d00aad1b32c31bce26c3dbba0b9e/regex-2025.9.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5db95ff632dbabc8c38c4e82bf545ab78d902e81160e6e455598014f0abe66b9", size = 852952, upload-time = "2025-09-19T00:35:43.751Z" }, - { url = "https://files.pythonhosted.org/packages/e5/1b/2dfa348fa551e900ed3f5f63f74185b6a08e8a76bc62bc9c106f4f92668b/regex-2025.9.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb967eb441b0f15ae610b7069bdb760b929f267efbf522e814bbbfffdf125ce2", size = 844355, upload-time = "2025-09-19T00:35:45.309Z" }, - { url = "https://files.pythonhosted.org/packages/f4/bf/aefb1def27fe33b8cbbb19c75c13aefccfbef1c6686f8e7f7095705969c7/regex-2025.9.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f04d2f20da4053d96c08f7fde6e1419b7ec9dbcee89c96e3d731fca77f411b95", size = 787254, upload-time = "2025-09-19T00:35:46.904Z" }, - { url = "https://files.pythonhosted.org/packages/e3/4e/8ef042e7cf0dbbb401e784e896acfc1b367b95dfbfc9ada94c2ed55a081f/regex-2025.9.18-cp311-cp311-win32.whl", hash = "sha256:895197241fccf18c0cea7550c80e75f185b8bd55b6924fcae269a1a92c614a07", size = 264129, upload-time = "2025-09-19T00:35:48.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/7d/c4fcabf80dcdd6821c0578ad9b451f8640b9110fb3dcb74793dd077069ff/regex-2025.9.18-cp311-cp311-win_amd64.whl", hash = "sha256:7e2b414deae99166e22c005e154a5513ac31493db178d8aec92b3269c9cce8c9", size = 276160, upload-time = "2025-09-19T00:36:00.45Z" }, - { url = "https://files.pythonhosted.org/packages/64/f8/0e13c8ae4d6df9d128afaba138342d532283d53a4c1e7a8c93d6756c8f4a/regex-2025.9.18-cp311-cp311-win_arm64.whl", hash = "sha256:fb137ec7c5c54f34a25ff9b31f6b7b0c2757be80176435bf367111e3f71d72df", size = 268471, upload-time = "2025-09-19T00:36:02.149Z" }, - { url = "https://files.pythonhosted.org/packages/b0/99/05859d87a66ae7098222d65748f11ef7f2dff51bfd7482a4e2256c90d72b/regex-2025.9.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:436e1b31d7efd4dcd52091d076482031c611dde58bf9c46ca6d0a26e33053a7e", size = 486335, upload-time = "2025-09-19T00:36:03.661Z" }, - { url = "https://files.pythonhosted.org/packages/97/7e/d43d4e8b978890932cf7b0957fce58c5b08c66f32698f695b0c2c24a48bf/regex-2025.9.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c190af81e5576b9c5fdc708f781a52ff20f8b96386c6e2e0557a78402b029f4a", size = 289720, upload-time = "2025-09-19T00:36:05.471Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3b/ff80886089eb5dcf7e0d2040d9aaed539e25a94300403814bb24cc775058/regex-2025.9.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4121f1ce2b2b5eec4b397cc1b277686e577e658d8f5870b7eb2d726bd2300ab", size = 287257, upload-time = "2025-09-19T00:36:07.072Z" }, - { url = "https://files.pythonhosted.org/packages/ee/66/243edf49dd8720cba8d5245dd4d6adcb03a1defab7238598c0c97cf549b8/regex-2025.9.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:300e25dbbf8299d87205e821a201057f2ef9aa3deb29caa01cd2cac669e508d5", size = 797463, upload-time = "2025-09-19T00:36:08.399Z" }, - { url = "https://files.pythonhosted.org/packages/df/71/c9d25a1142c70432e68bb03211d4a82299cd1c1fbc41db9409a394374ef5/regex-2025.9.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b47fcf9f5316c0bdaf449e879407e1b9937a23c3b369135ca94ebc8d74b1742", size = 862670, upload-time = "2025-09-19T00:36:10.101Z" }, - { url = "https://files.pythonhosted.org/packages/f8/8f/329b1efc3a64375a294e3a92d43372bf1a351aa418e83c21f2f01cf6ec41/regex-2025.9.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:57a161bd3acaa4b513220b49949b07e252165e6b6dc910ee7617a37ff4f5b425", size = 910881, upload-time = "2025-09-19T00:36:12.223Z" }, - { url = "https://files.pythonhosted.org/packages/35/9e/a91b50332a9750519320ed30ec378b74c996f6befe282cfa6bb6cea7e9fd/regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352", size = 802011, upload-time = "2025-09-19T00:36:13.901Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1d/6be3b8d7856b6e0d7ee7f942f437d0a76e0d5622983abbb6d21e21ab9a17/regex-2025.9.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f96fa342b6f54dcba928dd452e8d8cb9f0d63e711d1721cd765bb9f73bb048d", size = 786668, upload-time = "2025-09-19T00:36:15.391Z" }, - { url = "https://files.pythonhosted.org/packages/cb/ce/4a60e53df58bd157c5156a1736d3636f9910bdcc271d067b32b7fcd0c3a8/regex-2025.9.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f0d676522d68c207828dcd01fb6f214f63f238c283d9f01d85fc664c7c85b56", size = 856578, upload-time = "2025-09-19T00:36:16.845Z" }, - { url = "https://files.pythonhosted.org/packages/86/e8/162c91bfe7217253afccde112868afb239f94703de6580fb235058d506a6/regex-2025.9.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40532bff8a1a0621e7903ae57fce88feb2e8a9a9116d341701302c9302aef06e", size = 849017, upload-time = "2025-09-19T00:36:18.597Z" }, - { url = "https://files.pythonhosted.org/packages/35/34/42b165bc45289646ea0959a1bc7531733e90b47c56a72067adfe6b3251f6/regex-2025.9.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:039f11b618ce8d71a1c364fdee37da1012f5a3e79b1b2819a9f389cd82fd6282", size = 788150, upload-time = "2025-09-19T00:36:20.464Z" }, - { url = "https://files.pythonhosted.org/packages/79/5d/cdd13b1f3c53afa7191593a7ad2ee24092a5a46417725ffff7f64be8342d/regex-2025.9.18-cp312-cp312-win32.whl", hash = "sha256:e1dd06f981eb226edf87c55d523131ade7285137fbde837c34dc9d1bf309f459", size = 264536, upload-time = "2025-09-19T00:36:21.922Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f5/4a7770c9a522e7d2dc1fa3ffc83ab2ab33b0b22b447e62cffef186805302/regex-2025.9.18-cp312-cp312-win_amd64.whl", hash = "sha256:3d86b5247bf25fa3715e385aa9ff272c307e0636ce0c9595f64568b41f0a9c77", size = 275501, upload-time = "2025-09-19T00:36:23.4Z" }, - { url = "https://files.pythonhosted.org/packages/df/05/9ce3e110e70d225ecbed455b966003a3afda5e58e8aec2964042363a18f4/regex-2025.9.18-cp312-cp312-win_arm64.whl", hash = "sha256:032720248cbeeae6444c269b78cb15664458b7bb9ed02401d3da59fe4d68c3a5", size = 268601, upload-time = "2025-09-19T00:36:25.092Z" }, - { url = "https://files.pythonhosted.org/packages/d2/c7/5c48206a60ce33711cf7dcaeaed10dd737733a3569dc7e1dce324dd48f30/regex-2025.9.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a40f929cd907c7e8ac7566ac76225a77701a6221bca937bdb70d56cb61f57b2", size = 485955, upload-time = "2025-09-19T00:36:26.822Z" }, - { url = "https://files.pythonhosted.org/packages/e9/be/74fc6bb19a3c491ec1ace943e622b5a8539068771e8705e469b2da2306a7/regex-2025.9.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c90471671c2cdf914e58b6af62420ea9ecd06d1554d7474d50133ff26ae88feb", size = 289583, upload-time = "2025-09-19T00:36:28.577Z" }, - { url = "https://files.pythonhosted.org/packages/25/c4/9ceaa433cb5dc515765560f22a19578b95b92ff12526e5a259321c4fc1a0/regex-2025.9.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a351aff9e07a2dabb5022ead6380cff17a4f10e4feb15f9100ee56c4d6d06af", size = 287000, upload-time = "2025-09-19T00:36:30.161Z" }, - { url = "https://files.pythonhosted.org/packages/7d/e6/68bc9393cb4dc68018456568c048ac035854b042bc7c33cb9b99b0680afa/regex-2025.9.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc4b8e9d16e20ddfe16430c23468a8707ccad3365b06d4536142e71823f3ca29", size = 797535, upload-time = "2025-09-19T00:36:31.876Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/ebae9032d34b78ecfe9bd4b5e6575b55351dc8513485bb92326613732b8c/regex-2025.9.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b8cdbddf2db1c5e80338ba2daa3cfa3dec73a46fff2a7dda087c8efbf12d62f", size = 862603, upload-time = "2025-09-19T00:36:33.344Z" }, - { url = "https://files.pythonhosted.org/packages/3b/74/12332c54b3882557a4bcd2b99f8be581f5c6a43cf1660a85b460dd8ff468/regex-2025.9.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a276937d9d75085b2c91fb48244349c6954f05ee97bba0963ce24a9d915b8b68", size = 910829, upload-time = "2025-09-19T00:36:34.826Z" }, - { url = "https://files.pythonhosted.org/packages/86/70/ba42d5ed606ee275f2465bfc0e2208755b06cdabd0f4c7c4b614d51b57ab/regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783", size = 802059, upload-time = "2025-09-19T00:36:36.664Z" }, - { url = "https://files.pythonhosted.org/packages/da/c5/fcb017e56396a7f2f8357412638d7e2963440b131a3ca549be25774b3641/regex-2025.9.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dc6893b1f502d73037cf807a321cdc9be29ef3d6219f7970f842475873712ac", size = 786781, upload-time = "2025-09-19T00:36:38.168Z" }, - { url = "https://files.pythonhosted.org/packages/c6/ee/21c4278b973f630adfb3bcb23d09d83625f3ab1ca6e40ebdffe69901c7a1/regex-2025.9.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a61e85bfc63d232ac14b015af1261f826260c8deb19401c0597dbb87a864361e", size = 856578, upload-time = "2025-09-19T00:36:40.129Z" }, - { url = "https://files.pythonhosted.org/packages/87/0b/de51550dc7274324435c8f1539373ac63019b0525ad720132866fff4a16a/regex-2025.9.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1ef86a9ebc53f379d921fb9a7e42b92059ad3ee800fcd9e0fe6181090e9f6c23", size = 849119, upload-time = "2025-09-19T00:36:41.651Z" }, - { url = "https://files.pythonhosted.org/packages/60/52/383d3044fc5154d9ffe4321696ee5b2ee4833a28c29b137c22c33f41885b/regex-2025.9.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d3bc882119764ba3a119fbf2bd4f1b47bc56c1da5d42df4ed54ae1e8e66fdf8f", size = 788219, upload-time = "2025-09-19T00:36:43.575Z" }, - { url = "https://files.pythonhosted.org/packages/20/bd/2614fc302671b7359972ea212f0e3a92df4414aaeacab054a8ce80a86073/regex-2025.9.18-cp313-cp313-win32.whl", hash = "sha256:3810a65675845c3bdfa58c3c7d88624356dd6ee2fc186628295e0969005f928d", size = 264517, upload-time = "2025-09-19T00:36:45.503Z" }, - { url = "https://files.pythonhosted.org/packages/07/0f/ab5c1581e6563a7bffdc1974fb2d25f05689b88e2d416525271f232b1946/regex-2025.9.18-cp313-cp313-win_amd64.whl", hash = "sha256:16eaf74b3c4180ede88f620f299e474913ab6924d5c4b89b3833bc2345d83b3d", size = 275481, upload-time = "2025-09-19T00:36:46.965Z" }, - { url = "https://files.pythonhosted.org/packages/49/22/ee47672bc7958f8c5667a587c2600a4fba8b6bab6e86bd6d3e2b5f7cac42/regex-2025.9.18-cp313-cp313-win_arm64.whl", hash = "sha256:4dc98ba7dd66bd1261927a9f49bd5ee2bcb3660f7962f1ec02617280fc00f5eb", size = 268598, upload-time = "2025-09-19T00:36:48.314Z" }, - { url = "https://files.pythonhosted.org/packages/e8/83/6887e16a187c6226cb85d8301e47d3b73ecc4505a3a13d8da2096b44fd76/regex-2025.9.18-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fe5d50572bc885a0a799410a717c42b1a6b50e2f45872e2b40f4f288f9bce8a2", size = 489765, upload-time = "2025-09-19T00:36:49.996Z" }, - { url = "https://files.pythonhosted.org/packages/51/c5/e2f7325301ea2916ff301c8d963ba66b1b2c1b06694191df80a9c4fea5d0/regex-2025.9.18-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b9d9a2d6cda6621551ca8cf7a06f103adf72831153f3c0d982386110870c4d3", size = 291228, upload-time = "2025-09-19T00:36:51.654Z" }, - { url = "https://files.pythonhosted.org/packages/91/60/7d229d2bc6961289e864a3a3cfebf7d0d250e2e65323a8952cbb7e22d824/regex-2025.9.18-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:13202e4c4ac0ef9a317fff817674b293c8f7e8c68d3190377d8d8b749f566e12", size = 289270, upload-time = "2025-09-19T00:36:53.118Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d7/b4f06868ee2958ff6430df89857fbf3d43014bbf35538b6ec96c2704e15d/regex-2025.9.18-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874ff523b0fecffb090f80ae53dc93538f8db954c8bb5505f05b7787ab3402a0", size = 806326, upload-time = "2025-09-19T00:36:54.631Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e4/bca99034a8f1b9b62ccf337402a8e5b959dd5ba0e5e5b2ead70273df3277/regex-2025.9.18-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d13ab0490128f2bb45d596f754148cd750411afc97e813e4b3a61cf278a23bb6", size = 871556, upload-time = "2025-09-19T00:36:56.208Z" }, - { url = "https://files.pythonhosted.org/packages/6d/df/e06ffaf078a162f6dd6b101a5ea9b44696dca860a48136b3ae4a9caf25e2/regex-2025.9.18-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05440bc172bc4b4b37fb9667e796597419404dbba62e171e1f826d7d2a9ebcef", size = 913817, upload-time = "2025-09-19T00:36:57.807Z" }, - { url = "https://files.pythonhosted.org/packages/9e/05/25b05480b63292fd8e84800b1648e160ca778127b8d2367a0a258fa2e225/regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a", size = 811055, upload-time = "2025-09-19T00:36:59.762Z" }, - { url = "https://files.pythonhosted.org/packages/70/97/7bc7574655eb651ba3a916ed4b1be6798ae97af30104f655d8efd0cab24b/regex-2025.9.18-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:65d3c38c39efce73e0d9dc019697b39903ba25b1ad45ebbd730d2cf32741f40d", size = 794534, upload-time = "2025-09-19T00:37:01.405Z" }, - { url = "https://files.pythonhosted.org/packages/b4/c2/d5da49166a52dda879855ecdba0117f073583db2b39bb47ce9a3378a8e9e/regex-2025.9.18-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ae77e447ebc144d5a26d50055c6ddba1d6ad4a865a560ec7200b8b06bc529368", size = 866684, upload-time = "2025-09-19T00:37:03.441Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2d/0a5c4e6ec417de56b89ff4418ecc72f7e3feca806824c75ad0bbdae0516b/regex-2025.9.18-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3ef8cf53dc8df49d7e28a356cf824e3623764e9833348b655cfed4524ab8a90", size = 853282, upload-time = "2025-09-19T00:37:04.985Z" }, - { url = "https://files.pythonhosted.org/packages/f4/8e/d656af63e31a86572ec829665d6fa06eae7e144771e0330650a8bb865635/regex-2025.9.18-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9feb29817df349c976da9a0debf775c5c33fc1c8ad7b9f025825da99374770b7", size = 797830, upload-time = "2025-09-19T00:37:06.697Z" }, - { url = "https://files.pythonhosted.org/packages/db/ce/06edc89df8f7b83ffd321b6071be4c54dc7332c0f77860edc40ce57d757b/regex-2025.9.18-cp313-cp313t-win32.whl", hash = "sha256:168be0d2f9b9d13076940b1ed774f98595b4e3c7fc54584bba81b3cc4181742e", size = 267281, upload-time = "2025-09-19T00:37:08.568Z" }, - { url = "https://files.pythonhosted.org/packages/83/9a/2b5d9c8b307a451fd17068719d971d3634ca29864b89ed5c18e499446d4a/regex-2025.9.18-cp313-cp313t-win_amd64.whl", hash = "sha256:d59ecf3bb549e491c8104fea7313f3563c7b048e01287db0a90485734a70a730", size = 278724, upload-time = "2025-09-19T00:37:10.023Z" }, - { url = "https://files.pythonhosted.org/packages/3d/70/177d31e8089a278a764f8ec9a3faac8d14a312d622a47385d4b43905806f/regex-2025.9.18-cp313-cp313t-win_arm64.whl", hash = "sha256:dbef80defe9fb21310948a2595420b36c6d641d9bea4c991175829b2cc4bc06a", size = 269771, upload-time = "2025-09-19T00:37:13.041Z" }, - { url = "https://files.pythonhosted.org/packages/44/b7/3b4663aa3b4af16819f2ab6a78c4111c7e9b066725d8107753c2257448a5/regex-2025.9.18-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c6db75b51acf277997f3adcd0ad89045d856190d13359f15ab5dda21581d9129", size = 486130, upload-time = "2025-09-19T00:37:14.527Z" }, - { url = "https://files.pythonhosted.org/packages/80/5b/4533f5d7ac9c6a02a4725fe8883de2aebc713e67e842c04cf02626afb747/regex-2025.9.18-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8f9698b6f6895d6db810e0bda5364f9ceb9e5b11328700a90cae573574f61eea", size = 289539, upload-time = "2025-09-19T00:37:16.356Z" }, - { url = "https://files.pythonhosted.org/packages/b8/8d/5ab6797c2750985f79e9995fad3254caa4520846580f266ae3b56d1cae58/regex-2025.9.18-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29cd86aa7cb13a37d0f0d7c21d8d949fe402ffa0ea697e635afedd97ab4b69f1", size = 287233, upload-time = "2025-09-19T00:37:18.025Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/95afcb02ba8d3a64e6ffeb801718ce73471ad6440c55d993f65a4a5e7a92/regex-2025.9.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c9f285a071ee55cd9583ba24dde006e53e17780bb309baa8e4289cd472bcc47", size = 797876, upload-time = "2025-09-19T00:37:19.609Z" }, - { url = "https://files.pythonhosted.org/packages/c8/fb/720b1f49cec1f3b5a9fea5b34cd22b88b5ebccc8c1b5de9cc6f65eed165a/regex-2025.9.18-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5adf266f730431e3be9021d3e5b8d5ee65e563fec2883ea8093944d21863b379", size = 863385, upload-time = "2025-09-19T00:37:21.65Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ca/e0d07ecf701e1616f015a720dc13b84c582024cbfbb3fc5394ae204adbd7/regex-2025.9.18-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1137cabc0f38807de79e28d3f6e3e3f2cc8cfb26bead754d02e6d1de5f679203", size = 910220, upload-time = "2025-09-19T00:37:23.723Z" }, - { url = "https://files.pythonhosted.org/packages/b6/45/bba86413b910b708eca705a5af62163d5d396d5f647ed9485580c7025209/regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cc9e5525cada99699ca9223cce2d52e88c52a3d2a0e842bd53de5497c604164", size = 801827, upload-time = "2025-09-19T00:37:25.684Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a6/740fbd9fcac31a1305a8eed30b44bf0f7f1e042342be0a4722c0365ecfca/regex-2025.9.18-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bbb9246568f72dce29bcd433517c2be22c7791784b223a810225af3b50d1aafb", size = 786843, upload-time = "2025-09-19T00:37:27.62Z" }, - { url = "https://files.pythonhosted.org/packages/80/a7/0579e8560682645906da640c9055506465d809cb0f5415d9976f417209a6/regex-2025.9.18-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6a52219a93dd3d92c675383efff6ae18c982e2d7651c792b1e6d121055808743", size = 857430, upload-time = "2025-09-19T00:37:29.362Z" }, - { url = "https://files.pythonhosted.org/packages/8d/9b/4dc96b6c17b38900cc9fee254fc9271d0dde044e82c78c0811b58754fde5/regex-2025.9.18-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ae9b3840c5bd456780e3ddf2f737ab55a79b790f6409182012718a35c6d43282", size = 848612, upload-time = "2025-09-19T00:37:31.42Z" }, - { url = "https://files.pythonhosted.org/packages/b3/6a/6f659f99bebb1775e5ac81a3fb837b85897c1a4ef5acffd0ff8ffe7e67fb/regex-2025.9.18-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d488c236ac497c46a5ac2005a952c1a0e22a07be9f10c3e735bc7d1209a34773", size = 787967, upload-time = "2025-09-19T00:37:34.019Z" }, - { url = "https://files.pythonhosted.org/packages/61/35/9e35665f097c07cf384a6b90a1ac11b0b1693084a0b7a675b06f760496c6/regex-2025.9.18-cp314-cp314-win32.whl", hash = "sha256:0c3506682ea19beefe627a38872d8da65cc01ffa25ed3f2e422dffa1474f0788", size = 269847, upload-time = "2025-09-19T00:37:35.759Z" }, - { url = "https://files.pythonhosted.org/packages/af/64/27594dbe0f1590b82de2821ebfe9a359b44dcb9b65524876cd12fabc447b/regex-2025.9.18-cp314-cp314-win_amd64.whl", hash = "sha256:57929d0f92bebb2d1a83af372cd0ffba2263f13f376e19b1e4fa32aec4efddc3", size = 278755, upload-time = "2025-09-19T00:37:37.367Z" }, - { url = "https://files.pythonhosted.org/packages/30/a3/0cd8d0d342886bd7d7f252d701b20ae1a3c72dc7f34ef4b2d17790280a09/regex-2025.9.18-cp314-cp314-win_arm64.whl", hash = "sha256:6a4b44df31d34fa51aa5c995d3aa3c999cec4d69b9bd414a8be51984d859f06d", size = 271873, upload-time = "2025-09-19T00:37:39.125Z" }, - { url = "https://files.pythonhosted.org/packages/99/cb/8a1ab05ecf404e18b54348e293d9b7a60ec2bd7aa59e637020c5eea852e8/regex-2025.9.18-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b176326bcd544b5e9b17d6943f807697c0cb7351f6cfb45bf5637c95ff7e6306", size = 489773, upload-time = "2025-09-19T00:37:40.968Z" }, - { url = "https://files.pythonhosted.org/packages/93/3b/6543c9b7f7e734d2404fa2863d0d710c907bef99d4598760ed4563d634c3/regex-2025.9.18-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0ffd9e230b826b15b369391bec167baed57c7ce39efc35835448618860995946", size = 291221, upload-time = "2025-09-19T00:37:42.901Z" }, - { url = "https://files.pythonhosted.org/packages/cd/91/e9fdee6ad6bf708d98c5d17fded423dcb0661795a49cba1b4ffb8358377a/regex-2025.9.18-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec46332c41add73f2b57e2f5b642f991f6b15e50e9f86285e08ffe3a512ac39f", size = 289268, upload-time = "2025-09-19T00:37:44.823Z" }, - { url = "https://files.pythonhosted.org/packages/94/a6/bc3e8a918abe4741dadeaeb6c508e3a4ea847ff36030d820d89858f96a6c/regex-2025.9.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b80fa342ed1ea095168a3f116637bd1030d39c9ff38dc04e54ef7c521e01fc95", size = 806659, upload-time = "2025-09-19T00:37:46.684Z" }, - { url = "https://files.pythonhosted.org/packages/2b/71/ea62dbeb55d9e6905c7b5a49f75615ea1373afcad95830047e4e310db979/regex-2025.9.18-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4d97071c0ba40f0cf2a93ed76e660654c399a0a04ab7d85472239460f3da84b", size = 871701, upload-time = "2025-09-19T00:37:48.882Z" }, - { url = "https://files.pythonhosted.org/packages/6a/90/fbe9dedb7dad24a3a4399c0bae64bfa932ec8922a0a9acf7bc88db30b161/regex-2025.9.18-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0ac936537ad87cef9e0e66c5144484206c1354224ee811ab1519a32373e411f3", size = 913742, upload-time = "2025-09-19T00:37:51.015Z" }, - { url = "https://files.pythonhosted.org/packages/f0/1c/47e4a8c0e73d41eb9eb9fdeba3b1b810110a5139a2526e82fd29c2d9f867/regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dec57f96d4def58c422d212d414efe28218d58537b5445cf0c33afb1b4768571", size = 811117, upload-time = "2025-09-19T00:37:52.686Z" }, - { url = "https://files.pythonhosted.org/packages/2a/da/435f29fddfd015111523671e36d30af3342e8136a889159b05c1d9110480/regex-2025.9.18-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48317233294648bf7cd068857f248e3a57222259a5304d32c7552e2284a1b2ad", size = 794647, upload-time = "2025-09-19T00:37:54.626Z" }, - { url = "https://files.pythonhosted.org/packages/23/66/df5e6dcca25c8bc57ce404eebc7342310a0d218db739d7882c9a2b5974a3/regex-2025.9.18-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:274687e62ea3cf54846a9b25fc48a04459de50af30a7bd0b61a9e38015983494", size = 866747, upload-time = "2025-09-19T00:37:56.367Z" }, - { url = "https://files.pythonhosted.org/packages/82/42/94392b39b531f2e469b2daa40acf454863733b674481fda17462a5ffadac/regex-2025.9.18-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a78722c86a3e7e6aadf9579e3b0ad78d955f2d1f1a8ca4f67d7ca258e8719d4b", size = 853434, upload-time = "2025-09-19T00:37:58.39Z" }, - { url = "https://files.pythonhosted.org/packages/a8/f8/dcc64c7f7bbe58842a8f89622b50c58c3598fbbf4aad0a488d6df2c699f1/regex-2025.9.18-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:06104cd203cdef3ade989a1c45b6215bf42f8b9dd705ecc220c173233f7cba41", size = 798024, upload-time = "2025-09-19T00:38:00.397Z" }, - { url = "https://files.pythonhosted.org/packages/20/8d/edf1c5d5aa98f99a692313db813ec487732946784f8f93145e0153d910e5/regex-2025.9.18-cp314-cp314t-win32.whl", hash = "sha256:2e1eddc06eeaffd249c0adb6fafc19e2118e6308c60df9db27919e96b5656096", size = 273029, upload-time = "2025-09-19T00:38:02.383Z" }, - { url = "https://files.pythonhosted.org/packages/a7/24/02d4e4f88466f17b145f7ea2b2c11af3a942db6222429c2c146accf16054/regex-2025.9.18-cp314-cp314t-win_amd64.whl", hash = "sha256:8620d247fb8c0683ade51217b459cb4a1081c0405a3072235ba43a40d355c09a", size = 282680, upload-time = "2025-09-19T00:38:04.102Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a3/c64894858aaaa454caa7cc47e2f225b04d3ed08ad649eacf58d45817fad2/regex-2025.9.18-cp314-cp314t-win_arm64.whl", hash = "sha256:b7531a8ef61de2c647cdf68b3229b071e46ec326b3138b2180acb4275f470b01", size = 273034, upload-time = "2025-09-19T00:38:05.807Z" }, +version = "2026.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/3a246dbf05666918bd3664d9d787f84a9108f6f43cc953a077e4a7dfdb7e/regex-2026.4.4.tar.gz", hash = "sha256:e08270659717f6973523ce3afbafa53515c4dc5dcad637dc215b6fd50f689423", size = 416000, upload-time = "2026-04-03T20:56:28.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/59/fd98f8fd54b3feaa76a855324c676c17668c5a1121ec91b7ec96b01bf865/regex-2026.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:74fa82dcc8143386c7c0392e18032009d1db715c25f4ba22d23dc2e04d02a20f", size = 489403, upload-time = "2026-04-03T20:52:39.742Z" }, + { url = "https://files.pythonhosted.org/packages/6c/64/d0f222f68e3579d50babf0e4fcc9c9639ef0587fecc00b15e1e46bfc32fa/regex-2026.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a85b620a388d6c9caa12189233109e236b3da3deffe4ff11b84ae84e218a274f", size = 291208, upload-time = "2026-04-03T20:52:42.943Z" }, + { url = "https://files.pythonhosted.org/packages/16/7f/3fab9709b0b0060ba81a04b8a107b34147cd14b9c5551b772154d6505504/regex-2026.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2895506ebe32cc63eeed8f80e6eae453171cfccccab35b70dc3129abec35a5b8", size = 289214, upload-time = "2026-04-03T20:52:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/14/bc/f5dcf04fd462139dcd75495c02eee22032ef741cfa151386a39c3f5fc9b5/regex-2026.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6780f008ee81381c737634e75c24e5a6569cc883c4f8e37a37917ee79efcafd9", size = 785505, upload-time = "2026-04-03T20:52:46.35Z" }, + { url = "https://files.pythonhosted.org/packages/37/36/8a906e216d5b4de7ec3788c1d589b45db40c1c9580cd7b326835cfc976d4/regex-2026.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:88e9b048345c613f253bea4645b2fe7e579782b82cac99b1daad81e29cc2ed8e", size = 852129, upload-time = "2026-04-03T20:52:48.661Z" }, + { url = "https://files.pythonhosted.org/packages/a5/bb/bad2d79be0917a6ef31f5e0f161d9265cb56fd90a3ae1d2e8d991882a48b/regex-2026.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:be061028481186ba62a0f4c5f1cc1e3d5ab8bce70c89236ebe01023883bc903b", size = 899578, upload-time = "2026-04-03T20:52:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b9/7cd0ceb58cd99c70806241636640ae15b4a3fe62e22e9b99afa67a0d7965/regex-2026.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d2228c02b368d69b724c36e96d3d1da721561fb9cc7faa373d7bf65e07d75cb5", size = 793634, upload-time = "2026-04-03T20:52:53Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fb/c58e3ea40ed183806ccbac05c29a3e8c2f88c1d3a66ed27860d5cad7c62d/regex-2026.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0540e5b733618a2f84e9cb3e812c8afa82e151ca8e19cf6c4e95c5a65198236f", size = 786210, upload-time = "2026-04-03T20:52:54.713Z" }, + { url = "https://files.pythonhosted.org/packages/54/a9/53790fc7a6c948a7be2bc7214fd9cabdd0d1ba561b0f401c91f4ff0357f0/regex-2026.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cf9b1b2e692d4877880388934ac746c99552ce6bf40792a767fd42c8c99f136d", size = 769930, upload-time = "2026-04-03T20:52:56.825Z" }, + { url = "https://files.pythonhosted.org/packages/e3/3c/29ca44729191c79f5476538cd0fa04fa2553b3c45508519ecea4c7afa8f6/regex-2026.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:011bb48bffc1b46553ac704c975b3348717f4e4aa7a67522b51906f99da1820c", size = 774892, upload-time = "2026-04-03T20:52:58.934Z" }, + { url = "https://files.pythonhosted.org/packages/3e/db/6ae74ef8a4cfead341c367e4eed45f71fb1aaba35827a775eed4f1ba4f74/regex-2026.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8512fcdb43f1bf18582698a478b5ab73f9c1667a5b7548761329ef410cd0a760", size = 848816, upload-time = "2026-04-03T20:53:00.684Z" }, + { url = "https://files.pythonhosted.org/packages/53/9a/f7f2c1c6b610d7c6de1c3dc5951effd92c324b1fde761af2044b4721020f/regex-2026.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:867bddc63109a0276f5a31999e4c8e0eb7bbbad7d6166e28d969a2c1afeb97f9", size = 758363, upload-time = "2026-04-03T20:53:02.155Z" }, + { url = "https://files.pythonhosted.org/packages/dd/55/e5386d393bbf8b43c8b084703a46d635e7b2bdc6e0f5909a2619ea1125f1/regex-2026.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1b9a00b83f3a40e09859c78920571dcb83293c8004079653dd22ec14bbfa98c7", size = 837122, upload-time = "2026-04-03T20:53:03.727Z" }, + { url = "https://files.pythonhosted.org/packages/01/da/cc78710ea2e60b10bacfcc9beb18c67514200ab03597b3b2b319995785c2/regex-2026.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e355be718caf838aa089870259cf1776dc2a4aa980514af9d02c59544d9a8b22", size = 782140, upload-time = "2026-04-03T20:53:05.608Z" }, + { url = "https://files.pythonhosted.org/packages/a2/5f/c7bcba41529105d6c2ca7080ecab7184cd00bee2e1ad1fdea80e618704ea/regex-2026.4.4-cp310-cp310-win32.whl", hash = "sha256:33bfda9684646d323414df7abe5692c61d297dbb0530b28ec66442e768813c59", size = 266225, upload-time = "2026-04-03T20:53:07.342Z" }, + { url = "https://files.pythonhosted.org/packages/eb/26/a745729c2c49354ec4f4bce168f29da932ca01b4758227686cc16c7dde1b/regex-2026.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:0709f22a56798457ae317bcce42aacee33c680068a8f14097430d9f9ba364bee", size = 278393, upload-time = "2026-04-03T20:53:08.65Z" }, + { url = "https://files.pythonhosted.org/packages/87/8b/4327eeb9dbb4b098ebecaf02e9f82b79b6077beeb54c43d9a0660cf7c44c/regex-2026.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:ee9627de8587c1a22201cb16d0296ab92b4df5cdcb5349f4e9744d61db7c7c98", size = 270470, upload-time = "2026-04-03T20:53:10.018Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7a/617356cbecdb452812a5d42f720d6d5096b360d4a4c1073af700ea140ad2/regex-2026.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4c36a85b00fadb85db9d9e90144af0a980e1a3d2ef9cd0f8a5bef88054657c6", size = 489415, upload-time = "2026-04-03T20:53:11.645Z" }, + { url = "https://files.pythonhosted.org/packages/20/e6/bf057227144d02e3ba758b66649e87531d744dda5f3254f48660f18ae9d8/regex-2026.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dcb5453ecf9cd58b562967badd1edbf092b0588a3af9e32ee3d05c985077ce87", size = 291205, upload-time = "2026-04-03T20:53:13.289Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3b/637181b787dd1a820ba1c712cee2b4144cd84a32dc776ca067b12b2d70c8/regex-2026.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6aa809ed4dc3706cc38594d67e641601bd2f36d5555b2780ff074edfcb136cf8", size = 289225, upload-time = "2026-04-03T20:53:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/05/21/bac05d806ed02cd4b39d9c8e5b5f9a2998c94c3a351b7792e80671fa5315/regex-2026.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33424f5188a7db12958246a54f59a435b6cb62c5cf9c8d71f7cc49475a5fdada", size = 792434, upload-time = "2026-04-03T20:53:17.414Z" }, + { url = "https://files.pythonhosted.org/packages/d9/17/c65d1d8ae90b772d5758eb4014e1e011bb2db353fc4455432e6cc9100df7/regex-2026.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d346fccdde28abba117cc9edc696b9518c3307fbfcb689e549d9b5979018c6d", size = 861730, upload-time = "2026-04-03T20:53:18.903Z" }, + { url = "https://files.pythonhosted.org/packages/ad/64/933321aa082a2c6ee2785f22776143ba89840189c20d3b6b1d12b6aae16b/regex-2026.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:415a994b536440f5011aa77e50a4274d15da3245e876e5c7f19da349caaedd87", size = 906495, upload-time = "2026-04-03T20:53:20.561Z" }, + { url = "https://files.pythonhosted.org/packages/01/ea/4c8d306e9c36ac22417336b1e02e7b358152c34dc379673f2d331143725f/regex-2026.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21e5eb86179b4c67b5759d452ea7c48eb135cd93308e7a260aa489ed2eb423a4", size = 799810, upload-time = "2026-04-03T20:53:22.961Z" }, + { url = "https://files.pythonhosted.org/packages/29/ce/7605048f00e1379eba89d610c7d644d8f695dc9b26d3b6ecfa3132b872ff/regex-2026.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:312ec9dd1ae7d96abd8c5a36a552b2139931914407d26fba723f9e53c8186f86", size = 774242, upload-time = "2026-04-03T20:53:25.015Z" }, + { url = "https://files.pythonhosted.org/packages/e9/77/283e0d5023fde22cd9e86190d6d9beb21590a452b195ffe00274de470691/regex-2026.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0d2b28aa1354c7cd7f71b7658c4326f7facac106edd7f40eda984424229fd59", size = 781257, upload-time = "2026-04-03T20:53:26.918Z" }, + { url = "https://files.pythonhosted.org/packages/8b/fb/7f3b772be101373c8626ed34c5d727dcbb8abd42a7b1219bc25fd9a3cc04/regex-2026.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:349d7310eddff40429a099c08d995c6d4a4bfaf3ff40bd3b5e5cb5a5a3c7d453", size = 854490, upload-time = "2026-04-03T20:53:29.065Z" }, + { url = "https://files.pythonhosted.org/packages/85/30/56547b80f34f4dd2986e1cdd63b1712932f63b6c4ce2f79c50a6cd79d1c2/regex-2026.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e7ab63e9fe45a9ec3417509e18116b367e89c9ceb6219222a3396fa30b147f80", size = 763544, upload-time = "2026-04-03T20:53:30.917Z" }, + { url = "https://files.pythonhosted.org/packages/ac/2f/ce060fdfea8eff34a8997603532e44cdb7d1f35e3bc253612a8707a90538/regex-2026.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fe896e07a5a2462308297e515c0054e9ec2dd18dfdc9427b19900b37dfe6f40b", size = 844442, upload-time = "2026-04-03T20:53:32.463Z" }, + { url = "https://files.pythonhosted.org/packages/e5/44/810cb113096a1dacbe82789fbfab2823f79d19b7f1271acecb7009ba9b88/regex-2026.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eb59c65069498dbae3c0ef07bbe224e1eaa079825a437fb47a479f0af11f774f", size = 789162, upload-time = "2026-04-03T20:53:34.039Z" }, + { url = "https://files.pythonhosted.org/packages/20/96/9647dd7f2ecf6d9ce1fb04dfdb66910d094e10d8fe53e9c15096d8aa0bd2/regex-2026.4.4-cp311-cp311-win32.whl", hash = "sha256:2a5d273181b560ef8397c8825f2b9d57013de744da9e8257b8467e5da8599351", size = 266227, upload-time = "2026-04-03T20:53:35.601Z" }, + { url = "https://files.pythonhosted.org/packages/33/80/74e13262460530c3097ff343a17de9a34d040a5dc4de9cf3a8241faab51c/regex-2026.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:9542ccc1e689e752594309444081582f7be2fdb2df75acafea8a075108566735", size = 278399, upload-time = "2026-04-03T20:53:37.021Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/39f19f47f19dcefa3403f09d13562ca1c0fd07ab54db2bc03148f3f6b46a/regex-2026.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:b5f9fb784824a042be3455b53d0b112655686fdb7a91f88f095f3fee1e2a2a54", size = 270473, upload-time = "2026-04-03T20:53:38.633Z" }, + { url = "https://files.pythonhosted.org/packages/e5/28/b972a4d3df61e1d7bcf1b59fdb3cddef22f88b6be43f161bb41ebc0e4081/regex-2026.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c07ab8794fa929e58d97a0e1796b8b76f70943fa39df225ac9964615cf1f9d52", size = 490434, upload-time = "2026-04-03T20:53:40.219Z" }, + { url = "https://files.pythonhosted.org/packages/84/20/30041446cf6dc3e0eab344fc62770e84c23b6b68a3b657821f9f80cb69b4/regex-2026.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c785939dc023a1ce4ec09599c032cc9933d258a998d16ca6f2b596c010940eb", size = 292061, upload-time = "2026-04-03T20:53:41.862Z" }, + { url = "https://files.pythonhosted.org/packages/62/c8/3baa06d75c98c46d4cc4262b71fd2edb9062b5665e868bca57859dadf93a/regex-2026.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b1ce5c81c9114f1ce2f9288a51a8fd3aeea33a0cc440c415bf02da323aa0a76", size = 289628, upload-time = "2026-04-03T20:53:43.701Z" }, + { url = "https://files.pythonhosted.org/packages/31/87/3accf55634caad8c0acab23f5135ef7d4a21c39f28c55c816ae012931408/regex-2026.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:760ef21c17d8e6a4fe8cf406a97cf2806a4df93416ccc82fc98d25b1c20425be", size = 796651, upload-time = "2026-04-03T20:53:45.379Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0c/aaa2c83f34efedbf06f61cb1942c25f6cf1ee3b200f832c4d05f28306c2e/regex-2026.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7088fcdcb604a4417c208e2169715800d28838fefd7455fbe40416231d1d47c1", size = 865916, upload-time = "2026-04-03T20:53:47.064Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f6/8c6924c865124643e8f37823eca845dc27ac509b2ee58123685e71cd0279/regex-2026.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:07edca1ba687998968f7db5bc355288d0c6505caa7374f013d27356d93976d13", size = 912287, upload-time = "2026-04-03T20:53:49.422Z" }, + { url = "https://files.pythonhosted.org/packages/11/0e/a9f6f81013e0deaf559b25711623864970fe6a098314e374ccb1540a4152/regex-2026.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f657a7c1c6ec51b5e0ba97c9817d06b84ea5fa8d82e43b9405de0defdc2b9", size = 801126, upload-time = "2026-04-03T20:53:51.096Z" }, + { url = "https://files.pythonhosted.org/packages/71/61/3a0cc8af2dc0c8deb48e644dd2521f173f7e6513c6e195aad9aa8dd77ac5/regex-2026.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2b69102a743e7569ebee67e634a69c4cb7e59d6fa2e1aa7d3bdbf3f61435f62d", size = 776788, upload-time = "2026-04-03T20:53:52.889Z" }, + { url = "https://files.pythonhosted.org/packages/64/0b/8bb9cbf21ef7dee58e49b0fdb066a7aded146c823202e16494a36777594f/regex-2026.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dac006c8b6dda72d86ea3d1333d45147de79a3a3f26f10c1cf9287ca4ca0ac3", size = 785184, upload-time = "2026-04-03T20:53:55.627Z" }, + { url = "https://files.pythonhosted.org/packages/99/c2/d3e80e8137b25ee06c92627de4e4d98b94830e02b3e6f81f3d2e3f504cf5/regex-2026.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:50a766ee2010d504554bfb5f578ed2e066898aa26411d57e6296230627cdefa0", size = 859913, upload-time = "2026-04-03T20:53:57.249Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/9d5d876157d969c804622456ef250017ac7a8f83e0e14f903b9e6df5ce95/regex-2026.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9e2f5217648f68e3028c823df58663587c1507a5ba8419f4fdfc8a461be76043", size = 765732, upload-time = "2026-04-03T20:53:59.428Z" }, + { url = "https://files.pythonhosted.org/packages/82/80/b568935b4421388561c8ed42aff77247285d3ae3bb2a6ca22af63bae805e/regex-2026.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39d8de85a08e32632974151ba59c6e9140646dcc36c80423962b1c5c0a92e244", size = 852152, upload-time = "2026-04-03T20:54:01.505Z" }, + { url = "https://files.pythonhosted.org/packages/39/29/f0f81217e21cd998245da047405366385d5c6072048038a3d33b37a79dc0/regex-2026.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:55d9304e0e7178dfb1e106c33edf834097ddf4a890e2f676f6c5118f84390f73", size = 789076, upload-time = "2026-04-03T20:54:03.323Z" }, + { url = "https://files.pythonhosted.org/packages/49/1d/1d957a61976ab9d4e767dd4f9d04b66cc0c41c5e36cf40e2d43688b5ae6f/regex-2026.4.4-cp312-cp312-win32.whl", hash = "sha256:04bb679bc0bde8a7bfb71e991493d47314e7b98380b083df2447cda4b6edb60f", size = 266700, upload-time = "2026-04-03T20:54:05.639Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/bf575d396aeb58ea13b06ef2adf624f65b70fafef6950a80fc3da9cae3bc/regex-2026.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:db0ac18435a40a2543dbb3d21e161a6c78e33e8159bd2e009343d224bb03bb1b", size = 277768, upload-time = "2026-04-03T20:54:07.312Z" }, + { url = "https://files.pythonhosted.org/packages/c9/27/049df16ec6a6828ccd72add3c7f54b4df029669bea8e9817df6fff58be90/regex-2026.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:4ce255cc05c1947a12989c6db801c96461947adb7a59990f1360b5983fab4983", size = 270568, upload-time = "2026-04-03T20:54:09.484Z" }, + { url = "https://files.pythonhosted.org/packages/9d/83/c4373bc5f31f2cf4b66f9b7c31005bd87fe66f0dce17701f7db4ee79ee29/regex-2026.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:62f5519042c101762509b1d717b45a69c0139d60414b3c604b81328c01bd1943", size = 490273, upload-time = "2026-04-03T20:54:11.202Z" }, + { url = "https://files.pythonhosted.org/packages/46/f8/fe62afbcc3cf4ad4ac9adeaafd98aa747869ae12d3e8e2ac293d0593c435/regex-2026.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3790ba9fb5dd76715a7afe34dbe603ba03f8820764b1dc929dd08106214ed031", size = 291954, upload-time = "2026-04-03T20:54:13.412Z" }, + { url = "https://files.pythonhosted.org/packages/5a/92/4712b9fe6a33d232eeb1c189484b80c6c4b8422b90e766e1195d6e758207/regex-2026.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8fae3c6e795d7678963f2170152b0d892cf6aee9ee8afc8c45e6be38d5107fe7", size = 289487, upload-time = "2026-04-03T20:54:15.824Z" }, + { url = "https://files.pythonhosted.org/packages/88/2c/f83b93f85e01168f1070f045a42d4c937b69fdb8dd7ae82d307253f7e36e/regex-2026.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:298c3ec2d53225b3bf91142eb9691025bab610e0c0c51592dde149db679b3d17", size = 796646, upload-time = "2026-04-03T20:54:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/df/55/61a2e17bf0c4dc57e11caf8dd11771280d8aaa361785f9e3bc40d653f4a7/regex-2026.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9638791082eaf5b3ac112c587518ee78e083a11c4b28012d8fe2a0f536dfb17", size = 865904, upload-time = "2026-04-03T20:54:20.019Z" }, + { url = "https://files.pythonhosted.org/packages/45/32/1ac8ed1b5a346b5993a3d256abe0a0f03b0b73c8cc88d928537368ac65b6/regex-2026.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae3e764bd4c5ff55035dc82a8d49acceb42a5298edf6eb2fc4d328ee5dd7afae", size = 912304, upload-time = "2026-04-03T20:54:22.403Z" }, + { url = "https://files.pythonhosted.org/packages/26/47/2ee5c613ab546f0eddebf9905d23e07beb933416b1246c2d8791d01979b4/regex-2026.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffa81f81b80047ba89a3c69ae6a0f78d06f4a42ce5126b0eb2a0a10ad44e0b2e", size = 801126, upload-time = "2026-04-03T20:54:24.308Z" }, + { url = "https://files.pythonhosted.org/packages/75/cd/41dacd129ca9fd20bd7d02f83e0fad83e034ac8a084ec369c90f55ef37e2/regex-2026.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f56ebf9d70305307a707911b88469213630aba821e77de7d603f9d2f0730687d", size = 776772, upload-time = "2026-04-03T20:54:26.319Z" }, + { url = "https://files.pythonhosted.org/packages/89/6d/5af0b588174cb5f46041fa7dd64d3fd5cd2fe51f18766703d1edc387f324/regex-2026.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:773d1dfd652bbffb09336abf890bfd64785c7463716bf766d0eb3bc19c8b7f27", size = 785228, upload-time = "2026-04-03T20:54:28.387Z" }, + { url = "https://files.pythonhosted.org/packages/b7/3b/f5a72b7045bd59575fc33bf1345f156fcfd5a8484aea6ad84b12c5a82114/regex-2026.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d51d20befd5275d092cdffba57ded05f3c436317ee56466c8928ac32d960edaf", size = 860032, upload-time = "2026-04-03T20:54:30.641Z" }, + { url = "https://files.pythonhosted.org/packages/39/a4/72a317003d6fcd7a573584a85f59f525dfe8f67e355ca74eb6b53d66a5e2/regex-2026.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0a51cdb3c1e9161154f976cb2bef9894bc063ac82f31b733087ffb8e880137d0", size = 765714, upload-time = "2026-04-03T20:54:32.789Z" }, + { url = "https://files.pythonhosted.org/packages/25/1e/5672e16f34dbbcb2560cc7e6a2fbb26dfa8b270711e730101da4423d3973/regex-2026.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae5266a82596114e41fb5302140e9630204c1b5f325c770bec654b95dd54b0aa", size = 852078, upload-time = "2026-04-03T20:54:34.546Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0d/c813f0af7c6cc7ed7b9558bac2e5120b60ad0fa48f813e4d4bd55446f214/regex-2026.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c882cd92ec68585e9c1cf36c447ec846c0d94edd706fe59e0c198e65822fd23b", size = 789181, upload-time = "2026-04-03T20:54:36.642Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6d/a344608d1adbd2a95090ddd906cec09a11be0e6517e878d02a5123e0917f/regex-2026.4.4-cp313-cp313-win32.whl", hash = "sha256:05568c4fbf3cb4fa9e28e3af198c40d3237cf6041608a9022285fe567ec3ad62", size = 266690, upload-time = "2026-04-03T20:54:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/31/07/54049f89b46235ca6f45cd6c88668a7050e77d4a15555e47dd40fde75263/regex-2026.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:3384df51ed52db0bea967e21458ab0a414f67cdddfd94401688274e55147bb81", size = 277733, upload-time = "2026-04-03T20:54:40.11Z" }, + { url = "https://files.pythonhosted.org/packages/0e/21/61366a8e20f4d43fb597708cac7f0e2baadb491ecc9549b4980b2be27d16/regex-2026.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:acd38177bd2c8e69a411d6521760806042e244d0ef94e2dd03ecdaa8a3c99427", size = 270565, upload-time = "2026-04-03T20:54:41.883Z" }, + { url = "https://files.pythonhosted.org/packages/f1/1e/3a2b9672433bef02f5d39aa1143ca2c08f311c1d041c464a42be9ae648dc/regex-2026.4.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f94a11a9d05afcfcfa640e096319720a19cc0c9f7768e1a61fceee6a3afc6c7c", size = 494126, upload-time = "2026-04-03T20:54:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/4e/4b/c132a4f4fe18ad3340d89fcb56235132b69559136036b845be3c073142ed/regex-2026.4.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:36bcb9d6d1307ab629edc553775baada2aefa5c50ccc0215fbfd2afcfff43141", size = 293882, upload-time = "2026-04-03T20:54:45.41Z" }, + { url = "https://files.pythonhosted.org/packages/f4/5f/eaa38092ce7a023656280f2341dbbd4ad5f05d780a70abba7bb4f4bea54c/regex-2026.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261c015b3e2ed0919157046d768774ecde57f03d8fa4ba78d29793447f70e717", size = 292334, upload-time = "2026-04-03T20:54:47.051Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f6/dd38146af1392dac33db7074ab331cec23cced3759167735c42c5460a243/regex-2026.4.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c228cf65b4a54583763645dcd73819b3b381ca8b4bb1b349dee1c135f4112c07", size = 811691, upload-time = "2026-04-03T20:54:49.074Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f0/dc54c2e69f5eeec50601054998ec3690d5344277e782bd717e49867c1d29/regex-2026.4.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dd2630faeb6876fb0c287f664d93ddce4d50cd46c6e88e60378c05c9047e08ca", size = 871227, upload-time = "2026-04-03T20:54:51.035Z" }, + { url = "https://files.pythonhosted.org/packages/a1/af/cb16bd5dc61621e27df919a4449bbb7e5a1034c34d307e0a706e9cc0f3e3/regex-2026.4.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a50ab11b7779b849472337191f3a043e27e17f71555f98d0092fa6d73364520", size = 917435, upload-time = "2026-04-03T20:54:52.994Z" }, + { url = "https://files.pythonhosted.org/packages/5c/71/8b260897f22996b666edd9402861668f45a2ca259f665ac029e6104a2d7d/regex-2026.4.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0734f63afe785138549fbe822a8cfeaccd1bae814c5057cc0ed5b9f2de4fc883", size = 816358, upload-time = "2026-04-03T20:54:54.884Z" }, + { url = "https://files.pythonhosted.org/packages/1c/60/775f7f72a510ef238254906c2f3d737fc80b16ca85f07d20e318d2eea894/regex-2026.4.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4ee50606cb1967db7e523224e05f32089101945f859928e65657a2cbb3d278b", size = 785549, upload-time = "2026-04-03T20:54:57.01Z" }, + { url = "https://files.pythonhosted.org/packages/58/42/34d289b3627c03cf381e44da534a0021664188fa49ba41513da0b4ec6776/regex-2026.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6c1818f37be3ca02dcb76d63f2c7aaba4b0dc171b579796c6fbe00148dfec6b1", size = 801364, upload-time = "2026-04-03T20:54:58.981Z" }, + { url = "https://files.pythonhosted.org/packages/fc/20/f6ecf319b382a8f1ab529e898b222c3f30600fcede7834733c26279e7465/regex-2026.4.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f5bfc2741d150d0be3e4a0401a5c22b06e60acb9aa4daa46d9e79a6dcd0f135b", size = 866221, upload-time = "2026-04-03T20:55:00.88Z" }, + { url = "https://files.pythonhosted.org/packages/92/6a/9f16d3609d549bd96d7a0b2aee1625d7512ba6a03efc01652149ef88e74d/regex-2026.4.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:504ffa8a03609a087cad81277a629b6ce884b51a24bd388a7980ad61748618ff", size = 772530, upload-time = "2026-04-03T20:55:03.213Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f6/aa9768bc96a4c361ac96419fbaf2dcdc33970bb813df3ba9b09d5d7b6d96/regex-2026.4.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70aadc6ff12e4b444586e57fc30771f86253f9f0045b29016b9605b4be5f7dfb", size = 856989, upload-time = "2026-04-03T20:55:05.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b4/c671db3556be2473ae3e4bb7a297c518d281452871501221251ea4ecba57/regex-2026.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f4f83781191007b6ef43b03debc35435f10cad9b96e16d147efe84a1d48bdde4", size = 803241, upload-time = "2026-04-03T20:55:07.162Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5c/83e3b1d89fa4f6e5a1bc97b4abd4a9a97b3c1ac7854164f694f5f0ba98a0/regex-2026.4.4-cp313-cp313t-win32.whl", hash = "sha256:e014a797de43d1847df957c0a2a8e861d1c17547ee08467d1db2c370b7568baa", size = 269921, upload-time = "2026-04-03T20:55:09.62Z" }, + { url = "https://files.pythonhosted.org/packages/28/07/077c387121f42cdb4d92b1301133c0d93b5709d096d1669ab847dda9fe2e/regex-2026.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:b15b88b0d52b179712632832c1d6e58e5774f93717849a41096880442da41ab0", size = 281240, upload-time = "2026-04-03T20:55:11.521Z" }, + { url = "https://files.pythonhosted.org/packages/9d/22/ead4a4abc7c59a4d882662aa292ca02c8b617f30b6e163bc1728879e9353/regex-2026.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:586b89cdadf7d67bf86ae3342a4dcd2b8d70a832d90c18a0ae955105caf34dbe", size = 272440, upload-time = "2026-04-03T20:55:13.365Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f5/ed97c2dc47b5fbd4b73c0d7d75f9ebc8eca139f2bbef476bba35f28c0a77/regex-2026.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2da82d643fa698e5e5210e54af90181603d5853cf469f5eedf9bfc8f59b4b8c7", size = 490343, upload-time = "2026-04-03T20:55:15.241Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/de4828a7385ec166d673a5790ad06ac48cdaa98bc0960108dd4b9cc1aef7/regex-2026.4.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:54a1189ad9d9357760557c91103d5e421f0a2dabe68a5cdf9103d0dcf4e00752", size = 291909, upload-time = "2026-04-03T20:55:17.558Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d6/5cfbfc97f3201a4d24b596a77957e092030dcc4205894bc035cedcfce62f/regex-2026.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:76d67d5afb1fe402d10a6403bae668d000441e2ab115191a804287d53b772951", size = 289692, upload-time = "2026-04-03T20:55:20.561Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/f2212d9fd56fe897e36d0110ba30ba2d247bd6410c5bd98499c7e5a1e1f2/regex-2026.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e7cd3e4ee8d80447a83bbc9ab0c8459781fa77087f856c3e740d7763be0df27f", size = 796979, upload-time = "2026-04-03T20:55:22.56Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e3/a016c12675fbac988a60c7e1c16e67823ff0bc016beb27bd7a001dbdabc6/regex-2026.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e19e18c568d2866d8b6a6dfad823db86193503f90823a8f66689315ba28fbe8", size = 866744, upload-time = "2026-04-03T20:55:24.646Z" }, + { url = "https://files.pythonhosted.org/packages/af/a4/0b90ca4cf17adc3cb43de80ec71018c37c88ad64987e8d0d481a95ca60b5/regex-2026.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7698a6f38730fd1385d390d1ed07bb13dce39aa616aca6a6d89bea178464b9a4", size = 911613, upload-time = "2026-04-03T20:55:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3b/2b3dac0b82d41ab43aa87c6ecde63d71189d03fe8854b8ca455a315edac3/regex-2026.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:173a66f3651cdb761018078e2d9487f4cf971232c990035ec0eb1cdc6bf929a9", size = 800551, upload-time = "2026-04-03T20:55:29.532Z" }, + { url = "https://files.pythonhosted.org/packages/25/fe/5365eb7aa0e753c4b5957815c321519ecab033c279c60e1b1ae2367fa810/regex-2026.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa7922bbb2cc84fa062d37723f199d4c0cd200245ce269c05db82d904db66b83", size = 776911, upload-time = "2026-04-03T20:55:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b3/7fb0072156bba065e3b778a7bc7b0a6328212be5dd6a86fd207e0c4f2dab/regex-2026.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:59f67cd0a0acaf0e564c20bbd7f767286f23e91e2572c5703bf3e56ea7557edb", size = 785751, upload-time = "2026-04-03T20:55:33.797Z" }, + { url = "https://files.pythonhosted.org/packages/02/1a/9f83677eb699273e56e858f7bd95acdbee376d42f59e8bfca2fd80d79df3/regex-2026.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:475e50f3f73f73614f7cba5524d6de49dee269df00272a1b85e3d19f6d498465", size = 860484, upload-time = "2026-04-03T20:55:35.745Z" }, + { url = "https://files.pythonhosted.org/packages/3b/7a/93937507b61cfcff8b4c5857f1b452852b09f741daa9acae15c971d8554e/regex-2026.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a1c0c7d67b64d85ac2e1879923bad2f08a08f3004055f2f406ef73c850114bd4", size = 765939, upload-time = "2026-04-03T20:55:37.972Z" }, + { url = "https://files.pythonhosted.org/packages/86/ea/81a7f968a351c6552b1670ead861e2a385be730ee28402233020c67f9e0f/regex-2026.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:1371c2ccbb744d66ee63631cc9ca12aa233d5749972626b68fe1a649dd98e566", size = 851417, upload-time = "2026-04-03T20:55:39.92Z" }, + { url = "https://files.pythonhosted.org/packages/4c/7e/323c18ce4b5b8f44517a36342961a0306e931e499febbd876bb149d900f0/regex-2026.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59968142787042db793348a3f5b918cf24ced1f23247328530e063f89c128a95", size = 789056, upload-time = "2026-04-03T20:55:42.303Z" }, + { url = "https://files.pythonhosted.org/packages/c0/af/e7510f9b11b1913b0cd44eddb784b2d650b2af6515bfce4cffcc5bfd1d38/regex-2026.4.4-cp314-cp314-win32.whl", hash = "sha256:59efe72d37fd5a91e373e5146f187f921f365f4abc1249a5ab446a60f30dd5f8", size = 272130, upload-time = "2026-04-03T20:55:44.995Z" }, + { url = "https://files.pythonhosted.org/packages/9a/51/57dae534c915e2d3a21490e88836fa2ae79dde3b66255ecc0c0a155d2c10/regex-2026.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:e0aab3ff447845049d676827d2ff714aab4f73f340e155b7de7458cf53baa5a4", size = 280992, upload-time = "2026-04-03T20:55:47.316Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5e/abaf9f4c3792e34edb1434f06717fae2b07888d85cb5cec29f9204931bf8/regex-2026.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:a7a5bb6aa0cf62208bb4fa079b0c756734f8ad0e333b425732e8609bd51ee22f", size = 273563, upload-time = "2026-04-03T20:55:49.273Z" }, + { url = "https://files.pythonhosted.org/packages/ff/06/35da85f9f217b9538b99cbb170738993bcc3b23784322decb77619f11502/regex-2026.4.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:97850d0638391bdc7d35dc1c1039974dcb921eaafa8cc935ae4d7f272b1d60b3", size = 494191, upload-time = "2026-04-03T20:55:51.258Z" }, + { url = "https://files.pythonhosted.org/packages/54/5b/1bc35f479eef8285c4baf88d8c002023efdeebb7b44a8735b36195486ae7/regex-2026.4.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ee7337f88f2a580679f7bbfe69dc86c043954f9f9c541012f49abc554a962f2e", size = 293877, upload-time = "2026-04-03T20:55:53.214Z" }, + { url = "https://files.pythonhosted.org/packages/39/5b/f53b9ad17480b3ddd14c90da04bfb55ac6894b129e5dea87bcaf7d00e336/regex-2026.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7429f4e6192c11d659900c0648ba8776243bf396ab95558b8c51a345afeddde6", size = 292410, upload-time = "2026-04-03T20:55:55.736Z" }, + { url = "https://files.pythonhosted.org/packages/bb/56/52377f59f60a7c51aa4161eecf0b6032c20b461805aca051250da435ffc9/regex-2026.4.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4f10fbd5dd13dcf4265b4cc07d69ca70280742870c97ae10093e3d66000359", size = 811831, upload-time = "2026-04-03T20:55:57.802Z" }, + { url = "https://files.pythonhosted.org/packages/dd/63/8026310bf066f702a9c361f83a8c9658f3fe4edb349f9c1e5d5273b7c40c/regex-2026.4.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a152560af4f9742b96f3827090f866eeec5becd4765c8e0d3473d9d280e76a5a", size = 871199, upload-time = "2026-04-03T20:56:00.333Z" }, + { url = "https://files.pythonhosted.org/packages/20/9f/a514bbb00a466dbb506d43f187a04047f7be1505f10a9a15615ead5080ee/regex-2026.4.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54170b3e95339f415d54651f97df3bff7434a663912f9358237941bbf9143f55", size = 917649, upload-time = "2026-04-03T20:56:02.445Z" }, + { url = "https://files.pythonhosted.org/packages/cb/6b/8399f68dd41a2030218839b9b18360d79b86d22b9fab5ef477c7f23ca67c/regex-2026.4.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07f190d65f5a72dcb9cf7106bfc3d21e7a49dd2879eda2207b683f32165e4d99", size = 816388, upload-time = "2026-04-03T20:56:04.595Z" }, + { url = "https://files.pythonhosted.org/packages/1e/9c/103963f47c24339a483b05edd568594c2be486188f688c0170fd504b2948/regex-2026.4.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9a2741ce5a29d3c84b0b94261ba630ab459a1b847a0d6beca7d62d188175c790", size = 785746, upload-time = "2026-04-03T20:56:07.13Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ee/7f6054c0dec0cee3463c304405e4ff42e27cff05bf36fcb34be549ab17bd/regex-2026.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b26c30df3a28fd9793113dac7385a4deb7294a06c0f760dd2b008bd49a9139bc", size = 801483, upload-time = "2026-04-03T20:56:09.365Z" }, + { url = "https://files.pythonhosted.org/packages/30/c2/51d3d941cf6070dc00c3338ecf138615fc3cce0421c3df6abe97a08af61a/regex-2026.4.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:421439d1bee44b19f4583ccf42670ca464ffb90e9fdc38d37f39d1ddd1e44f1f", size = 866331, upload-time = "2026-04-03T20:56:12.039Z" }, + { url = "https://files.pythonhosted.org/packages/16/e8/76d50dcc122ac33927d939f350eebcfe3dbcbda96913e03433fc36de5e63/regex-2026.4.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b40379b53ecbc747fd9bdf4a0ea14eb8188ca1bd0f54f78893a39024b28f4863", size = 772673, upload-time = "2026-04-03T20:56:14.558Z" }, + { url = "https://files.pythonhosted.org/packages/a5/6e/5f6bf75e20ea6873d05ba4ec78378c375cbe08cdec571c83fbb01606e563/regex-2026.4.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:08c55c13d2eef54f73eeadc33146fb0baaa49e7335eb1aff6ae1324bf0ddbe4a", size = 857146, upload-time = "2026-04-03T20:56:16.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/33/3c76d9962949e487ebba353a18e89399f292287204ac8f2f4cfc3a51c233/regex-2026.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9776b85f510062f5a75ef112afe5f494ef1635607bf1cc220c1391e9ac2f5e81", size = 803463, upload-time = "2026-04-03T20:56:18.923Z" }, + { url = "https://files.pythonhosted.org/packages/19/eb/ef32dcd2cb69b69bc0c3e55205bce94a7def48d495358946bc42186dcccc/regex-2026.4.4-cp314-cp314t-win32.whl", hash = "sha256:385edaebde5db5be103577afc8699fea73a0e36a734ba24870be7ffa61119d74", size = 275709, upload-time = "2026-04-03T20:56:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/a0/86/c291bf740945acbf35ed7dbebf8e2eea2f3f78041f6bd7cdab80cb274dc0/regex-2026.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:5d354b18839328927832e2fa5f7c95b7a3ccc39e7a681529e1685898e6436d45", size = 285622, upload-time = "2026-04-03T20:56:23.641Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e7/ec846d560ae6a597115153c02ca6138a7877a1748b2072d9521c10a93e58/regex-2026.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:af0384cb01a33600c49505c27c6c57ab0b27bf84a74e28524c92ca897ebdac9d", size = 275773, upload-time = "2026-04-03T20:56:26.07Z" }, ] [[package]] name = "requests" -version = "2.32.5" +version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -2722,44 +2398,48 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, ] [[package]] name = "rich" -version = "14.1.0" +version = "15.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, ] [[package]] name = "safetensors" -version = "0.6.2" +version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ac/cc/738f3011628920e027a11754d9cae9abec1aed00f7ae860abbf843755233/safetensors-0.6.2.tar.gz", hash = "sha256:43ff2aa0e6fa2dc3ea5524ac7ad93a9839256b8703761e76e2d0b2a3fa4f15d9", size = 197968, upload-time = "2025-08-08T13:13:58.654Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/b1/3f5fd73c039fc87dba3ff8b5d528bfc5a32b597fea8e7a6a4800343a17c7/safetensors-0.6.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9c85ede8ec58f120bad982ec47746981e210492a6db876882aa021446af8ffba", size = 454797, upload-time = "2025-08-08T13:13:52.066Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c9/bb114c158540ee17907ec470d01980957fdaf87b4aa07914c24eba87b9c6/safetensors-0.6.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d6675cf4b39c98dbd7d940598028f3742e0375a6b4d4277e76beb0c35f4b843b", size = 432206, upload-time = "2025-08-08T13:13:50.931Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8e/f70c34e47df3110e8e0bb268d90db8d4be8958a54ab0336c9be4fe86dac8/safetensors-0.6.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d2d2b3ce1e2509c68932ca03ab8f20570920cd9754b05063d4368ee52833ecd", size = 473261, upload-time = "2025-08-08T13:13:41.259Z" }, - { url = "https://files.pythonhosted.org/packages/2a/f5/be9c6a7c7ef773e1996dc214e73485286df1836dbd063e8085ee1976f9cb/safetensors-0.6.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:93de35a18f46b0f5a6a1f9e26d91b442094f2df02e9fd7acf224cfec4238821a", size = 485117, upload-time = "2025-08-08T13:13:43.506Z" }, - { url = "https://files.pythonhosted.org/packages/c9/55/23f2d0a2c96ed8665bf17a30ab4ce5270413f4d74b6d87dd663258b9af31/safetensors-0.6.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89a89b505f335640f9120fac65ddeb83e40f1fd081cb8ed88b505bdccec8d0a1", size = 616154, upload-time = "2025-08-08T13:13:45.096Z" }, - { url = "https://files.pythonhosted.org/packages/98/c6/affb0bd9ce02aa46e7acddbe087912a04d953d7a4d74b708c91b5806ef3f/safetensors-0.6.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4d0d0b937e04bdf2ae6f70cd3ad51328635fe0e6214aa1fc811f3b576b3bda", size = 520713, upload-time = "2025-08-08T13:13:46.25Z" }, - { url = "https://files.pythonhosted.org/packages/fe/5d/5a514d7b88e310c8b146e2404e0dc161282e78634d9358975fd56dfd14be/safetensors-0.6.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8045db2c872db8f4cbe3faa0495932d89c38c899c603f21e9b6486951a5ecb8f", size = 485835, upload-time = "2025-08-08T13:13:49.373Z" }, - { url = "https://files.pythonhosted.org/packages/7a/7b/4fc3b2ba62c352b2071bea9cfbad330fadda70579f617506ae1a2f129cab/safetensors-0.6.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:81e67e8bab9878bb568cffbc5f5e655adb38d2418351dc0859ccac158f753e19", size = 521503, upload-time = "2025-08-08T13:13:47.651Z" }, - { url = "https://files.pythonhosted.org/packages/5a/50/0057e11fe1f3cead9254315a6c106a16dd4b1a19cd247f7cc6414f6b7866/safetensors-0.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0e4d029ab0a0e0e4fdf142b194514695b1d7d3735503ba700cf36d0fc7136ce", size = 652256, upload-time = "2025-08-08T13:13:53.167Z" }, - { url = "https://files.pythonhosted.org/packages/e9/29/473f789e4ac242593ac1656fbece6e1ecd860bb289e635e963667807afe3/safetensors-0.6.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:fa48268185c52bfe8771e46325a1e21d317207bcabcb72e65c6e28e9ffeb29c7", size = 747281, upload-time = "2025-08-08T13:13:54.656Z" }, - { url = "https://files.pythonhosted.org/packages/68/52/f7324aad7f2df99e05525c84d352dc217e0fa637a4f603e9f2eedfbe2c67/safetensors-0.6.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:d83c20c12c2d2f465997c51b7ecb00e407e5f94d7dec3ea0cc11d86f60d3fde5", size = 692286, upload-time = "2025-08-08T13:13:55.884Z" }, - { url = "https://files.pythonhosted.org/packages/ad/fe/cad1d9762868c7c5dc70c8620074df28ebb1a8e4c17d4c0cb031889c457e/safetensors-0.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d944cea65fad0ead848b6ec2c37cc0b197194bec228f8020054742190e9312ac", size = 655957, upload-time = "2025-08-08T13:13:57.029Z" }, - { url = "https://files.pythonhosted.org/packages/59/a7/e2158e17bbe57d104f0abbd95dff60dda916cf277c9f9663b4bf9bad8b6e/safetensors-0.6.2-cp38-abi3-win32.whl", hash = "sha256:cab75ca7c064d3911411461151cb69380c9225798a20e712b102edda2542ddb1", size = 308926, upload-time = "2025-08-08T13:14:01.095Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c3/c0be1135726618dc1e28d181b8c442403d8dbb9e273fd791de2d4384bcdd/safetensors-0.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:c7b214870df923cbc1593c3faee16bec59ea462758699bd3fee399d00aac072c", size = 320192, upload-time = "2025-08-08T13:13:59.467Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/29/9c/6e74567782559a63bd040a236edca26fd71bc7ba88de2ef35d75df3bca5e/safetensors-0.7.0.tar.gz", hash = "sha256:07663963b67e8bd9f0b8ad15bb9163606cd27cc5a1b96235a50d8369803b96b0", size = 200878, upload-time = "2025-11-19T15:18:43.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/47/aef6c06649039accf914afef490268e1067ed82be62bcfa5b7e886ad15e8/safetensors-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c82f4d474cf725255d9e6acf17252991c3c8aac038d6ef363a4bf8be2f6db517", size = 467781, upload-time = "2025-11-19T15:18:35.84Z" }, + { url = "https://files.pythonhosted.org/packages/e8/00/374c0c068e30cd31f1e1b46b4b5738168ec79e7689ca82ee93ddfea05109/safetensors-0.7.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:94fd4858284736bb67a897a41608b5b0c2496c9bdb3bf2af1fa3409127f20d57", size = 447058, upload-time = "2025-11-19T15:18:34.416Z" }, + { url = "https://files.pythonhosted.org/packages/f1/06/578ffed52c2296f93d7fd2d844cabfa92be51a587c38c8afbb8ae449ca89/safetensors-0.7.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e07d91d0c92a31200f25351f4acb2bc6aff7f48094e13ebb1d0fb995b54b6542", size = 491748, upload-time = "2025-11-19T15:18:09.79Z" }, + { url = "https://files.pythonhosted.org/packages/ae/33/1debbbb70e4791dde185edb9413d1fe01619255abb64b300157d7f15dddd/safetensors-0.7.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8469155f4cb518bafb4acf4865e8bb9d6804110d2d9bdcaa78564b9fd841e104", size = 503881, upload-time = "2025-11-19T15:18:16.145Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1c/40c2ca924d60792c3be509833df711b553c60effbd91da6f5284a83f7122/safetensors-0.7.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54bef08bf00a2bff599982f6b08e8770e09cc012d7bba00783fc7ea38f1fb37d", size = 623463, upload-time = "2025-11-19T15:18:21.11Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3a/13784a9364bd43b0d61eef4bea2845039bc2030458b16594a1bd787ae26e/safetensors-0.7.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42cb091236206bb2016d245c377ed383aa7f78691748f3bb6ee1bfa51ae2ce6a", size = 532855, upload-time = "2025-11-19T15:18:25.719Z" }, + { url = "https://files.pythonhosted.org/packages/a0/60/429e9b1cb3fc651937727befe258ea24122d9663e4d5709a48c9cbfceecb/safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac7252938f0696ddea46f5e855dd3138444e82236e3be475f54929f0c510d48", size = 507152, upload-time = "2025-11-19T15:18:33.023Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a8/4b45e4e059270d17af60359713ffd83f97900d45a6afa73aaa0d737d48b6/safetensors-0.7.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1d060c70284127fa805085d8f10fbd0962792aed71879d00864acda69dbab981", size = 541856, upload-time = "2025-11-19T15:18:31.075Z" }, + { url = "https://files.pythonhosted.org/packages/06/87/d26d8407c44175d8ae164a95b5a62707fcc445f3c0c56108e37d98070a3d/safetensors-0.7.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cdab83a366799fa730f90a4ebb563e494f28e9e92c4819e556152ad55e43591b", size = 674060, upload-time = "2025-11-19T15:18:37.211Z" }, + { url = "https://files.pythonhosted.org/packages/11/f5/57644a2ff08dc6325816ba7217e5095f17269dada2554b658442c66aed51/safetensors-0.7.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:672132907fcad9f2aedcb705b2d7b3b93354a2aec1b2f706c4db852abe338f85", size = 771715, upload-time = "2025-11-19T15:18:38.689Z" }, + { url = "https://files.pythonhosted.org/packages/86/31/17883e13a814bd278ae6e266b13282a01049b0c81341da7fd0e3e71a80a3/safetensors-0.7.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:5d72abdb8a4d56d4020713724ba81dac065fedb7f3667151c4a637f1d3fb26c0", size = 714377, upload-time = "2025-11-19T15:18:40.162Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d8/0c8a7dc9b41dcac53c4cbf9df2b9c83e0e0097203de8b37a712b345c0be5/safetensors-0.7.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0f6d66c1c538d5a94a73aa9ddca8ccc4227e6c9ff555322ea40bdd142391dd4", size = 677368, upload-time = "2025-11-19T15:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/05/e5/cb4b713c8a93469e3c5be7c3f8d77d307e65fe89673e731f5c2bfd0a9237/safetensors-0.7.0-cp38-abi3-win32.whl", hash = "sha256:c74af94bf3ac15ac4d0f2a7c7b4663a15f8c2ab15ed0fc7531ca61d0835eccba", size = 326423, upload-time = "2025-11-19T15:18:45.74Z" }, + { url = "https://files.pythonhosted.org/packages/5d/e6/ec8471c8072382cb91233ba7267fd931219753bb43814cbc71757bfd4dab/safetensors-0.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:d1239932053f56f3456f32eb9625590cc7582e905021f94636202a864d470755", size = 341380, upload-time = "2025-11-19T15:18:44.427Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6a/4d08d89a6fcbe905c5ae68b8b34f0791850882fc19782d0d02c65abbdf3b/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4729811a6640d019a4b7ba8638ee2fd21fa5ca8c7e7bdf0fed62068fcaac737", size = 492430, upload-time = "2025-11-19T15:18:11.884Z" }, + { url = "https://files.pythonhosted.org/packages/dd/29/59ed8152b30f72c42d00d241e58eaca558ae9dbfa5695206e2e0f54c7063/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12f49080303fa6bb424b362149a12949dfbbf1e06811a88f2307276b0c131afd", size = 503977, upload-time = "2025-11-19T15:18:17.523Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/4811bfec67fa260e791369b16dab105e4bae82686120554cc484064e22b4/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0071bffba4150c2f46cae1432d31995d77acfd9f8db598b5d1a2ce67e8440ad2", size = 623890, upload-time = "2025-11-19T15:18:22.666Z" }, + { url = "https://files.pythonhosted.org/packages/58/5b/632a58724221ef03d78ab65062e82a1010e1bef8e8e0b9d7c6d7b8044841/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:473b32699f4200e69801bf5abf93f1a4ecd432a70984df164fc22ccf39c4a6f3", size = 531885, upload-time = "2025-11-19T15:18:27.146Z" }, ] [[package]] @@ -2768,8 +2448,10 @@ version = "1.7.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "joblib" }, - { name = "numpy" }, - { name = "scipy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "threadpoolctl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } @@ -2810,8 +2492,13 @@ wheels = [ name = "scipy" version = "1.13.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ae/00/48c2f661e2816ccf2ecd77982f6605b2950afe60f60a52b4cbbc2504aa8f/scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", size = 57210720, upload-time = "2024-05-23T03:29:26.079Z" } wheels = [ @@ -2836,36 +2523,100 @@ wheels = [ ] [[package]] -name = "seaborn" -version = "0.13.2" +name = "scipy" +version = "1.17.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "matplotlib" }, - { name = "numpy" }, - { name = "pandas" }, +resolution-markers = [ + "python_full_version >= '3.14' and platform_machine != 's390x'", + "python_full_version >= '3.14' and platform_machine == 's390x'", + "python_full_version == '3.13.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914, upload-time = "2024-01-25T13:21:49.598Z" }, +dependencies = [ + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, ] [[package]] name = "sentence-transformers" -version = "5.1.1" +version = "5.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, - { name = "pillow" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "scikit-learn" }, - { name = "scipy" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "torch" }, { name = "tqdm" }, { name = "transformers" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/47/7d61a19ba7e6b5f36f0ffff5bbf032a1c1913612caac611e12383069eda0/sentence_transformers-5.1.1.tar.gz", hash = "sha256:8af3f844b2ecf9a6c2dfeafc2c02938a87f61202b54329d70dfd7dfd7d17a84e", size = 374434, upload-time = "2025-09-22T11:28:27.54Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/68/7f98c221940ce783b492ad6140384daf2e2918cd7175009d6a362c22b9ee/sentence_transformers-5.4.1.tar.gz", hash = "sha256:436bcb1182a0ff42a8fb2b1c43498a70d0a75b688d182f2cd0d1dd115af61ddc", size = 428910, upload-time = "2026-04-14T13:34:59.006Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/21/4670d03ab8587b0ab6f7d5fa02a95c3dd6b1f39d0e40e508870201f3d76c/sentence_transformers-5.1.1-py3-none-any.whl", hash = "sha256:5ed544629eafe89ca668a8910ebff96cf0a9c5254ec14b05c66c086226c892fd", size = 486574, upload-time = "2025-09-22T11:28:26.311Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d9/3a9b6f2ccdedc9dc00fe37b2fc58f58f8efbff44565cf4bf39d8568bb13a/sentence_transformers-5.4.1-py3-none-any.whl", hash = "sha256:a6d640fc363849b63affb8e140e9d328feabab86f83d58ac3e16b1c28140b790", size = 571311, upload-time = "2026-04-14T13:34:57.731Z" }, ] [[package]] @@ -2961,8 +2712,8 @@ wheels = [ [[package]] name = "smart-chunker" -version = "0.0.4" -source = { git = "https://github.com/AsphodelRem/smart_chunker.git#09c9c6de21d2f83e79a722d9fa93a15bf33768f3" } +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "accelerate" }, { name = "nltk" }, @@ -2971,17 +2722,9 @@ dependencies = [ { name = "torch" }, { name = "transformers" }, ] - -[[package]] -name = "smart-open" -version = "7.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/16/be/bf2d60280a9d7fac98ece2150a22538fa4332cda67d04d9618c8406f791e/smart_open-7.3.1.tar.gz", hash = "sha256:b33fee8dffd206f189d5e704106a8723afb4210d2ff47e0e1f7fbe436187a990", size = 51405, upload-time = "2025-09-08T10:03:53.726Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/c3/4c1307e29c9ab0d17df8d1e9600381147c138014a5415787f7d26ba0b943/smart_chunker-0.0.5.tar.gz", hash = "sha256:935a62daa60bafd31c3c7496d751f723fbfb289c42ee7aa85355589d14726804", size = 17297, upload-time = "2026-03-24T10:22:59.35Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/d9/460cf1d58945dd771c228c29d5664f431dfc4060d3d092fed40546b11472/smart_open-7.3.1-py3-none-any.whl", hash = "sha256:e243b2e7f69d6c0c96dd763d6fbbedbb4e0e4fc6d74aa007acc5b018d523858c", size = 61722, upload-time = "2025-09-08T10:03:52.02Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/9e9b63d8c4c20a8f39aa2c5ddaf2721f4242f95c7de7d1ef7e07bde18f41/smart_chunker-0.0.5-py3-none-any.whl", hash = "sha256:18dc6cff5f1da0fa13990883ed823a40395faced826db21f9cd2327318d36e1a", size = 9841, upload-time = "2026-03-24T10:22:57.75Z" }, ] [[package]] @@ -3002,51 +2745,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763, upload-time = "2020-04-17T15:50:31.878Z" }, ] -[[package]] -name = "statsmodels" -version = "0.14.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "packaging" }, - { name = "pandas" }, - { name = "patsy" }, - { name = "scipy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/64/cc/8c1bf59bf8203dea1bf2ea811cfe667d7bcc6909c83d8afb02b08e30f50b/statsmodels-0.14.5.tar.gz", hash = "sha256:de260e58cccfd2ceddf835b55a357233d6ca853a1aa4f90f7553a52cc71c6ddf", size = 20525016, upload-time = "2025-07-07T12:14:23.195Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2c/55b2a5d10c1a211ecab3f792021d2581bbe1c5ca0a1059f6715dddc6899d/statsmodels-0.14.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9fc2b5cdc0c95cba894849651fec1fa1511d365e3eb72b0cc75caac44077cd48", size = 10058241, upload-time = "2025-07-07T12:13:16.286Z" }, - { url = "https://files.pythonhosted.org/packages/66/d9/6967475805de06691e951072d05e40e3f1c71b6221bb92401193ee19bd2a/statsmodels-0.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b8d96b0bbaeabd3a557c35cc7249baa9cfbc6dd305c32a9f2cbdd7f46c037e7f", size = 9734017, upload-time = "2025-07-07T12:05:08.498Z" }, - { url = "https://files.pythonhosted.org/packages/df/a8/803c280419a7312e2472969fe72cf461c1210a27770a662cbe3b5cd7c6fe/statsmodels-0.14.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:145bc39b2cb201efb6c83cc3f2163c269e63b0d4809801853dec6f440bd3bc37", size = 10459677, upload-time = "2025-07-07T14:21:51.809Z" }, - { url = "https://files.pythonhosted.org/packages/a1/25/edf20acbd670934b02cd9344e29c9a03ce040122324b3491bb075ae76b2d/statsmodels-0.14.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7c14fb2617bb819fb2532e1424e1da2b98a3419a80e95f33365a72d437d474e", size = 10678631, upload-time = "2025-07-07T14:22:05.496Z" }, - { url = "https://files.pythonhosted.org/packages/64/22/8b1e38310272e766abd6093607000a81827420a3348f09eff08a9e54cbaf/statsmodels-0.14.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1e9742d8a5ac38a3bfc4b7f4b0681903920f20cbbf466d72b1fd642033846108", size = 10699273, upload-time = "2025-07-07T14:22:19.487Z" }, - { url = "https://files.pythonhosted.org/packages/d1/6f/6de51f1077b7cef34611f1d6721392ea170153251b4d977efcf6d100f779/statsmodels-0.14.5-cp310-cp310-win_amd64.whl", hash = "sha256:1cab9e6fce97caf4239cdb2df375806937da5d0b7ba2699b13af33a07f438464", size = 9644785, upload-time = "2025-07-07T12:05:20.927Z" }, - { url = "https://files.pythonhosted.org/packages/14/30/fd49902b30416b828de763e161c0d6e2cc04d119ae4fbdd3f3b43dc8f1be/statsmodels-0.14.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b7091a8442076c708c926de3603653a160955e80a2b6d931475b7bb8ddc02e5", size = 10053330, upload-time = "2025-07-07T12:07:39.689Z" }, - { url = "https://files.pythonhosted.org/packages/ca/c1/2654541ff6f5790d01d1e5ba36405fde873f4a854f473e90b4fe56b37333/statsmodels-0.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:128872be8f3208f4446d91ea9e4261823902fc7997fee7e1a983eb62fd3b7c6e", size = 9735555, upload-time = "2025-07-07T12:13:28.935Z" }, - { url = "https://files.pythonhosted.org/packages/ce/da/6ebb64d0db4e86c0d2d9cde89e03247702da0ab191789f7813d4f9a348da/statsmodels-0.14.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2ad5aee04ae7196c429df2174df232c057e478c5fa63193d01c8ec9aae04d31", size = 10307522, upload-time = "2025-07-07T14:22:32.853Z" }, - { url = "https://files.pythonhosted.org/packages/67/49/ac803ca093ec3845184a752a91cd84511245e1f97103b15cfe32794a3bb0/statsmodels-0.14.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f402fc793458dd6d96e099acb44cd1de1428565bf7ef3030878a8daff091f08a", size = 10474665, upload-time = "2025-07-07T14:22:46.011Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c8/ae82feb00582f4814fac5d2cb3ec32f93866b413cf5878b2fe93688ec63c/statsmodels-0.14.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:26c028832730aebfbfd4e7501694e1f9ad31ec8536e776716673f4e7afd4059a", size = 10713120, upload-time = "2025-07-07T14:23:00.067Z" }, - { url = "https://files.pythonhosted.org/packages/05/ac/4276459ea71aa46e2967ea283fc88ee5631c11f29a06787e16cf4aece1b8/statsmodels-0.14.5-cp311-cp311-win_amd64.whl", hash = "sha256:ec56f771d9529cdc17ed2fb2a950d100b6e83a7c5372aae8ac5bb065c474b856", size = 9640980, upload-time = "2025-07-07T12:05:33.085Z" }, - { url = "https://files.pythonhosted.org/packages/5f/a5/fcc4f5f16355660ce7a1742e28a43e3a9391b492fc4ff29fdd6893e81c05/statsmodels-0.14.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:37e7364a39f9aa3b51d15a208c2868b90aadb8412f868530f5cba9197cb00eaa", size = 10042891, upload-time = "2025-07-07T12:13:41.671Z" }, - { url = "https://files.pythonhosted.org/packages/1c/6f/db0cf5efa48277ac6218d9b981c8fd5e63c4c43e0d9d65015fdc38eed0ef/statsmodels-0.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4263d7f4d0f1d5ac6eb4db22e1ee34264a14d634b9332c975c9d9109b6b46e12", size = 9698912, upload-time = "2025-07-07T12:07:54.674Z" }, - { url = "https://files.pythonhosted.org/packages/4a/93/4ddc3bc4a59c51e6a57c49df1b889882c40d9e141e855b3517f6a8de3232/statsmodels-0.14.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86224f6e36f38486e471e75759d241fe2912d8bc25ab157d54ee074c6aedbf45", size = 10237801, upload-time = "2025-07-07T14:23:12.593Z" }, - { url = "https://files.pythonhosted.org/packages/66/de/dc6bf2f6e8c8eb4c5815560ebdbdf2d69a767bc0f65fde34bc086cf5b36d/statsmodels-0.14.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3dd760a6fa80cd5e0371685c697bb9c2c0e6e1f394d975e596a1e6d0bbb9372", size = 10424154, upload-time = "2025-07-07T14:23:25.365Z" }, - { url = "https://files.pythonhosted.org/packages/16/4f/2d5a8d14bebdf2b03b3ea89b8c6a2c837bb406ba5b7a41add8bd303bce29/statsmodels-0.14.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6264fb00e02f858b86bd01ef2dc05055a71d4a0cc7551b9976b07b0f0e6cf24f", size = 10652915, upload-time = "2025-07-07T14:23:39.337Z" }, - { url = "https://files.pythonhosted.org/packages/df/4c/2feda3a9f0e17444a84ba5398ada6a4d2e1b8f832760048f04e2b8ea0c41/statsmodels-0.14.5-cp312-cp312-win_amd64.whl", hash = "sha256:b2ed065bfbaf8bb214c7201656df840457c2c8c65e1689e3eb09dc7440f9c61c", size = 9611236, upload-time = "2025-07-07T12:08:06.794Z" }, - { url = "https://files.pythonhosted.org/packages/84/fd/4c374108cf108b3130240a5b45847a61f70ddf973429044a81a05189b046/statsmodels-0.14.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:906263134dd1a640e55ecb01fda4a9be7b9e08558dba9e4c4943a486fdb0c9c8", size = 10013958, upload-time = "2025-07-07T14:35:01.04Z" }, - { url = "https://files.pythonhosted.org/packages/5a/36/bf3d7f0e36acd3ba9ec0babd79ace25506b6872780cbd710fb7cd31f0fa2/statsmodels-0.14.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9118f76344f77cffbb3a9cbcff8682b325be5eed54a4b3253e09da77a74263d3", size = 9674243, upload-time = "2025-07-07T12:08:22.571Z" }, - { url = "https://files.pythonhosted.org/packages/90/ce/a55a6f37b5277683ceccd965a5828b24672bbc427db6b3969ae0b0fc29fb/statsmodels-0.14.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dc4ee159070557c9a6c000625d85f653de437772fe7086857cff68f501afe45", size = 10219521, upload-time = "2025-07-07T14:23:52.646Z" }, - { url = "https://files.pythonhosted.org/packages/1e/48/973da1ee8bc0743519759e74c3615b39acdc3faf00e0a0710f8c856d8c9d/statsmodels-0.14.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a085d47c8ef5387279a991633883d0e700de2b0acc812d7032d165888627bef", size = 10453538, upload-time = "2025-07-07T14:24:06.959Z" }, - { url = "https://files.pythonhosted.org/packages/c7/d6/18903fb707afd31cf1edaec5201964dbdacb2bfae9a22558274647a7c88f/statsmodels-0.14.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f866b2ebb2904b47c342d00def83c526ef2eb1df6a9a3c94ba5fe63d0005aec", size = 10681584, upload-time = "2025-07-07T14:24:21.038Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/80df1bbbfcdc50bff4152f43274420fa9856d56e234d160d6206eb1f5827/statsmodels-0.14.5-cp313-cp313-win_amd64.whl", hash = "sha256:2a06bca03b7a492f88c8106103ab75f1a5ced25de90103a89f3a287518017939", size = 9604641, upload-time = "2025-07-07T12:08:36.23Z" }, - { url = "https://files.pythonhosted.org/packages/fd/6c/0fb40a89d715412160097c6f3387049ed88c9bd866c8838a8852c705ae2f/statsmodels-0.14.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:07c4dad25bbb15864a31b4917a820f6d104bdc24e5ddadcda59027390c3bed9e", size = 10211256, upload-time = "2025-10-30T13:46:58.591Z" }, - { url = "https://files.pythonhosted.org/packages/88/4a/e36fe8b19270ab3e80df357da924c6c029cab0fb9a0fbd28aaf49341707d/statsmodels-0.14.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:babb067c852e966c2c933b79dbb5d0240919d861941a2ef6c0e13321c255528d", size = 10110933, upload-time = "2025-10-30T13:47:11.774Z" }, - { url = "https://files.pythonhosted.org/packages/8a/bf/1b7e7b1a6c09a88a9c5c9e60622c050dfd08af11c2e6d4a42dbc71b32ee1/statsmodels-0.14.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:110194b137286173cc676d7bad0119a197778de6478fc6cbdc3b33571165ac1e", size = 10253981, upload-time = "2025-10-30T16:32:22.399Z" }, - { url = "https://files.pythonhosted.org/packages/b8/d0/f95da95524bdd99613923ca61a3036d1308cee1290e5e8acb89f51736a8c/statsmodels-0.14.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c8a9c384a60c80731b278e7fd18764364c8817f4995b13a175d636f967823d1", size = 10460450, upload-time = "2025-10-30T16:32:44.985Z" }, - { url = "https://files.pythonhosted.org/packages/28/bb/59e7be0271be264b7b541baf3973f97747740950bfd5115de731f63da8ab/statsmodels-0.14.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:557df3a870a57248df744fdfcc444ecbc5bdbf1c042b8a8b5d8e3e797830dc2a", size = 10694060, upload-time = "2025-10-30T16:33:07.656Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c0/b28d0fd0347ea38d3610052f479e4b922eb33bb8790817f93cd89e6e08ba/statsmodels-0.14.5-cp314-cp314-win_amd64.whl", hash = "sha256:95af7a9c4689d514f4341478b891f867766f3da297f514b8c4adf08f4fa61d03", size = 9648961, upload-time = "2025-10-30T13:47:24.303Z" }, -] - [[package]] name = "sympy" version = "1.14.0" @@ -3179,54 +2877,54 @@ wheels = [ [[package]] name = "torch" -version = "2.8.0" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, + { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, { name = "filelock" }, { name = "fsspec" }, { name = "jinja2" }, { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "networkx", version = "3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, + { name = "setuptools" }, { name = "sympy" }, - { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "triton", marker = "sys_platform == 'linux'" }, { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/63/28/110f7274254f1b8476c561dada127173f994afa2b1ffc044efb773c15650/torch-2.8.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:0be92c08b44009d4131d1ff7a8060d10bafdb7ddcb7359ef8d8c5169007ea905", size = 102052793, upload-time = "2025-08-06T14:53:15.852Z" }, - { url = "https://files.pythonhosted.org/packages/70/1c/58da560016f81c339ae14ab16c98153d51c941544ae568da3cb5b1ceb572/torch-2.8.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:89aa9ee820bb39d4d72b794345cccef106b574508dd17dbec457949678c76011", size = 888025420, upload-time = "2025-08-06T14:54:18.014Z" }, - { url = "https://files.pythonhosted.org/packages/70/87/f69752d0dd4ba8218c390f0438130c166fa264a33b7025adb5014b92192c/torch-2.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e8e5bf982e87e2b59d932769938b698858c64cc53753894be25629bdf5cf2f46", size = 241363614, upload-time = "2025-08-06T14:53:31.496Z" }, - { url = "https://files.pythonhosted.org/packages/ef/d6/e6d4c57e61c2b2175d3aafbfb779926a2cfd7c32eeda7c543925dceec923/torch-2.8.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:a3f16a58a9a800f589b26d47ee15aca3acf065546137fc2af039876135f4c760", size = 73611154, upload-time = "2025-08-06T14:53:10.919Z" }, - { url = "https://files.pythonhosted.org/packages/8f/c4/3e7a3887eba14e815e614db70b3b529112d1513d9dae6f4d43e373360b7f/torch-2.8.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:220a06fd7af8b653c35d359dfe1aaf32f65aa85befa342629f716acb134b9710", size = 102073391, upload-time = "2025-08-06T14:53:20.937Z" }, - { url = "https://files.pythonhosted.org/packages/5a/63/4fdc45a0304536e75a5e1b1bbfb1b56dd0e2743c48ee83ca729f7ce44162/torch-2.8.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c12fa219f51a933d5f80eeb3a7a5d0cbe9168c0a14bbb4055f1979431660879b", size = 888063640, upload-time = "2025-08-06T14:55:05.325Z" }, - { url = "https://files.pythonhosted.org/packages/84/57/2f64161769610cf6b1c5ed782bd8a780e18a3c9d48931319f2887fa9d0b1/torch-2.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:8c7ef765e27551b2fbfc0f41bcf270e1292d9bf79f8e0724848b1682be6e80aa", size = 241366752, upload-time = "2025-08-06T14:53:38.692Z" }, - { url = "https://files.pythonhosted.org/packages/a4/5e/05a5c46085d9b97e928f3f037081d3d2b87fb4b4195030fc099aaec5effc/torch-2.8.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:5ae0524688fb6707c57a530c2325e13bb0090b745ba7b4a2cd6a3ce262572916", size = 73621174, upload-time = "2025-08-06T14:53:25.44Z" }, - { url = "https://files.pythonhosted.org/packages/49/0c/2fd4df0d83a495bb5e54dca4474c4ec5f9c62db185421563deeb5dabf609/torch-2.8.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e2fab4153768d433f8ed9279c8133a114a034a61e77a3a104dcdf54388838705", size = 101906089, upload-time = "2025-08-06T14:53:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/99/a8/6acf48d48838fb8fe480597d98a0668c2beb02ee4755cc136de92a0a956f/torch-2.8.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2aca0939fb7e4d842561febbd4ffda67a8e958ff725c1c27e244e85e982173c", size = 887913624, upload-time = "2025-08-06T14:56:44.33Z" }, - { url = "https://files.pythonhosted.org/packages/af/8a/5c87f08e3abd825c7dfecef5a0f1d9aa5df5dd0e3fd1fa2f490a8e512402/torch-2.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:2f4ac52f0130275d7517b03a33d2493bab3693c83dcfadf4f81688ea82147d2e", size = 241326087, upload-time = "2025-08-06T14:53:46.503Z" }, - { url = "https://files.pythonhosted.org/packages/be/66/5c9a321b325aaecb92d4d1855421e3a055abd77903b7dab6575ca07796db/torch-2.8.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:619c2869db3ada2c0105487ba21b5008defcc472d23f8b80ed91ac4a380283b0", size = 73630478, upload-time = "2025-08-06T14:53:57.144Z" }, - { url = "https://files.pythonhosted.org/packages/10/4e/469ced5a0603245d6a19a556e9053300033f9c5baccf43a3d25ba73e189e/torch-2.8.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2b2f96814e0345f5a5aed9bf9734efa913678ed19caf6dc2cddb7930672d6128", size = 101936856, upload-time = "2025-08-06T14:54:01.526Z" }, - { url = "https://files.pythonhosted.org/packages/16/82/3948e54c01b2109238357c6f86242e6ecbf0c63a1af46906772902f82057/torch-2.8.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:65616ca8ec6f43245e1f5f296603e33923f4c30f93d65e103d9e50c25b35150b", size = 887922844, upload-time = "2025-08-06T14:55:50.78Z" }, - { url = "https://files.pythonhosted.org/packages/e3/54/941ea0a860f2717d86a811adf0c2cd01b3983bdd460d0803053c4e0b8649/torch-2.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:659df54119ae03e83a800addc125856effda88b016dfc54d9f65215c3975be16", size = 241330968, upload-time = "2025-08-06T14:54:45.293Z" }, - { url = "https://files.pythonhosted.org/packages/de/69/8b7b13bba430f5e21d77708b616f767683629fc4f8037564a177d20f90ed/torch-2.8.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:1a62a1ec4b0498930e2543535cf70b1bef8c777713de7ceb84cd79115f553767", size = 73915128, upload-time = "2025-08-06T14:54:34.769Z" }, - { url = "https://files.pythonhosted.org/packages/15/0e/8a800e093b7f7430dbaefa80075aee9158ec22e4c4fc3c1a66e4fb96cb4f/torch-2.8.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:83c13411a26fac3d101fe8035a6b0476ae606deb8688e904e796a3534c197def", size = 102020139, upload-time = "2025-08-06T14:54:39.047Z" }, - { url = "https://files.pythonhosted.org/packages/4a/15/5e488ca0bc6162c86a33b58642bc577c84ded17c7b72d97e49b5833e2d73/torch-2.8.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:8f0a9d617a66509ded240add3754e462430a6c1fc5589f86c17b433dd808f97a", size = 887990692, upload-time = "2025-08-06T14:56:18.286Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a8/6a04e4b54472fc5dba7ca2341ab219e529f3c07b6941059fbf18dccac31f/torch-2.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a7242b86f42be98ac674b88a4988643b9bc6145437ec8f048fea23f72feb5eca", size = 241603453, upload-time = "2025-08-06T14:55:22.945Z" }, - { url = "https://files.pythonhosted.org/packages/04/6e/650bb7f28f771af0cb791b02348db8b7f5f64f40f6829ee82aa6ce99aabe/torch-2.8.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:7b677e17f5a3e69fdef7eb3b9da72622f8d322692930297e4ccb52fefc6c8211", size = 73632395, upload-time = "2025-08-06T14:55:28.645Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f2/c1690994afe461aae2d0cac62251e6802a703dec0a6c549c02ecd0de92a9/torch-2.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2c0d7fcfbc0c4e8bb5ebc3907cbc0c6a0da1b8f82b1fc6e14e914fa0b9baf74e", size = 80526521, upload-time = "2026-03-23T18:12:06.86Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f0/98ae802fa8c09d3149b0c8690741f3f5753c90e779bd28c9613257295945/torch-2.11.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:4cf8687f4aec3900f748d553483ef40e0ac38411c3c48d0a86a438f6d7a99b18", size = 419723025, upload-time = "2026-03-23T18:11:43.774Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1e/18a9b10b4bd34f12d4e561c52b0ae7158707b8193c6cfc0aad2b48167090/torch-2.11.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1b32ceda909818a03b112006709b02be1877240c31750a8d9c6b7bf5f2d8a6e5", size = 530589207, upload-time = "2026-03-23T18:11:23.756Z" }, + { url = "https://files.pythonhosted.org/packages/35/40/2d532e8c0e23705be9d1debce5bc37b68d59a39bda7584c26fe9668076fe/torch-2.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:b3c712ae6fb8e7a949051a953fc412fe0a6940337336c3b6f905e905dac5157f", size = 114518313, upload-time = "2026-03-23T18:11:58.281Z" }, + { url = "https://files.pythonhosted.org/packages/ae/0d/98b410492609e34a155fa8b121b55c7dca229f39636851c3a9ec20edea21/torch-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7b6a60d48062809f58595509c524b88e6ddec3ebe25833d6462eeab81e5f2ce4", size = 80529712, upload-time = "2026-03-23T18:12:02.608Z" }, + { url = "https://files.pythonhosted.org/packages/84/03/acea680005f098f79fd70c1d9d5ccc0cb4296ec2af539a0450108232fc0c/torch-2.11.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d91aac77f24082809d2c5a93f52a5f085032740a1ebc9252a7b052ef5a4fddc6", size = 419718178, upload-time = "2026-03-23T18:10:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/8c/8b/d7be22fbec9ffee6cff31a39f8750d4b3a65d349a286cf4aec74c2375662/torch-2.11.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7aa2f9bbc6d4595ba72138026b2074be1233186150e9292865e04b7a63b8c67a", size = 530604548, upload-time = "2026-03-23T18:10:03.569Z" }, + { url = "https://files.pythonhosted.org/packages/d1/bd/9912d30b68845256aabbb4a40aeefeef3c3b20db5211ccda653544ada4b6/torch-2.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:73e24aaf8f36ab90d95cd1761208b2eb70841c2a9ca1a3f9061b39fc5331b708", size = 114519675, upload-time = "2026-03-23T18:11:52.995Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8b/69e3008d78e5cee2b30183340cc425081b78afc5eff3d080daab0adda9aa/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b5866312ee6e52ea625cd211dcb97d6a2cdc1131a5f15cc0d87eec948f6dd34", size = 80606338, upload-time = "2026-03-23T18:11:34.781Z" }, + { url = "https://files.pythonhosted.org/packages/13/16/42e5915ebe4868caa6bac83a8ed59db57f12e9a61b7d749d584776ed53d5/torch-2.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f99924682ef0aa6a4ab3b1b76f40dc6e273fca09f367d15a524266db100a723f", size = 419731115, upload-time = "2026-03-23T18:11:06.944Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c9/82638ef24d7877510f83baf821f5619a61b45568ce21c0a87a91576510aa/torch-2.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0f68f4ac6d95d12e896c3b7a912b5871619542ec54d3649cf48cc1edd4dd2756", size = 530712279, upload-time = "2026-03-23T18:10:31.481Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ff/6756f1c7ee302f6d202120e0f4f05b432b839908f9071157302cedfc5232/torch-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbf39280699d1b869f55eac536deceaa1b60bd6788ba74f399cc67e60a5fab10", size = 114556047, upload-time = "2026-03-23T18:10:55.931Z" }, + { url = "https://files.pythonhosted.org/packages/87/89/5ea6722763acee56b045435fb84258db7375c48165ec8be7880ab2b281c5/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6debd97ccd3205bbb37eb806a9d8219e1139d15419982c09e23ef7d4369d18", size = 80606801, upload-time = "2026-03-23T18:10:18.649Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/8ed2173589cbfe744ed54e5a73efc107c0085ba5777ee93a5f4c1ab90553/torch-2.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:63a68fa59de8f87acc7e85a5478bb2dddbb3392b7593ec3e78827c793c4b73fd", size = 419732382, upload-time = "2026-03-23T18:08:30.835Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e1/b73f7c575a4b8f87a5928f50a1e35416b5e27295d8be9397d5293e7e8d4c/torch-2.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cc89b9b173d9adfab59fd227f0ab5e5516d9a52b658ae41d64e59d2e55a418db", size = 530711509, upload-time = "2026-03-23T18:08:47.213Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/3e3fcdd388fbe54e29fd3f991f36846ff4ac90b0d0181e9c8f7236565f82/torch-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dda3b3f52d121063a731ddb835f010dc137b920d7fec2778e52f60d8e4bf0cd", size = 114555842, upload-time = "2026-03-23T18:09:52.111Z" }, + { url = "https://files.pythonhosted.org/packages/db/38/8ac78069621b8c2b4979c2f96dc8409ef5e9c4189f6aac629189a78677ca/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8b394322f49af4362d4f80e424bcaca7efcd049619af03a4cf4501520bdf0fb4", size = 80959574, upload-time = "2026-03-23T18:10:14.214Z" }, + { url = "https://files.pythonhosted.org/packages/6d/6c/56bfb37073e7136e6dd86bfc6af7339946dd684e0ecf2155ac0eee687ae1/torch-2.11.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2658f34ce7e2dabf4ec73b45e2ca68aedad7a5be87ea756ad656eaf32bf1e1ea", size = 419732324, upload-time = "2026-03-23T18:09:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/07/f4/1b666b6d61d3394cca306ea543ed03a64aad0a201b6cd159f1d41010aeb1/torch-2.11.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:98bb213c3084cfe176302949bdc360074b18a9da7ab59ef2edc9d9f742504778", size = 530596026, upload-time = "2026-03-23T18:09:20.842Z" }, + { url = "https://files.pythonhosted.org/packages/48/6b/30d1459fa7e4b67e9e3fe1685ca1d8bb4ce7c62ef436c3a615963c6c866c/torch-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a97b94bbf62992949b4730c6cd2cc9aee7b335921ee8dc207d930f2ed09ae2db", size = 114793702, upload-time = "2026-03-23T18:09:47.304Z" }, + { url = "https://files.pythonhosted.org/packages/26/0d/8603382f61abd0db35841148ddc1ffd607bf3100b11c6e1dab6d2fc44e72/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01018087326984a33b64e04c8cb5c2795f9120e0d775ada1f6638840227b04d7", size = 80573442, upload-time = "2026-03-23T18:09:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/c7/86/7cd7c66cb9cec6be330fff36db5bd0eef386d80c031b581ec81be1d4b26c/torch-2.11.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:2bb3cc54bd0dea126b0060bb1ec9de0f9c7f7342d93d436646516b0330cd5be7", size = 419749385, upload-time = "2026-03-23T18:07:33.77Z" }, + { url = "https://files.pythonhosted.org/packages/47/e8/b98ca2d39b2e0e4730c0ee52537e488e7008025bc77ca89552ff91021f7c/torch-2.11.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4dc8b3809469b6c30b411bb8c4cad3828efd26236153d9beb6a3ec500f211a60", size = 530716756, upload-time = "2026-03-23T18:07:50.02Z" }, + { url = "https://files.pythonhosted.org/packages/78/88/d4a4cda8362f8a30d1ed428564878c3cafb0d87971fbd3947d4c84552095/torch-2.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b4e811728bd0cc58fb2b0948fe939a1ee2bf1422f6025be2fca4c7bd9d79718", size = 114552300, upload-time = "2026-03-23T18:09:05.617Z" }, + { url = "https://files.pythonhosted.org/packages/bf/46/4419098ed6d801750f26567b478fc185c3432e11e2cad712bc6b4c2ab0d0/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8245477871c3700d4370352ffec94b103cfcb737229445cf9946cddb7b2ca7cd", size = 80959460, upload-time = "2026-03-23T18:09:00.818Z" }, + { url = "https://files.pythonhosted.org/packages/fd/66/54a56a4a6ceaffb567231994a9745821d3af922a854ed33b0b3a278e0a99/torch-2.11.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:ab9a8482f475f9ba20e12db84b0e55e2f58784bdca43a854a6ccd3fd4b9f75e6", size = 419735835, upload-time = "2026-03-23T18:07:18.974Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e7/0b6665f533aa9e337662dc190425abc0af1fe3234088f4454c52393ded61/torch-2.11.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:563ed3d25542d7e7bbc5b235ccfacfeb97fb470c7fee257eae599adb8005c8a2", size = 530613405, upload-time = "2026-03-23T18:08:07.014Z" }, + { url = "https://files.pythonhosted.org/packages/cf/bf/c8d12a2c86dbfd7f40fb2f56fbf5a505ccf2d9ce131eb559dfc7c51e1a04/torch-2.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b2a43985ff5ef6ddd923bbcf99943e5f58059805787c5c9a2622bf05ca2965b0", size = 114792991, upload-time = "2026-03-23T18:08:19.216Z" }, ] [[package]] @@ -3243,53 +2941,59 @@ wheels = [ [[package]] name = "transformers" -version = "4.57.0" +version = "5.5.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "filelock" }, { name = "huggingface-hub" }, - { name = "numpy" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "packaging" }, { name = "pyyaml" }, { name = "regex" }, - { name = "requests" }, { name = "safetensors" }, { name = "tokenizers" }, { name = "tqdm" }, + { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/5c/a22c39dac2687f3fe2a6b97e2c1ae516e91cd4d3976a7a2b7c24ff2fae48/transformers-4.57.0.tar.gz", hash = "sha256:d045753f3d93f9216e693cdb168698dfd2e9d3aad1bb72579a5d60ebf1545a8b", size = 10142956, upload-time = "2025-10-03T17:03:47.177Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/1e/1e244ab2ab50a863e6b52cc55761910567fa532b69a6740f6e99c5fdbd98/transformers-5.5.4.tar.gz", hash = "sha256:2e67cadba81fc7608cc07c4dd54f524820bc3d95b1cabd0ef3db7733c4f8b82e", size = 8227649, upload-time = "2026-04-13T16:55:55.181Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/2b/4d2708ac1ff5cd708b6548f4c5812d0ae40d1c28591c4c1c762b6dbdef2d/transformers-4.57.0-py3-none-any.whl", hash = "sha256:9d7c6d098c026e40d897e017ed1f481ab803cbac041021dbc6ae6100e4949b55", size = 11990588, upload-time = "2025-10-03T17:03:43.629Z" }, + { url = "https://files.pythonhosted.org/packages/29/fb/162a66789c65e5afa3b051309240c26bf37fbc8fea285b4546ae747995a2/transformers-5.5.4-py3-none-any.whl", hash = "sha256:0bd6281b82966fe5a7a16f553ea517a9db1dee6284d7cb224dfd88fc0dd1c167", size = 10236696, upload-time = "2026-04-13T16:55:51.497Z" }, ] [[package]] name = "triton" -version = "3.4.0" +version = "3.6.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "setuptools" }, -] wheels = [ - { url = "https://files.pythonhosted.org/packages/62/ee/0ee5f64a87eeda19bbad9bc54ae5ca5b98186ed00055281fd40fb4beb10e/triton-3.4.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ff2785de9bc02f500e085420273bb5cc9c9bb767584a4aa28d6e360cec70128", size = 155430069, upload-time = "2025-07-30T19:58:21.715Z" }, - { url = "https://files.pythonhosted.org/packages/7d/39/43325b3b651d50187e591eefa22e236b2981afcebaefd4f2fc0ea99df191/triton-3.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b70f5e6a41e52e48cfc087436c8a28c17ff98db369447bcaff3b887a3ab4467", size = 155531138, upload-time = "2025-07-30T19:58:29.908Z" }, - { url = "https://files.pythonhosted.org/packages/d0/66/b1eb52839f563623d185f0927eb3530ee4d5ffe9d377cdaf5346b306689e/triton-3.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c1d84a5c0ec2c0f8e8a072d7fd150cab84a9c239eaddc6706c081bfae4eb04", size = 155560068, upload-time = "2025-07-30T19:58:37.081Z" }, - { url = "https://files.pythonhosted.org/packages/30/7b/0a685684ed5322d2af0bddefed7906674f67974aa88b0fae6e82e3b766f6/triton-3.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00be2964616f4c619193cb0d1b29a99bd4b001d7dc333816073f92cf2a8ccdeb", size = 155569223, upload-time = "2025-07-30T19:58:44.017Z" }, - { url = "https://files.pythonhosted.org/packages/20/63/8cb444ad5cdb25d999b7d647abac25af0ee37d292afc009940c05b82dda0/triton-3.4.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7936b18a3499ed62059414d7df563e6c163c5e16c3773678a3ee3d417865035d", size = 155659780, upload-time = "2025-07-30T19:58:51.171Z" }, + { url = "https://files.pythonhosted.org/packages/44/ba/b1b04f4b291a3205d95ebd24465de0e5bf010a2df27a4e58a9b5f039d8f2/triton-3.6.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c723cfb12f6842a0ae94ac307dba7e7a44741d720a40cf0e270ed4a4e3be781", size = 175972180, upload-time = "2026-01-20T16:15:53.664Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f7/f1c9d3424ab199ac53c2da567b859bcddbb9c9e7154805119f8bd95ec36f/triton-3.6.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6550fae429e0667e397e5de64b332d1e5695b73650ee75a6146e2e902770bea", size = 188105201, upload-time = "2026-01-20T16:00:29.272Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2c/96f92f3c60387e14cc45aed49487f3486f89ea27106c1b1376913c62abe4/triton-3.6.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49df5ef37379c0c2b5c0012286f80174fcf0e073e5ade1ca9a86c36814553651", size = 176081190, upload-time = "2026-01-20T16:16:00.523Z" }, + { url = "https://files.pythonhosted.org/packages/e0/12/b05ba554d2c623bffa59922b94b0775673de251f468a9609bc9e45de95e9/triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3", size = 188214640, upload-time = "2026-01-20T16:00:35.869Z" }, + { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243, upload-time = "2026-01-20T16:16:07.857Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521, upload-time = "2026-01-20T16:16:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087, upload-time = "2026-01-20T16:16:18.989Z" }, + { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, + { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577, upload-time = "2026-01-20T16:16:25.426Z" }, + { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, + { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804, upload-time = "2026-01-20T16:16:31.528Z" }, + { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, ] [[package]] name = "typer" -version = "0.19.2" +version = "0.24.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "annotated-doc" }, { name = "click" }, { name = "rich" }, { name = "shellingham" }, - { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/ca/950278884e2ca20547ff3eb109478c6baf6b8cf219318e6bc4f666fad8e8/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca", size = 104755, upload-time = "2025-09-23T09:47:48.256Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9", size = 46748, upload-time = "2025-09-23T09:47:46.777Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, ] [[package]] @@ -3322,23 +3026,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, ] -[[package]] -name = "umap-learn" -version = "0.5.9.post2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numba" }, - { name = "numpy" }, - { name = "pynndescent" }, - { name = "scikit-learn" }, - { name = "scipy" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5f/ee/6bc65bd375c812026a7af63fe9d09d409382120aff25f2152f1ba12af5ec/umap_learn-0.5.9.post2.tar.gz", hash = "sha256:bdf60462d779bd074ce177a0714ced17e6d161285590fa487f3f9548dd3c31c9", size = 95441, upload-time = "2025-07-03T00:18:02.479Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/b1/c24deeda9baf1fd491aaad941ed89e0fed6c583a117fd7b79e0a33a1e6c0/umap_learn-0.5.9.post2-py3-none-any.whl", hash = "sha256:fbe51166561e0e7fab00ef3d516ac2621243b8d15cf4bef9f656d701736b16a0", size = 90146, upload-time = "2025-07-03T00:18:01.042Z" }, -] - [[package]] name = "urllib3" version = "2.5.0" @@ -3356,198 +3043,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b66 wheels = [ { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, ] - -[[package]] -name = "wrapt" -version = "1.17.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/23/bb82321b86411eb51e5a5db3fb8f8032fd30bd7c2d74bfe936136b2fa1d6/wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04", size = 53482, upload-time = "2025-08-12T05:51:44.467Z" }, - { url = "https://files.pythonhosted.org/packages/45/69/f3c47642b79485a30a59c63f6d739ed779fb4cc8323205d047d741d55220/wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2", size = 38676, upload-time = "2025-08-12T05:51:32.636Z" }, - { url = "https://files.pythonhosted.org/packages/d1/71/e7e7f5670c1eafd9e990438e69d8fb46fa91a50785332e06b560c869454f/wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c", size = 38957, upload-time = "2025-08-12T05:51:54.655Z" }, - { url = "https://files.pythonhosted.org/packages/de/17/9f8f86755c191d6779d7ddead1a53c7a8aa18bccb7cea8e7e72dfa6a8a09/wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775", size = 81975, upload-time = "2025-08-12T05:52:30.109Z" }, - { url = "https://files.pythonhosted.org/packages/f2/15/dd576273491f9f43dd09fce517f6c2ce6eb4fe21681726068db0d0467096/wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd", size = 83149, upload-time = "2025-08-12T05:52:09.316Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c4/5eb4ce0d4814521fee7aa806264bf7a114e748ad05110441cd5b8a5c744b/wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05", size = 82209, upload-time = "2025-08-12T05:52:10.331Z" }, - { url = "https://files.pythonhosted.org/packages/31/4b/819e9e0eb5c8dc86f60dfc42aa4e2c0d6c3db8732bce93cc752e604bb5f5/wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418", size = 81551, upload-time = "2025-08-12T05:52:31.137Z" }, - { url = "https://files.pythonhosted.org/packages/f8/83/ed6baf89ba3a56694700139698cf703aac9f0f9eb03dab92f57551bd5385/wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390", size = 36464, upload-time = "2025-08-12T05:53:01.204Z" }, - { url = "https://files.pythonhosted.org/packages/2f/90/ee61d36862340ad7e9d15a02529df6b948676b9a5829fd5e16640156627d/wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6", size = 38748, upload-time = "2025-08-12T05:53:00.209Z" }, - { url = "https://files.pythonhosted.org/packages/bd/c3/cefe0bd330d389c9983ced15d326f45373f4073c9f4a8c2f99b50bfea329/wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18", size = 36810, upload-time = "2025-08-12T05:52:51.906Z" }, - { url = "https://files.pythonhosted.org/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7", size = 53482, upload-time = "2025-08-12T05:51:45.79Z" }, - { url = "https://files.pythonhosted.org/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85", size = 38674, upload-time = "2025-08-12T05:51:34.629Z" }, - { url = "https://files.pythonhosted.org/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f", size = 38959, upload-time = "2025-08-12T05:51:56.074Z" }, - { url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311", size = 82376, upload-time = "2025-08-12T05:52:32.134Z" }, - { url = "https://files.pythonhosted.org/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1", size = 83604, upload-time = "2025-08-12T05:52:11.663Z" }, - { url = "https://files.pythonhosted.org/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5", size = 82782, upload-time = "2025-08-12T05:52:12.626Z" }, - { url = "https://files.pythonhosted.org/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2", size = 82076, upload-time = "2025-08-12T05:52:33.168Z" }, - { url = "https://files.pythonhosted.org/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89", size = 36457, upload-time = "2025-08-12T05:53:03.936Z" }, - { url = "https://files.pythonhosted.org/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77", size = 38745, upload-time = "2025-08-12T05:53:02.885Z" }, - { url = "https://files.pythonhosted.org/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a", size = 36806, upload-time = "2025-08-12T05:52:53.368Z" }, - { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, - { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, - { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, - { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, - { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, - { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, - { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, - { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, - { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, - { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, - { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, - { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, - { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, - { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, - { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, - { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, - { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, - { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, - { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, - { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, - { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, - { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, - { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, - { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, - { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, - { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, - { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, - { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, - { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, - { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, - { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, - { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, -] - -[[package]] -name = "yarl" -version = "1.22.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "multidict" }, - { name = "propcache" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/43/a2204825342f37c337f5edb6637040fa14e365b2fcc2346960201d457579/yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e", size = 140517, upload-time = "2025-10-06T14:08:42.494Z" }, - { url = "https://files.pythonhosted.org/packages/44/6f/674f3e6f02266428c56f704cd2501c22f78e8b2eeb23f153117cc86fb28a/yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f", size = 93495, upload-time = "2025-10-06T14:08:46.2Z" }, - { url = "https://files.pythonhosted.org/packages/b8/12/5b274d8a0f30c07b91b2f02cba69152600b47830fcfb465c108880fcee9c/yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf", size = 94400, upload-time = "2025-10-06T14:08:47.855Z" }, - { url = "https://files.pythonhosted.org/packages/e2/7f/df1b6949b1fa1aa9ff6de6e2631876ad4b73c4437822026e85d8acb56bb1/yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a", size = 347545, upload-time = "2025-10-06T14:08:49.683Z" }, - { url = "https://files.pythonhosted.org/packages/84/09/f92ed93bd6cd77872ab6c3462df45ca45cd058d8f1d0c9b4f54c1704429f/yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c", size = 319598, upload-time = "2025-10-06T14:08:51.215Z" }, - { url = "https://files.pythonhosted.org/packages/c3/97/ac3f3feae7d522cf7ccec3d340bb0b2b61c56cb9767923df62a135092c6b/yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147", size = 363893, upload-time = "2025-10-06T14:08:53.144Z" }, - { url = "https://files.pythonhosted.org/packages/06/49/f3219097403b9c84a4d079b1d7bda62dd9b86d0d6e4428c02d46ab2c77fc/yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb", size = 371240, upload-time = "2025-10-06T14:08:55.036Z" }, - { url = "https://files.pythonhosted.org/packages/35/9f/06b765d45c0e44e8ecf0fe15c9eacbbde342bb5b7561c46944f107bfb6c3/yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6", size = 346965, upload-time = "2025-10-06T14:08:56.722Z" }, - { url = "https://files.pythonhosted.org/packages/c5/69/599e7cea8d0fcb1694323b0db0dda317fa3162f7b90166faddecf532166f/yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0", size = 342026, upload-time = "2025-10-06T14:08:58.563Z" }, - { url = "https://files.pythonhosted.org/packages/95/6f/9dfd12c8bc90fea9eab39832ee32ea48f8e53d1256252a77b710c065c89f/yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda", size = 335637, upload-time = "2025-10-06T14:09:00.506Z" }, - { url = "https://files.pythonhosted.org/packages/57/2e/34c5b4eb9b07e16e873db5b182c71e5f06f9b5af388cdaa97736d79dd9a6/yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc", size = 359082, upload-time = "2025-10-06T14:09:01.936Z" }, - { url = "https://files.pythonhosted.org/packages/31/71/fa7e10fb772d273aa1f096ecb8ab8594117822f683bab7d2c5a89914c92a/yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737", size = 357811, upload-time = "2025-10-06T14:09:03.445Z" }, - { url = "https://files.pythonhosted.org/packages/26/da/11374c04e8e1184a6a03cf9c8f5688d3e5cec83ed6f31ad3481b3207f709/yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467", size = 351223, upload-time = "2025-10-06T14:09:05.401Z" }, - { url = "https://files.pythonhosted.org/packages/82/8f/e2d01f161b0c034a30410e375e191a5d27608c1f8693bab1a08b089ca096/yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea", size = 82118, upload-time = "2025-10-06T14:09:11.148Z" }, - { url = "https://files.pythonhosted.org/packages/62/46/94c76196642dbeae634c7a61ba3da88cd77bed875bf6e4a8bed037505aa6/yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca", size = 86852, upload-time = "2025-10-06T14:09:12.958Z" }, - { url = "https://files.pythonhosted.org/packages/af/af/7df4f179d3b1a6dcb9a4bd2ffbc67642746fcafdb62580e66876ce83fff4/yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b", size = 82012, upload-time = "2025-10-06T14:09:14.664Z" }, - { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, - { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, - { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, - { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, - { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, - { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, - { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, - { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, - { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, - { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, - { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, - { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, - { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, - { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, - { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, - { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, - { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, - { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, - { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, - { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, - { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, - { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, - { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, - { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, - { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, - { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, - { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, - { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, - { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, - { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, - { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, - { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, - { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, - { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, - { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, - { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, - { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, - { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, - { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, - { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, - { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, - { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, - { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, - { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, - { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, - { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, - { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, - { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, - { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, - { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, - { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, - { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, - { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, - { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, - { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, - { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, - { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, - { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, - { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, - { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, - { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, - { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, - { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, - { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, - { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, - { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, - { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, - { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, - { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, - { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, - { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, - { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, - { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, - { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, - { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, - { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, - { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, - { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, - { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, - { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, - { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, - { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, - { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, - { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, -] From 01f82bfafc1e31388ed34e41f110377b9eb455a6 Mon Sep 17 00:00:00 2001 From: AsphodelRem Date: Mon, 27 Apr 2026 00:35:01 +0700 Subject: [PATCH 42/42] Remove old test --- tests/storage/test_nano_vdb_storage.py | 126 ------------------------- 1 file changed, 126 deletions(-) delete mode 100644 tests/storage/test_nano_vdb_storage.py diff --git a/tests/storage/test_nano_vdb_storage.py b/tests/storage/test_nano_vdb_storage.py deleted file mode 100644 index 83ca4c1..0000000 --- a/tests/storage/test_nano_vdb_storage.py +++ /dev/null @@ -1,126 +0,0 @@ -from __future__ import annotations - -from typing import Dict, List, Optional - -import pytest - -from ragu.models.embedder import Embedder -from ragu.storage.types import Embedding, EmbeddingHit -from ragu.storage.vdb_storage_adapters.nano_vdb import NanoVectorDBStorage - - -class DummyEmbedder(Embedder): - def __init__(self, dim: int, vector_by_text: Dict[str, Optional[List[float]]]): - self._dim = dim - self.vector_by_text = vector_by_text - - @property - def dim(self) -> int: - return self._dim - - async def embed_text(self, text: str, **kwargs) -> list[float]: - vector = self.vector_by_text.get(text) - assert vector is not None - return vector - - -def _make_embeddings(data: Dict[str, dict], embedder: DummyEmbedder) -> List[Embedding]: - """Helper to build Embedding objects from old-style dict format.""" - result = [] - for record_id, payload in data.items(): - content = payload.get("content", "") - vector = embedder.vector_by_text.get(content) - metadata = {k: v for k, v in payload.items() if k != "content"} - result.append(Embedding(id=record_id, vector=vector, metadata=metadata)) - return result - - -@pytest.mark.asyncio -async def test_upsert_and_query_returns_expected_fields(tmp_path): - embedder = DummyEmbedder( - dim=3, - vector_by_text={ - "alpha": [1.0, 0.0, 0.0], - "beta": [0.0, 1.0, 0.0], - "query-alpha": [1.0, 0.0, 0.0], - }, - ) - storage_file = tmp_path / "vdb.json" - vdb = NanoVectorDBStorage( - embedding_dim=embedder.dim, - filename=str(storage_file), - cosine_threshold=0.2, - ) - - embeddings = _make_embeddings( - {"id-alpha": {"content": "alpha", "tag": "A"}, "id-beta": {"content": "beta", "tag": "B"}}, - embedder, - ) - await vdb.upsert(embeddings) - - query_vector = embedder.vector_by_text["query-alpha"] - results = await vdb.query(Embedding(vector=query_vector), top_k=10) - - assert len(results) == 1 - assert isinstance(results[0], EmbeddingHit) - assert results[0].id == "id-alpha" - assert "distance" in results[0].__dataclass_fields__ or hasattr(results[0], "distance") - assert results[0].metadata["tag"] == "A" - - -@pytest.mark.asyncio -async def test_upsert_empty_returns_empty_list(tmp_path): - storage_file = tmp_path / "vdb.json" - vdb = NanoVectorDBStorage(embedding_dim=3, filename=str(storage_file)) - - inserted = await vdb.upsert([]) - - assert inserted is None - - -@pytest.mark.asyncio -async def test_upsert_rejects_none_embeddings(tmp_path): - embedder = DummyEmbedder( - dim=3, - vector_by_text={ - "keep": [1.0, 0.0, 0.0], - "drop": None, - "query-keep": [1.0, 0.0, 0.0], - }, - ) - storage_file = tmp_path / "vdb.json" - vdb = NanoVectorDBStorage(embedding_dim=embedder.dim, filename=str(storage_file), cosine_threshold=0.0) - - embeddings = _make_embeddings( - {"id-keep": {"content": "keep"}, "id-drop": {"content": "drop"}}, - embedder, - ) - with pytest.raises(AssertionError): - await vdb.upsert(embeddings) - - -@pytest.mark.asyncio -async def test_delete_and_persistence_round_trip(tmp_path): - embedder = DummyEmbedder( - dim=3, - vector_by_text={ - "persist": [1.0, 0.0, 0.0], - "query": [1.0, 0.0, 0.0], - }, - ) - storage_file = tmp_path / "vdb.json" - vdb = NanoVectorDBStorage(embedding_dim=embedder.dim, filename=str(storage_file), cosine_threshold=0.0) - - embeddings = _make_embeddings({"id-persist": {"content": "persist"}}, embedder) - await vdb.upsert(embeddings) - await vdb.index_done_callback() - - reloaded = NanoVectorDBStorage(embedding_dim=embedder.dim, filename=str(storage_file), cosine_threshold=0.0) - query_vector = embedder.vector_by_text["query"] - loaded_results = await reloaded.query(Embedding(vector=query_vector), top_k=10) - assert any(r.id == "id-persist" for r in loaded_results) - - await reloaded.delete([]) - await reloaded.delete(["id-persist"]) - post_delete_results = await reloaded.query(Embedding(vector=query_vector), top_k=10) - assert all(r.id != "id-persist" for r in post_delete_results)