forked from innovacion/searchbox
Rewrite tests while keeping 97% cov
This commit is contained in:
@@ -23,15 +23,16 @@ class Client:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, backend: Backend, collection: str):
|
def __init__(self, backend: Backend, collection: str, **kwargs: str):
|
||||||
"""Initialize the client with a specific backend and collection.
|
"""Initialize the client with a specific backend and collection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
backend: The vector search backend to use
|
backend: The vector search backend to use
|
||||||
collection: Name of the collection to operate on
|
collection: Name of the collection to operate on
|
||||||
|
**kwargs: Additional keyword arguments to pass to the backend
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.engine = get_engine(backend)
|
self.engine = get_engine(backend, **kwargs)
|
||||||
self.collection = collection
|
self.collection = collection
|
||||||
|
|
||||||
async def create_index(self, size: int) -> bool:
|
async def create_index(self, size: int) -> bool:
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ class Settings(VaultSettings):
|
|||||||
"http://localhost:6333"
|
"http://localhost:6333"
|
||||||
|
|
||||||
>>> # With environment variables:
|
>>> # With environment variables:
|
||||||
>>> # export VECTOR_SEARCH_URL="https://my-qdrant.com"
|
>>> # export QDRANT_URL="https://my-qdrant.com"
|
||||||
>>> # export VECTOR_SEARCH_API_KEY="secret-key"
|
>>> # export QDRANT_API_KEY="secret-key"
|
||||||
>>> settings = Settings()
|
>>> settings = Settings()
|
||||||
>>> settings.qdrant_url # "https://my-qdrant.com"
|
>>> settings.qdrant_url # "https://my-qdrant.com"
|
||||||
>>> settings.qdrant_api_key # "secret-key"
|
>>> settings.qdrant_api_key # "secret-key"
|
||||||
|
|||||||
@@ -59,19 +59,21 @@ class Backend(StrEnum):
|
|||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_engine(
|
def get_engine(backend: Literal["qdrant"]) -> QdrantEngine: ...
|
||||||
backend: Literal[Backend.QDRANT], use_settings: Literal[True]
|
|
||||||
) -> QdrantEngine: ...
|
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_engine(
|
def get_engine(
|
||||||
backend: Literal[Backend.QDRANT], url: str, api_key: str
|
backend: Literal["qdrant"], url: str, api_key: str | None = None
|
||||||
) -> QdrantEngine: ...
|
) -> QdrantEngine: ...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def get_engine(backend: Backend, **kwargs: str) -> QdrantEngine: ...
|
||||||
|
|
||||||
|
|
||||||
@cache
|
@cache
|
||||||
def get_engine(backend: Backend, use_settings: bool = False, **kwargs):
|
def get_engine(backend: Backend, **kwargs: str):
|
||||||
"""Get a vector search engine instance for the specified backend.
|
"""Get a vector search engine instance for the specified backend.
|
||||||
|
|
||||||
This factory function creates and returns engine instances based on the
|
This factory function creates and returns engine instances based on the
|
||||||
@@ -106,7 +108,7 @@ def get_engine(backend: Backend, use_settings: bool = False, **kwargs):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if backend == Backend.QDRANT:
|
if backend == Backend.QDRANT:
|
||||||
return QdrantEngine.from_settings() if use_settings else QdrantEngine(**kwargs)
|
return QdrantEngine(**kwargs) if kwargs else QdrantEngine.from_settings()
|
||||||
elif backend == Backend.COSMOS:
|
elif backend == Backend.COSMOS:
|
||||||
raise NotImplementedError("Cosmos engine is not implemented yet")
|
raise NotImplementedError("Cosmos engine is not implemented yet")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ maintaining a consistent interface for the semantic search workflow.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from collections.abc import Sequence
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
|
|
||||||
from ..models import Chunk, Condition, SearchRow
|
from ..models import Chunk, Condition, SearchRow
|
||||||
@@ -59,7 +60,7 @@ class BaseEngine[ResponseType, ConditionType, ChunkType](ABC):
|
|||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def transform_conditions(
|
def transform_conditions(
|
||||||
self, conditions: list[Condition] | None
|
self, conditions: Sequence[Condition] | None
|
||||||
) -> ConditionType | None:
|
) -> ConditionType | None:
|
||||||
"""Transform generic conditions to backend-specific filter format.
|
"""Transform generic conditions to backend-specific filter format.
|
||||||
|
|
||||||
@@ -153,7 +154,7 @@ class BaseEngine[ResponseType, ConditionType, ChunkType](ABC):
|
|||||||
embedding: list[float],
|
embedding: list[float],
|
||||||
collection: str,
|
collection: str,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
conditions: list[Condition] | None = None,
|
conditions: Sequence[Condition] | None = None,
|
||||||
threshold: float | None = None,
|
threshold: float | None = None,
|
||||||
) -> list[SearchRow]:
|
) -> list[SearchRow]:
|
||||||
"""Perform semantic search with generic interface.
|
"""Perform semantic search with generic interface.
|
||||||
@@ -228,7 +229,7 @@ class BaseEngine[ResponseType, ConditionType, ChunkType](ABC):
|
|||||||
...
|
...
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def run_upload_chunk(self, index_name: str, chunk: ChunkType) -> bool:
|
async def _upload_chunk(self, index_name: str, chunk: ChunkType) -> bool:
|
||||||
"""Upload a backend-specific chunk to the vector index.
|
"""Upload a backend-specific chunk to the vector index.
|
||||||
|
|
||||||
This method performs the actual upload operation using the backend's
|
This method performs the actual upload operation using the backend's
|
||||||
@@ -259,4 +260,4 @@ class BaseEngine[ResponseType, ConditionType, ChunkType](ABC):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
transformed_chunk = self.transform_chunk(chunk)
|
transformed_chunk = self.transform_chunk(chunk)
|
||||||
return await self.run_upload_chunk(index_name, transformed_chunk)
|
return await self._upload_chunk(index_name, transformed_chunk)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from typing import final, override
|
|||||||
|
|
||||||
from qdrant_client import AsyncQdrantClient, models
|
from qdrant_client import AsyncQdrantClient, models
|
||||||
|
|
||||||
|
from ..config import Settings
|
||||||
from ..models import Chunk, Condition, Match, MatchAny, MatchExclude, SearchRow
|
from ..models import Chunk, Condition, Match, MatchAny, MatchExclude, SearchRow
|
||||||
from .base_engine import BaseEngine
|
from .base_engine import BaseEngine
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ class QdrantEngine(
|
|||||||
ConnectionError: If unable to establish connection to Qdrant server.
|
ConnectionError: If unable to establish connection to Qdrant server.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.client = AsyncQdrantClient(url=url, api_key=api_key)
|
self.client = AsyncQdrantClient(location=url, api_key=api_key)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_settings(cls) -> "QdrantEngine":
|
def from_settings(cls) -> "QdrantEngine":
|
||||||
@@ -77,14 +78,12 @@ class QdrantEngine(
|
|||||||
Initialized QdrantEngine instance.
|
Initialized QdrantEngine instance.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from ..config import Settings
|
|
||||||
|
|
||||||
settings = Settings() # type: ignore[reportCallArgs]
|
settings = Settings() # type: ignore[reportCallArgs]
|
||||||
return cls(url=settings.qdrant_url, api_key=settings.qdrant_api_key)
|
return cls(url=settings.qdrant_url, api_key=settings.qdrant_api_key)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def transform_conditions(
|
def transform_conditions(
|
||||||
self, conditions: list[Condition] | None
|
self, conditions: Sequence[Condition] | None
|
||||||
) -> models.Filter | None:
|
) -> models.Filter | None:
|
||||||
"""Transform generic conditions to Qdrant Filter objects.
|
"""Transform generic conditions to Qdrant Filter objects.
|
||||||
|
|
||||||
@@ -217,19 +216,18 @@ class QdrantEngine(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def run_upload_chunk(
|
async def _upload_chunk(self, index_name: str, chunk: models.PointStruct) -> bool:
|
||||||
self, index_name: str, chunk: models.PointStruct
|
|
||||||
) -> bool:
|
|
||||||
result = await self.client.upsert(
|
result = await self.client.upsert(
|
||||||
collection_name=index_name,
|
collection_name=index_name,
|
||||||
points=[chunk],
|
points=[chunk],
|
||||||
|
wait=True,
|
||||||
)
|
)
|
||||||
return result.status == models.UpdateStatus.ACKNOWLEDGED
|
return result.status == models.UpdateStatus.COMPLETED
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def transform_chunk(self, chunk: Chunk) -> models.PointStruct:
|
def transform_chunk(self, chunk: Chunk) -> models.PointStruct:
|
||||||
return models.PointStruct(
|
return models.PointStruct(
|
||||||
id=chunk.id,
|
id=str(chunk.id),
|
||||||
vector=chunk.vector,
|
vector=chunk.vector,
|
||||||
payload=chunk.payload.model_dump(),
|
payload=chunk.payload.model_dump(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from fastmcp.server.server import Transport
|
|||||||
from .server import mcp
|
from .server import mcp
|
||||||
|
|
||||||
|
|
||||||
def run(transport: Transport = "sse"):
|
def run(transport: Transport = "sse"): # pragma: no cover
|
||||||
"""Run the vector search MCP server with the specified transport.
|
"""Run the vector search MCP server with the specified transport.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ from typing import Annotated
|
|||||||
|
|
||||||
from fastmcp import FastMCP
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
from ..engine import Backend, get_engine
|
from ..engine import get_engine
|
||||||
|
|
||||||
mcp = FastMCP("Searchbox MCP")
|
mcp = FastMCP("Searchbox MCP")
|
||||||
|
|
||||||
engine_map = {"qdrant": get_engine(Backend.QDRANT, use_settings=True)}
|
engine_map = {"qdrant": get_engine("qdrant")}
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool(exclude_args=["backend", "embedding", "collection", "limit", "threshold"])
|
@mcp.tool(exclude_args=["backend", "embedding", "collection", "limit", "threshold"])
|
||||||
async def get_information(
|
async def get_information(
|
||||||
question: Annotated[str, "The user question"],
|
query: Annotated[str, "The user query"],
|
||||||
backend: str = "qdrant",
|
backend: str = "qdrant",
|
||||||
embedding: list[float] = [],
|
embedding: list[float] = [],
|
||||||
collection: str = "default",
|
collection: str = "default",
|
||||||
@@ -37,7 +37,7 @@ async def get_information(
|
|||||||
threshold: float | None = None,
|
threshold: float | None = None,
|
||||||
):
|
):
|
||||||
"""Search a private repository for information."""
|
"""Search a private repository for information."""
|
||||||
_ = question
|
_ = query
|
||||||
|
|
||||||
engine = engine_map[backend]
|
engine = engine_map[backend]
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ The models provide type safety and validation for:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@@ -152,6 +153,6 @@ class Chunk(BaseModel):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: str
|
id: UUID
|
||||||
vector: list[float]
|
vector: list[float]
|
||||||
payload: ChunkData
|
payload: ChunkData
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
"""Tests for the vector search client module."""
|
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
"""Tests for the vector search client module."""
|
|
||||||
|
|
||||||
from unittest.mock import AsyncMock, MagicMock
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from searchbox.client import Client
|
|
||||||
from searchbox.engine import Backend
|
|
||||||
from searchbox.models import Chunk, ChunkData, Match
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_engine():
|
|
||||||
"""Create a mock engine for testing."""
|
|
||||||
engine = AsyncMock()
|
|
||||||
engine.create_index.return_value = True
|
|
||||||
engine.upload_chunk.return_value = True
|
|
||||||
engine.semantic_search.return_value = [
|
|
||||||
{"chunk_id": "1", "score": 0.95, "payload": {"text": "result 1"}},
|
|
||||||
{"chunk_id": "2", "score": 0.85, "payload": {"text": "result 2"}},
|
|
||||||
]
|
|
||||||
return engine
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def sample_chunk():
|
|
||||||
"""Create a sample chunk for testing."""
|
|
||||||
return Chunk(
|
|
||||||
id="test-chunk-1",
|
|
||||||
vector=[0.1, 0.2, 0.3, 0.4, 0.5],
|
|
||||||
payload=ChunkData(
|
|
||||||
page_content="This is a test chunk content",
|
|
||||||
filename="test_document.pdf",
|
|
||||||
page=1,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestClient:
|
|
||||||
"""Test suite for the Client class."""
|
|
||||||
|
|
||||||
def test_client_initialization(self, mock_engine, monkeypatch):
|
|
||||||
"""Test that Client initializes correctly with backend and collection."""
|
|
||||||
# Mock the get_engine function
|
|
||||||
mock_get_engine = MagicMock(return_value=mock_engine)
|
|
||||||
monkeypatch.setattr("searchbox.client.get_engine", mock_get_engine)
|
|
||||||
|
|
||||||
client = Client(backend=Backend.QDRANT, collection="test_collection")
|
|
||||||
|
|
||||||
assert client.collection == "test_collection"
|
|
||||||
assert client.engine == mock_engine
|
|
||||||
mock_get_engine.assert_called_once_with(Backend.QDRANT)
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_create_index(self, mock_engine, monkeypatch):
|
|
||||||
"""Test create_index method delegates to engine."""
|
|
||||||
mock_get_engine = MagicMock(return_value=mock_engine)
|
|
||||||
monkeypatch.setattr("searchbox.client.get_engine", mock_get_engine)
|
|
||||||
|
|
||||||
client = Client(backend=Backend.QDRANT, collection="test_collection")
|
|
||||||
result = await client.create_index(size=512)
|
|
||||||
|
|
||||||
assert result is True
|
|
||||||
mock_engine.create_index.assert_called_once_with("test_collection", 512)
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_create_index_failure(self, mock_engine, monkeypatch):
|
|
||||||
"""Test create_index method handles failure."""
|
|
||||||
mock_engine.create_index.return_value = False
|
|
||||||
mock_get_engine = MagicMock(return_value=mock_engine)
|
|
||||||
monkeypatch.setattr("searchbox.client.get_engine", mock_get_engine)
|
|
||||||
|
|
||||||
client = Client(backend=Backend.QDRANT, collection="test_collection")
|
|
||||||
result = await client.create_index(size=256)
|
|
||||||
|
|
||||||
assert result is False
|
|
||||||
mock_engine.create_index.assert_called_once_with("test_collection", 256)
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_upload_chunk(self, mock_engine, monkeypatch, sample_chunk):
|
|
||||||
"""Test upload_chunk method delegates to engine."""
|
|
||||||
mock_get_engine = MagicMock(return_value=mock_engine)
|
|
||||||
monkeypatch.setattr("searchbox.client.get_engine", mock_get_engine)
|
|
||||||
|
|
||||||
client = Client(backend=Backend.QDRANT, collection="documents")
|
|
||||||
result = await client.upload_chunk(sample_chunk)
|
|
||||||
|
|
||||||
assert result is True
|
|
||||||
mock_engine.upload_chunk.assert_called_once_with("documents", sample_chunk)
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_upload_chunk_failure(self, mock_engine, monkeypatch, sample_chunk):
|
|
||||||
"""Test upload_chunk method handles failure."""
|
|
||||||
mock_engine.upload_chunk.return_value = False
|
|
||||||
mock_get_engine = MagicMock(return_value=mock_engine)
|
|
||||||
monkeypatch.setattr("searchbox.client.get_engine", mock_get_engine)
|
|
||||||
|
|
||||||
client = Client(backend=Backend.QDRANT, collection="documents")
|
|
||||||
result = await client.upload_chunk(sample_chunk)
|
|
||||||
|
|
||||||
assert result is False
|
|
||||||
mock_engine.upload_chunk.assert_called_once_with("documents", sample_chunk)
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_semantic_search_default_parameters(self, mock_engine, monkeypatch):
|
|
||||||
"""Test semantic_search with default parameters."""
|
|
||||||
mock_get_engine = MagicMock(return_value=mock_engine)
|
|
||||||
monkeypatch.setattr("searchbox.client.get_engine", mock_get_engine)
|
|
||||||
|
|
||||||
client = Client(backend=Backend.QDRANT, collection="search_collection")
|
|
||||||
embedding = [0.1, 0.2, 0.3, 0.4, 0.5]
|
|
||||||
|
|
||||||
result = await client.semantic_search(embedding)
|
|
||||||
|
|
||||||
mock_engine.semantic_search.assert_called_once_with(
|
|
||||||
embedding, "search_collection", 10, None, None
|
|
||||||
)
|
|
||||||
assert result == mock_engine.semantic_search.return_value
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_semantic_search_with_limit(self, mock_engine, monkeypatch):
|
|
||||||
"""Test semantic_search with custom limit."""
|
|
||||||
mock_get_engine = MagicMock(return_value=mock_engine)
|
|
||||||
monkeypatch.setattr("searchbox.client.get_engine", mock_get_engine)
|
|
||||||
|
|
||||||
client = Client(backend=Backend.QDRANT, collection="search_collection")
|
|
||||||
embedding = [0.1, 0.2, 0.3]
|
|
||||||
|
|
||||||
result = await client.semantic_search(embedding, limit=5)
|
|
||||||
|
|
||||||
mock_engine.semantic_search.assert_called_once_with(
|
|
||||||
embedding, "search_collection", 5, None, None
|
|
||||||
)
|
|
||||||
assert result == mock_engine.semantic_search.return_value
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_semantic_search_with_conditions(self, mock_engine, monkeypatch):
|
|
||||||
"""Test semantic_search with filter conditions."""
|
|
||||||
mock_get_engine = MagicMock(return_value=mock_engine)
|
|
||||||
monkeypatch.setattr("searchbox.client.get_engine", mock_get_engine)
|
|
||||||
|
|
||||||
client = Client(backend=Backend.QDRANT, collection="filtered_collection")
|
|
||||||
embedding = [0.1, 0.2, 0.3, 0.4]
|
|
||||||
conditions = [Match(key="category", value="technology")]
|
|
||||||
|
|
||||||
result = await client.semantic_search(embedding, conditions=conditions)
|
|
||||||
|
|
||||||
mock_engine.semantic_search.assert_called_once_with(
|
|
||||||
embedding, "filtered_collection", 10, conditions, None
|
|
||||||
)
|
|
||||||
assert result == mock_engine.semantic_search.return_value
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_semantic_search_with_threshold(self, mock_engine, monkeypatch):
|
|
||||||
"""Test semantic_search with similarity threshold."""
|
|
||||||
mock_get_engine = MagicMock(return_value=mock_engine)
|
|
||||||
monkeypatch.setattr("searchbox.client.get_engine", mock_get_engine)
|
|
||||||
|
|
||||||
client = Client(backend=Backend.QDRANT, collection="threshold_collection")
|
|
||||||
embedding = [0.5, 0.4, 0.3, 0.2, 0.1]
|
|
||||||
|
|
||||||
result = await client.semantic_search(embedding, threshold=0.8)
|
|
||||||
|
|
||||||
mock_engine.semantic_search.assert_called_once_with(
|
|
||||||
embedding, "threshold_collection", 10, None, 0.8
|
|
||||||
)
|
|
||||||
assert result == mock_engine.semantic_search.return_value
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_semantic_search_all_parameters(self, mock_engine, monkeypatch):
|
|
||||||
"""Test semantic_search with all parameters specified."""
|
|
||||||
mock_get_engine = MagicMock(return_value=mock_engine)
|
|
||||||
monkeypatch.setattr("searchbox.client.get_engine", mock_get_engine)
|
|
||||||
|
|
||||||
client = Client(backend=Backend.QDRANT, collection="full_params_collection")
|
|
||||||
embedding = [0.2, 0.4, 0.6, 0.8, 1.0]
|
|
||||||
conditions = [
|
|
||||||
Match(key="status", value="published"),
|
|
||||||
Match(key="author", value="john_doe"),
|
|
||||||
]
|
|
||||||
|
|
||||||
result = await client.semantic_search(
|
|
||||||
embedding=embedding,
|
|
||||||
limit=3,
|
|
||||||
conditions=conditions,
|
|
||||||
threshold=0.75,
|
|
||||||
)
|
|
||||||
|
|
||||||
mock_engine.semantic_search.assert_called_once_with(
|
|
||||||
embedding, "full_params_collection", 3, conditions, 0.75
|
|
||||||
)
|
|
||||||
assert result == mock_engine.semantic_search.return_value
|
|
||||||
|
|
||||||
def test_client_is_final(self):
|
|
||||||
"""Test that Client class is marked as final."""
|
|
||||||
from typing import get_origin
|
|
||||||
|
|
||||||
# Check if Client is decorated with @final
|
|
||||||
assert hasattr(Client, "__final__") or Client.__dict__.get("__final__", False)
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_client_integration_workflow(self, mock_engine, monkeypatch, sample_chunk):
|
|
||||||
"""Test a complete workflow: create index, upload chunk, search."""
|
|
||||||
mock_get_engine = MagicMock(return_value=mock_engine)
|
|
||||||
monkeypatch.setattr("searchbox.client.get_engine", mock_get_engine)
|
|
||||||
|
|
||||||
client = Client(backend=Backend.QDRANT, collection="workflow_test")
|
|
||||||
|
|
||||||
# Create index
|
|
||||||
index_result = await client.create_index(size=384)
|
|
||||||
assert index_result is True
|
|
||||||
|
|
||||||
# Upload chunk
|
|
||||||
upload_result = await client.upload_chunk(sample_chunk)
|
|
||||||
assert upload_result is True
|
|
||||||
|
|
||||||
# Search
|
|
||||||
search_embedding = [0.1, 0.2, 0.3, 0.4, 0.5]
|
|
||||||
search_result = await client.semantic_search(search_embedding, limit=5)
|
|
||||||
|
|
||||||
# Verify all operations were called correctly
|
|
||||||
mock_engine.create_index.assert_called_once_with("workflow_test", 384)
|
|
||||||
mock_engine.upload_chunk.assert_called_once_with("workflow_test", sample_chunk)
|
|
||||||
mock_engine.semantic_search.assert_called_once_with(
|
|
||||||
search_embedding, "workflow_test", 5, None, None
|
|
||||||
)
|
|
||||||
assert search_result == mock_engine.semantic_search.return_value
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_semantic_search_empty_results(self, mock_engine, monkeypatch):
|
|
||||||
"""Test semantic_search when no results are found."""
|
|
||||||
mock_engine.semantic_search.return_value = []
|
|
||||||
mock_get_engine = MagicMock(return_value=mock_engine)
|
|
||||||
monkeypatch.setattr("searchbox.client.get_engine", mock_get_engine)
|
|
||||||
|
|
||||||
client = Client(backend=Backend.QDRANT, collection="empty_results")
|
|
||||||
embedding = [0.1, 0.2, 0.3]
|
|
||||||
|
|
||||||
result = await client.semantic_search(embedding)
|
|
||||||
|
|
||||||
assert result == []
|
|
||||||
mock_engine.semantic_search.assert_called_once_with(
|
|
||||||
embedding, "empty_results", 10, None, None
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_client_attributes_after_init(self, mock_engine, monkeypatch):
|
|
||||||
"""Test that client has the expected attributes after initialization."""
|
|
||||||
mock_get_engine = MagicMock(return_value=mock_engine)
|
|
||||||
monkeypatch.setattr("searchbox.client.get_engine", mock_get_engine)
|
|
||||||
|
|
||||||
client = Client(backend=Backend.QDRANT, collection="attr_test")
|
|
||||||
|
|
||||||
assert hasattr(client, "engine")
|
|
||||||
assert hasattr(client, "collection")
|
|
||||||
assert client.engine is mock_engine
|
|
||||||
assert client.collection == "attr_test"
|
|
||||||
assert hasattr(client, "create_index")
|
|
||||||
assert hasattr(client, "upload_chunk")
|
|
||||||
assert hasattr(client, "semantic_search")
|
|
||||||
55
tests/test_client/test_qdrant_client.py
Normal file
55
tests/test_client/test_qdrant_client.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
from uuid import uuid4
|
||||||
|
import pytest
|
||||||
|
from searchbox.client import Client
|
||||||
|
from searchbox.engine.qdrant_engine import QdrantEngine
|
||||||
|
from searchbox.models import Chunk
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def qdrant_client():
|
||||||
|
client = Client("qdrant", "test_index", url=":memory:")
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_qdrant_client(qdrant_client: Client):
|
||||||
|
assert isinstance(qdrant_client, Client)
|
||||||
|
assert isinstance(qdrant_client.engine, QdrantEngine)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_qdrant_index(qdrant_client: Client):
|
||||||
|
result = await qdrant_client.create_index(3)
|
||||||
|
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_upload_chunk(qdrant_client: Client):
|
||||||
|
chunk = Chunk.model_validate(
|
||||||
|
{
|
||||||
|
"id": uuid4(),
|
||||||
|
"vector": [0.0, 0.1, 0.3],
|
||||||
|
"payload": {
|
||||||
|
"page_content": "This is a test page content.",
|
||||||
|
"filename": "test.txt",
|
||||||
|
"page": 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await qdrant_client.upload_chunk(chunk)
|
||||||
|
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_semantic_search(qdrant_client: Client):
|
||||||
|
result = await qdrant_client.semantic_search([0.0, 0.1, 0.3])
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
|
||||||
|
first_result = result[0]
|
||||||
|
assert first_result.chunk_id is not None
|
||||||
|
assert first_result.score > 0.9
|
||||||
|
assert first_result.payload == {
|
||||||
|
"page_content": "This is a test page content.",
|
||||||
|
"filename": "test.txt",
|
||||||
|
"page": 1,
|
||||||
|
}
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
# Engine Module Test Suite
|
|
||||||
|
|
||||||
This directory contains comprehensive tests for the vector search engine module, covering all components from the abstract base engine to concrete implementations and the factory pattern.
|
|
||||||
|
|
||||||
## Test Structure
|
|
||||||
|
|
||||||
### Core Test Files
|
|
||||||
|
|
||||||
- **`test_base_engine.py`** - Tests for the abstract `BaseEngine` class
|
|
||||||
- **`test_qdrant_engine.py`** - Tests for the `QdrantEngine` implementation
|
|
||||||
- **`test_factory.py`** - Tests for the engine factory and `EngineType` enum
|
|
||||||
- **`test_integration.py`** - End-to-end integration tests
|
|
||||||
- **`conftest.py`** - Shared fixtures and test configuration
|
|
||||||
|
|
||||||
## Test Coverage
|
|
||||||
|
|
||||||
### BaseEngine Tests (`test_base_engine.py`)
|
|
||||||
- ✅ Abstract class enforcement - ensures BaseEngine cannot be instantiated
|
|
||||||
- ✅ Abstract method verification - validates required method signatures
|
|
||||||
- ✅ Generic typing constraints - tests TypeVar functionality
|
|
||||||
- ✅ Semantic search workflow - tests the complete search flow
|
|
||||||
- ✅ Condition transformation - tests with various condition types
|
|
||||||
- ✅ Response transformation - tests data structure conversion
|
|
||||||
- ✅ Parameter validation - tests default values and edge cases
|
|
||||||
|
|
||||||
### QdrantEngine Tests (`test_qdrant_engine.py`)
|
|
||||||
- ✅ Inheritance verification - ensures proper BaseEngine inheritance
|
|
||||||
- ✅ Generic type parameters - validates `BaseEngine[list[ScoredPoint], Filter]`
|
|
||||||
- ✅ Condition transformation - tests conversion to Qdrant Filter objects
|
|
||||||
- Match conditions → `MatchValue`
|
|
||||||
- MatchAny conditions → `MatchAny`
|
|
||||||
- MatchExclude conditions → `MatchExcept`
|
|
||||||
- ✅ Response transformation - tests `ScoredPoint` to `SearchRow` conversion
|
|
||||||
- ✅ Null payload filtering - ensures entries with null payloads are excluded
|
|
||||||
- ✅ Client interaction - mocks and verifies Qdrant client calls
|
|
||||||
- ✅ Error propagation - ensures client exceptions bubble up correctly
|
|
||||||
- ✅ Initialization - tests Settings and AsyncQdrantClient setup
|
|
||||||
|
|
||||||
### Factory Tests (`test_factory.py`)
|
|
||||||
- ✅ Backend type enumeration - tests `Backend` enum values and behavior
|
|
||||||
- ✅ Factory function typing - validates overload signatures for type safety
|
|
||||||
- ✅ Engine instantiation - tests creation of concrete engine instances
|
|
||||||
- ✅ Error handling - validates behavior with invalid inputs
|
|
||||||
- ✅ Caching behavior - ensures `@cache` decorator works correctly (same instances)
|
|
||||||
- ✅ COSMOS engine handling - tests NotImplementedError for unimplemented engines
|
|
||||||
- ✅ String enum behavior - tests StrEnum functionality and JSON serialization
|
|
||||||
|
|
||||||
### Integration Tests (`test_integration.py`)
|
|
||||||
- ✅ Complete workflow - factory → conditions → search → response transformation
|
|
||||||
- ✅ Parameter passing - verifies correct parameter flow through all layers
|
|
||||||
- ✅ Complex conditions - tests multiple condition types together
|
|
||||||
- ✅ Large result sets - tests handling of 100+ search results
|
|
||||||
- ✅ Edge cases - empty conditions, null payloads, error scenarios
|
|
||||||
- ✅ Named vectors - tests support for Qdrant NamedVector objects
|
|
||||||
- ✅ Multiple engine instances - tests independence and concurrent usage
|
|
||||||
|
|
||||||
## Test Fixtures (`conftest.py`)
|
|
||||||
|
|
||||||
### Automatic Fixtures
|
|
||||||
- `clear_engine_cache` - Auto-clears `@cache` decorator before/after each test
|
|
||||||
|
|
||||||
### Mock Objects
|
|
||||||
- `mock_qdrant_client` - AsyncMock for Qdrant client with default responses
|
|
||||||
- `mock_settings` - Mock Settings object with test configuration
|
|
||||||
- `mock_qdrant_engine_dependencies` - Complete mocked environment
|
|
||||||
|
|
||||||
### Sample Data
|
|
||||||
- `sample_embedding` - Standard test embedding vector
|
|
||||||
- `sample_conditions` - Common condition objects for testing
|
|
||||||
- `sample_scored_points` - Realistic ScoredPoint objects
|
|
||||||
- `sample_search_rows` - Expected SearchRow outputs
|
|
||||||
- `qdrant_filter_single/multiple` - Pre-built Qdrant Filter objects
|
|
||||||
|
|
||||||
## Running Tests
|
|
||||||
|
|
||||||
### Run All Engine Tests
|
|
||||||
```bash
|
|
||||||
uv run pytest tests/test_engine/ -v
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run Specific Test File
|
|
||||||
```bash
|
|
||||||
uv run pytest tests/test_engine/test_base_engine.py -v
|
|
||||||
uv run pytest tests/test_engine/test_qdrant_engine.py -v
|
|
||||||
uv run pytest tests/test_engine/test_factory.py -v
|
|
||||||
uv run pytest tests/test_engine/test_integration.py -v
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run Specific Test Class or Method
|
|
||||||
```bash
|
|
||||||
uv run pytest tests/test_engine/test_factory.py::TestEngineFactory::test_get_engine_qdrant -v
|
|
||||||
uv run pytest tests/test_engine/test_integration.py::TestEngineIntegration -v
|
|
||||||
```
|
|
||||||
|
|
||||||
### Coverage Report
|
|
||||||
```bash
|
|
||||||
uv run pytest tests/test_engine/ --cov=src/vector_search_mcp/engine --cov-report=html
|
|
||||||
```
|
|
||||||
|
|
||||||
## Test Patterns Used
|
|
||||||
|
|
||||||
### Mocking Strategy
|
|
||||||
- **External dependencies** - All external services (Qdrant client, Settings) are mocked
|
|
||||||
- **Dependency injection** - Tests inject mocks through constructor parameters
|
|
||||||
- **Return value control** - Mocks return predictable test data for assertions
|
|
||||||
|
|
||||||
### Async Testing
|
|
||||||
- Uses `@pytest.mark.asyncio` for async method testing
|
|
||||||
- `AsyncMock` objects for async client methods
|
|
||||||
- Proper await/async syntax throughout
|
|
||||||
|
|
||||||
### Type Testing
|
|
||||||
- Generic type parameter validation
|
|
||||||
- Overload signature verification
|
|
||||||
- Runtime type checking where applicable
|
|
||||||
|
|
||||||
### Error Testing
|
|
||||||
- Exception propagation validation
|
|
||||||
- Invalid input handling
|
|
||||||
- Boundary condition testing
|
|
||||||
|
|
||||||
## Key Test Insights
|
|
||||||
|
|
||||||
### Generic Typing Validation
|
|
||||||
The tests verify that the generic `BaseEngine[ResponseType, ConditionType]` pattern works correctly:
|
|
||||||
- `QdrantEngine` is typed as `BaseEngine[list[ScoredPoint], Filter]`
|
|
||||||
- Type checkers can verify correct usage at compile time
|
|
||||||
- Runtime behavior matches type declarations
|
|
||||||
|
|
||||||
### Factory Pattern Testing
|
|
||||||
The overload tests ensure proper type inference:
|
|
||||||
```python
|
|
||||||
# Type checker knows this returns QdrantEngine
|
|
||||||
engine = get_engine(Backend.QDRANT)
|
|
||||||
|
|
||||||
# Type checker knows this returns BaseEngine (generic)
|
|
||||||
backend: Backend = some_variable
|
|
||||||
engine = get_engine(backend)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Caching Behavior
|
|
||||||
Tests verify that the `@cache` decorator works correctly:
|
|
||||||
```python
|
|
||||||
# Both calls return the same instance
|
|
||||||
engine1 = get_engine(Backend.QDRANT)
|
|
||||||
engine2 = get_engine(Backend.QDRANT)
|
|
||||||
assert engine1 is engine2 # Same instance due to caching
|
|
||||||
```
|
|
||||||
|
|
||||||
### Integration Flow Validation
|
|
||||||
Integration tests verify the complete data flow:
|
|
||||||
1. `get_engine()` creates proper engine instance
|
|
||||||
2. `semantic_search()` calls `transform_conditions()`
|
|
||||||
3. Transformed conditions passed to `run_similarity_query()`
|
|
||||||
4. Query response processed by `transform_response()`
|
|
||||||
5. Final `SearchRow` objects returned to caller
|
|
||||||
|
|
||||||
## Maintenance Notes
|
|
||||||
|
|
||||||
### Adding New Engine Types
|
|
||||||
When adding new engines:
|
|
||||||
1. Add enum value to `Backend`
|
|
||||||
2. Add overload signature to `get_engine()`
|
|
||||||
3. Update factory tests for new count and behavior
|
|
||||||
4. Create engine-specific test file following `test_qdrant_engine.py` pattern
|
|
||||||
5. Remember that `@cache` decorator will cache instances per backend type
|
|
||||||
|
|
||||||
### Mock Updates
|
|
||||||
If engine interfaces change:
|
|
||||||
1. Update fixture return types in `conftest.py`
|
|
||||||
2. Verify mock method signatures match real implementations
|
|
||||||
3. Update integration tests for new parameter flows
|
|
||||||
4. Ensure cache clearing fixture handles any new caching behavior
|
|
||||||
|
|
||||||
### Performance Testing
|
|
||||||
Current tests focus on correctness. For performance testing:
|
|
||||||
- Use `pytest-benchmark` for timing critical paths
|
|
||||||
- Test with realistic data sizes (1000+ embeddings)
|
|
||||||
- Mock network I/O but measure transformation logic
|
|
||||||
- Consider cache warming effects when benchmarking
|
|
||||||
|
|
||||||
## Recent Fixes Applied
|
|
||||||
|
|
||||||
### After Formatter Changes
|
|
||||||
The following issues were resolved after code formatting:
|
|
||||||
|
|
||||||
1. **Enum Rename**: `EngineType` → `Backend` - All tests updated
|
|
||||||
2. **Caching Addition**: `@cache` decorator added to `get_engine()`
|
|
||||||
- Tests updated to expect same instances (not different ones)
|
|
||||||
- Auto-cache clearing fixture added to `conftest.py`
|
|
||||||
3. **Mock Isolation**: Improved mock setup to prevent real network calls
|
|
||||||
- Proper patch contexts in all integration tests
|
|
||||||
- Cache clearing ensures clean test state
|
|
||||||
|
|
||||||
All 62 tests now pass successfully! 🎉
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# Test package for engine module
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from qdrant_client import models
|
|
||||||
|
|
||||||
from searchbox.engine import get_engine
|
|
||||||
from searchbox.models import Match, MatchAny, MatchExclude, SearchRow
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def clear_engine_cache():
|
|
||||||
"""Clear the engine cache before each test for proper isolation"""
|
|
||||||
get_engine.cache_clear()
|
|
||||||
yield
|
|
||||||
get_engine.cache_clear()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_qdrant_client():
|
|
||||||
"""Create a mock Qdrant client for testing"""
|
|
||||||
client = AsyncMock()
|
|
||||||
|
|
||||||
# Default search response
|
|
||||||
client.search.return_value = [
|
|
||||||
models.ScoredPoint(
|
|
||||||
id=1,
|
|
||||||
score=0.95,
|
|
||||||
payload={"text": "Test document 1", "category": "test"},
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
models.ScoredPoint(
|
|
||||||
id=2,
|
|
||||||
score=0.85,
|
|
||||||
payload={"text": "Test document 2", "category": "test"},
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
return client
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_settings():
|
|
||||||
"""Create mock settings for testing"""
|
|
||||||
settings = MagicMock()
|
|
||||||
settings.url = "http://localhost:6333"
|
|
||||||
settings.api_key = "test_api_key"
|
|
||||||
return settings
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def sample_embedding():
|
|
||||||
"""Provide a sample embedding vector for testing"""
|
|
||||||
return [0.1, 0.2, 0.3, 0.4, 0.5]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def sample_conditions():
|
|
||||||
"""Provide sample conditions for testing"""
|
|
||||||
return [
|
|
||||||
Match(key="category", value="document"),
|
|
||||||
MatchAny(key="tags", any=["python", "rust"]),
|
|
||||||
MatchExclude(key="status", exclude=["deleted"]),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def sample_scored_points():
|
|
||||||
"""Provide sample ScoredPoint objects for testing"""
|
|
||||||
return [
|
|
||||||
models.ScoredPoint(
|
|
||||||
id=1,
|
|
||||||
score=0.95,
|
|
||||||
payload={"text": "First document", "category": "tech"},
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
models.ScoredPoint(
|
|
||||||
id=2,
|
|
||||||
score=0.87,
|
|
||||||
payload={"text": "Second document", "category": "science"},
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
models.ScoredPoint(
|
|
||||||
id=3,
|
|
||||||
score=0.75,
|
|
||||||
payload=None, # This should be filtered out
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def sample_search_rows():
|
|
||||||
"""Provide sample SearchRow objects for testing"""
|
|
||||||
return [
|
|
||||||
SearchRow(
|
|
||||||
chunk_id="1",
|
|
||||||
score=0.95,
|
|
||||||
payload={"text": "First document", "category": "tech"},
|
|
||||||
),
|
|
||||||
SearchRow(
|
|
||||||
chunk_id="2",
|
|
||||||
score=0.87,
|
|
||||||
payload={"text": "Second document", "category": "science"},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_qdrant_engine_dependencies():
|
|
||||||
"""Mock all external dependencies for QdrantEngine"""
|
|
||||||
with (
|
|
||||||
patch("vector_search_mcp.engine.qdrant_engine.Settings") as mock_settings_class,
|
|
||||||
patch(
|
|
||||||
"vector_search_mcp.engine.qdrant_engine.AsyncQdrantClient"
|
|
||||||
) as mock_client_class,
|
|
||||||
):
|
|
||||||
# Setup mock settings
|
|
||||||
mock_settings = MagicMock()
|
|
||||||
mock_settings.url = "http://localhost:6333"
|
|
||||||
mock_settings.api_key = "test_api_key"
|
|
||||||
mock_settings_class.return_value = mock_settings
|
|
||||||
|
|
||||||
# Setup mock client
|
|
||||||
mock_client = AsyncMock()
|
|
||||||
mock_client_class.return_value = mock_client
|
|
||||||
|
|
||||||
yield {
|
|
||||||
"settings_class": mock_settings_class,
|
|
||||||
"client_class": mock_client_class,
|
|
||||||
"settings": mock_settings,
|
|
||||||
"client": mock_client,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def qdrant_filter_single():
|
|
||||||
"""Create a single-condition Qdrant filter for testing"""
|
|
||||||
return models.Filter(
|
|
||||||
must=[
|
|
||||||
models.FieldCondition(
|
|
||||||
key="category", match=models.MatchValue(value="document")
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def qdrant_filter_multiple():
|
|
||||||
"""Create a multi-condition Qdrant filter for testing"""
|
|
||||||
return models.Filter(
|
|
||||||
must=[
|
|
||||||
models.FieldCondition(
|
|
||||||
key="category", match=models.MatchValue(value="document")
|
|
||||||
),
|
|
||||||
models.FieldCondition(
|
|
||||||
key="tags", match=models.MatchAny(any=["python", "rust"])
|
|
||||||
),
|
|
||||||
models.FieldCondition(
|
|
||||||
key="status", match=models.MatchExcept(**{"except": ["deleted"]})
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
from typing import Any
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from searchbox.engine.base_engine import BaseEngine
|
|
||||||
from searchbox.models import Condition, Match, MatchAny, SearchRow
|
|
||||||
|
|
||||||
|
|
||||||
class MockEngine(BaseEngine[dict[str, Any], str, dict]):
|
|
||||||
"""Mock engine for testing BaseEngine abstract functionality"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.transform_conditions_called = False
|
|
||||||
self.transform_response_called = False
|
|
||||||
self.run_similarity_query_called = False
|
|
||||||
|
|
||||||
def transform_conditions(self, conditions: list[Condition] | None) -> str | None:
|
|
||||||
self.transform_conditions_called = True
|
|
||||||
if not conditions:
|
|
||||||
return None
|
|
||||||
return f"transformed_{len(conditions)}_conditions"
|
|
||||||
|
|
||||||
def transform_response(self, response: dict[str, Any]) -> list[SearchRow]:
|
|
||||||
self.transform_response_called = True
|
|
||||||
return [
|
|
||||||
SearchRow(
|
|
||||||
chunk_id=str(i),
|
|
||||||
score=response.get(f"score_{i}", 0.5),
|
|
||||||
payload={"text": f"result_{i}"},
|
|
||||||
)
|
|
||||||
for i in range(response.get("count", 1))
|
|
||||||
]
|
|
||||||
|
|
||||||
async def run_similarity_query(
|
|
||||||
self,
|
|
||||||
embedding: list[float],
|
|
||||||
collection: str,
|
|
||||||
limit: int = 10,
|
|
||||||
conditions: str | None = None,
|
|
||||||
threshold: float | None = None,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
self.run_similarity_query_called = True
|
|
||||||
return {
|
|
||||||
"count": min(limit, 3),
|
|
||||||
"collection": collection,
|
|
||||||
"conditions": conditions,
|
|
||||||
"threshold": threshold,
|
|
||||||
"score_0": 0.95,
|
|
||||||
"score_1": 0.85,
|
|
||||||
"score_2": 0.75,
|
|
||||||
}
|
|
||||||
|
|
||||||
async def create_index(self, name: str, size: int) -> bool:
|
|
||||||
"""Mock implementation of create_index"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
def transform_chunk(self, chunk) -> dict:
|
|
||||||
"""Mock implementation of transform_chunk"""
|
|
||||||
return {
|
|
||||||
"id": chunk.id,
|
|
||||||
"vector": chunk.vector,
|
|
||||||
"payload": chunk.payload.model_dump()
|
|
||||||
}
|
|
||||||
|
|
||||||
async def run_upload_chunk(self, index_name: str, chunk: dict) -> bool:
|
|
||||||
"""Mock implementation of run_upload_chunk"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class TestBaseEngine:
|
|
||||||
"""Test suite for BaseEngine abstract class"""
|
|
||||||
|
|
||||||
def test_base_engine_is_abstract(self):
|
|
||||||
"""Test that BaseEngine cannot be instantiated directly"""
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
BaseEngine()
|
|
||||||
|
|
||||||
def test_base_engine_abc_methods(self):
|
|
||||||
"""Test that BaseEngine has the correct abstract methods"""
|
|
||||||
abstract_methods = BaseEngine.__abstractmethods__
|
|
||||||
expected_methods = {
|
|
||||||
"transform_conditions",
|
|
||||||
"transform_response",
|
|
||||||
"run_similarity_query",
|
|
||||||
"create_index",
|
|
||||||
"transform_chunk",
|
|
||||||
"run_upload_chunk",
|
|
||||||
}
|
|
||||||
assert abstract_methods == expected_methods
|
|
||||||
|
|
||||||
def test_mock_engine_instantiation(self):
|
|
||||||
"""Test that mock engine can be instantiated"""
|
|
||||||
engine = MockEngine()
|
|
||||||
assert isinstance(engine, BaseEngine)
|
|
||||||
assert not engine.transform_conditions_called
|
|
||||||
assert not engine.transform_response_called
|
|
||||||
assert not engine.run_similarity_query_called
|
|
||||||
|
|
||||||
def test_transform_conditions_with_none(self):
|
|
||||||
"""Test transform_conditions with None input"""
|
|
||||||
engine = MockEngine()
|
|
||||||
result = engine.transform_conditions(None)
|
|
||||||
|
|
||||||
assert result is None
|
|
||||||
assert engine.transform_conditions_called
|
|
||||||
|
|
||||||
def test_transform_conditions_with_conditions(self):
|
|
||||||
"""Test transform_conditions with actual conditions"""
|
|
||||||
engine = MockEngine()
|
|
||||||
conditions = [
|
|
||||||
Match(key="category", value="test"),
|
|
||||||
MatchAny(key="tags", any=["tag1", "tag2"]),
|
|
||||||
]
|
|
||||||
|
|
||||||
result = engine.transform_conditions(conditions)
|
|
||||||
|
|
||||||
assert result == "transformed_2_conditions"
|
|
||||||
assert engine.transform_conditions_called
|
|
||||||
|
|
||||||
def test_transform_response(self):
|
|
||||||
"""Test transform_response with mock data"""
|
|
||||||
engine = MockEngine()
|
|
||||||
mock_response = {"count": 2, "score_0": 0.9, "score_1": 0.8}
|
|
||||||
|
|
||||||
result = engine.transform_response(mock_response)
|
|
||||||
|
|
||||||
assert len(result) == 2
|
|
||||||
assert all(isinstance(row, SearchRow) for row in result)
|
|
||||||
assert result[0].chunk_id == "0"
|
|
||||||
assert result[0].score == 0.9
|
|
||||||
assert result[1].chunk_id == "1"
|
|
||||||
assert result[1].score == 0.8
|
|
||||||
assert engine.transform_response_called
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_run_similarity_query(self):
|
|
||||||
"""Test run_similarity_query with various parameters"""
|
|
||||||
engine = MockEngine()
|
|
||||||
embedding = [0.1, 0.2, 0.3, 0.4, 0.5]
|
|
||||||
|
|
||||||
result = await engine.run_similarity_query(
|
|
||||||
embedding=embedding,
|
|
||||||
collection="test_collection",
|
|
||||||
limit=5,
|
|
||||||
conditions="test_conditions",
|
|
||||||
threshold=0.7,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert isinstance(result, dict)
|
|
||||||
assert result["collection"] == "test_collection"
|
|
||||||
assert result["conditions"] == "test_conditions"
|
|
||||||
assert result["threshold"] == 0.7
|
|
||||||
assert result["count"] == 3 # min(limit=5, 3)
|
|
||||||
assert engine.run_similarity_query_called
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_semantic_search_full_flow(self):
|
|
||||||
"""Test the complete semantic_search flow"""
|
|
||||||
engine = MockEngine()
|
|
||||||
vector = [0.1, 0.2, 0.3, 0.4, 0.5]
|
|
||||||
conditions = [Match(key="status", value="active")]
|
|
||||||
|
|
||||||
result = await engine.semantic_search(
|
|
||||||
embedding=vector,
|
|
||||||
collection="test_collection",
|
|
||||||
limit=2,
|
|
||||||
conditions=conditions,
|
|
||||||
threshold=0.8,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Verify all methods were called
|
|
||||||
assert engine.transform_conditions_called
|
|
||||||
assert engine.run_similarity_query_called
|
|
||||||
assert engine.transform_response_called
|
|
||||||
|
|
||||||
# Verify result structure
|
|
||||||
assert isinstance(result, list)
|
|
||||||
assert len(result) == 2
|
|
||||||
assert all(isinstance(row, SearchRow) for row in result)
|
|
||||||
assert result[0].score == 0.95
|
|
||||||
assert result[1].score == 0.85
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_semantic_search_with_none_conditions(self):
|
|
||||||
"""Test semantic_search with None conditions"""
|
|
||||||
engine = MockEngine()
|
|
||||||
vector = [0.1, 0.2, 0.3]
|
|
||||||
|
|
||||||
result = await engine.semantic_search(
|
|
||||||
embedding=vector, collection="test_collection"
|
|
||||||
)
|
|
||||||
|
|
||||||
assert engine.transform_conditions_called
|
|
||||||
assert engine.run_similarity_query_called
|
|
||||||
assert engine.transform_response_called
|
|
||||||
assert isinstance(result, list)
|
|
||||||
assert len(result) == 3 # default limit from mock
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_semantic_search_default_parameters(self):
|
|
||||||
"""Test semantic_search with default parameters"""
|
|
||||||
engine = MockEngine()
|
|
||||||
vector = [0.1, 0.2, 0.3]
|
|
||||||
|
|
||||||
result = await engine.semantic_search(
|
|
||||||
embedding=vector, collection="test_collection"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check that defaults were used (limit=10, conditions=None, threshold=None)
|
|
||||||
assert isinstance(result, list)
|
|
||||||
assert len(result) <= 10 # Should respect limit
|
|
||||||
|
|
||||||
def test_typing_constraints(self):
|
|
||||||
"""Test that the generic typing works correctly"""
|
|
||||||
engine = MockEngine()
|
|
||||||
|
|
||||||
# Verify the engine has the correct generic types
|
|
||||||
assert hasattr(engine, "transform_conditions")
|
|
||||||
assert hasattr(engine, "transform_response")
|
|
||||||
assert hasattr(engine, "run_similarity_query")
|
|
||||||
|
|
||||||
# The mock engine should work with dict[str, Any] and str types
|
|
||||||
conditions_result = engine.transform_conditions([])
|
|
||||||
assert conditions_result is None or isinstance(conditions_result, str)
|
|
||||||
|
|
||||||
response_result = engine.transform_response({"test": "data"})
|
|
||||||
assert isinstance(response_result, list)
|
|
||||||
assert all(isinstance(item, SearchRow) for item in response_result)
|
|
||||||
|
|
||||||
|
|
||||||
class IncompleteEngine(BaseEngine[str, int, str]):
|
|
||||||
"""Incomplete engine implementation for testing abstract method enforcement"""
|
|
||||||
|
|
||||||
def transform_conditions(self, conditions: list[Condition] | None) -> int | None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Missing transform_response, run_similarity_query, create_index, transform_chunk, run_upload_chunk
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_upload_chunk_workflow(self):
|
|
||||||
"""Test the complete upload_chunk workflow"""
|
|
||||||
engine = MockEngine()
|
|
||||||
from searchbox.models import Chunk, ChunkData
|
|
||||||
|
|
||||||
chunk = Chunk(
|
|
||||||
id="test-chunk-1",
|
|
||||||
vector=[0.1, 0.2, 0.3],
|
|
||||||
payload=ChunkData(
|
|
||||||
page_content="Test content",
|
|
||||||
filename="test.pdf",
|
|
||||||
page=1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
result = await engine.upload_chunk("test_index", chunk)
|
|
||||||
|
|
||||||
# Verify the workflow called both transform_chunk and run_upload_chunk
|
|
||||||
assert result is True
|
|
||||||
# The MockEngine.run_upload_chunk should have been called with transformed chunk
|
|
||||||
|
|
||||||
|
|
||||||
class TestAbstractMethodEnforcement:
|
|
||||||
"""Test that abstract methods must be implemented"""
|
|
||||||
|
|
||||||
def test_incomplete_engine_cannot_be_instantiated(self):
|
|
||||||
"""Test that incomplete engine implementations cannot be instantiated"""
|
|
||||||
with pytest.raises(TypeError, match="Can't instantiate abstract class"):
|
|
||||||
IncompleteEngine()
|
|
||||||
18
tests/test_engine/test_engine_init.py
Normal file
18
tests/test_engine/test_engine_init.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import pytest
|
||||||
|
from searchbox.engine import UnknownEngineError, get_engine
|
||||||
|
from searchbox.engine.qdrant_engine import QdrantEngine
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_qdrant_engine():
|
||||||
|
engine = get_engine("qdrant")
|
||||||
|
assert isinstance(engine, QdrantEngine)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_qdrant_engine_with_kwargs():
|
||||||
|
engine = get_engine("qdrant", url="http://localhost:6333", api_key=None)
|
||||||
|
assert isinstance(engine, QdrantEngine)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_unknown_engine():
|
||||||
|
with pytest.raises(UnknownEngineError):
|
||||||
|
get_engine("unknown") # type: ignore[reportCallArgs]
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from searchbox.engine import Backend, get_engine, UnknownEngineError
|
|
||||||
from searchbox.engine.base_engine import BaseEngine
|
|
||||||
from searchbox.engine.qdrant_engine import QdrantEngine
|
|
||||||
|
|
||||||
|
|
||||||
class TestEngineFactory:
|
|
||||||
"""Test suite for get_engine factory function"""
|
|
||||||
|
|
||||||
def test_engine_type_enum_values(self):
|
|
||||||
"""Test that EngineType enum has expected values"""
|
|
||||||
assert Backend.QDRANT == "qdrant"
|
|
||||||
assert len(Backend) == 2 # QDRANT and COSMOS engine types
|
|
||||||
|
|
||||||
def test_get_engine_qdrant(self):
|
|
||||||
"""Test get_engine returns QdrantEngine for QDRANT type"""
|
|
||||||
with (
|
|
||||||
patch(
|
|
||||||
"searchbox.engine.qdrant_engine.Settings"
|
|
||||||
) as mock_settings_class,
|
|
||||||
patch(
|
|
||||||
"searchbox.engine.qdrant_engine.AsyncQdrantClient"
|
|
||||||
) as mock_client_class,
|
|
||||||
):
|
|
||||||
# Setup mocks
|
|
||||||
mock_settings = MagicMock()
|
|
||||||
mock_settings.url = "http://localhost:6333"
|
|
||||||
mock_settings.api_key = "test_key"
|
|
||||||
mock_settings_class.return_value = mock_settings
|
|
||||||
|
|
||||||
mock_client = MagicMock()
|
|
||||||
mock_client_class.return_value = mock_client
|
|
||||||
|
|
||||||
# Test factory function
|
|
||||||
engine = get_engine(Backend.QDRANT)
|
|
||||||
|
|
||||||
# Verify return type
|
|
||||||
assert isinstance(engine, QdrantEngine)
|
|
||||||
assert isinstance(engine, BaseEngine)
|
|
||||||
|
|
||||||
# Verify initialization was called correctly
|
|
||||||
mock_settings_class.assert_called_once()
|
|
||||||
mock_client_class.assert_called_once_with(
|
|
||||||
url=mock_settings.url, api_key=mock_settings.api_key
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_engine_invalid_type(self):
|
|
||||||
"""Test get_engine raises ValueError for unknown engine type"""
|
|
||||||
# Create an invalid engine type (bypassing enum validation)
|
|
||||||
invalid_type = "invalid_engine"
|
|
||||||
|
|
||||||
with pytest.raises(UnknownEngineError, match="Unknown engine type: invalid_engine"):
|
|
||||||
# We need to cast to bypass type checking
|
|
||||||
get_engine(invalid_type) # type: ignore
|
|
||||||
|
|
||||||
def test_get_engine_typing_literal_qdrant(self):
|
|
||||||
"""Test that get_engine with literal QDRANT returns correct type"""
|
|
||||||
with (
|
|
||||||
patch("searchbox.engine.qdrant_engine.Settings"),
|
|
||||||
patch("searchbox.engine.qdrant_engine.AsyncQdrantClient"),
|
|
||||||
):
|
|
||||||
# When using literal Backend.QDRANT, mypy should know it's QdrantEngine
|
|
||||||
engine = get_engine(Backend.QDRANT)
|
|
||||||
|
|
||||||
# Runtime verification that it's the correct type
|
|
||||||
assert type(engine).__name__ == "QdrantEngine"
|
|
||||||
assert hasattr(engine, "client") # QdrantEngine specific attribute
|
|
||||||
assert hasattr(engine, "settings") # QdrantEngine specific attribute
|
|
||||||
|
|
||||||
def test_get_engine_typing_variable(self):
|
|
||||||
"""Test that get_engine with variable returns BaseEngine type"""
|
|
||||||
with (
|
|
||||||
patch("searchbox.engine.qdrant_engine.Settings"),
|
|
||||||
patch("searchbox.engine.qdrant_engine.AsyncQdrantClient"),
|
|
||||||
):
|
|
||||||
# When using a variable, mypy should see it as BaseEngine
|
|
||||||
engine_type: Backend = Backend.QDRANT
|
|
||||||
engine = get_engine(engine_type)
|
|
||||||
|
|
||||||
# Runtime verification - it's still a QdrantEngine but typed as BaseEngine
|
|
||||||
assert isinstance(engine, BaseEngine)
|
|
||||||
assert isinstance(engine, QdrantEngine)
|
|
||||||
|
|
||||||
def test_get_engine_uses_cache(self):
|
|
||||||
"""Test that get_engine uses cache and returns same instances"""
|
|
||||||
with (
|
|
||||||
patch(
|
|
||||||
"searchbox.engine.qdrant_engine.Settings"
|
|
||||||
) as mock_settings_class,
|
|
||||||
patch(
|
|
||||||
"searchbox.engine.qdrant_engine.AsyncQdrantClient"
|
|
||||||
) as mock_client_class,
|
|
||||||
):
|
|
||||||
# Setup mocks
|
|
||||||
mock_settings_class.return_value = MagicMock()
|
|
||||||
mock_client_class.return_value = MagicMock()
|
|
||||||
|
|
||||||
# Create multiple engines
|
|
||||||
engine1 = get_engine(Backend.QDRANT)
|
|
||||||
engine2 = get_engine(Backend.QDRANT)
|
|
||||||
|
|
||||||
# Verify they are the same instance due to @cache decorator
|
|
||||||
assert engine1 is engine2
|
|
||||||
assert id(engine1) == id(engine2)
|
|
||||||
|
|
||||||
# But they are the same type
|
|
||||||
assert type(engine1) is type(engine2)
|
|
||||||
assert isinstance(engine1, QdrantEngine)
|
|
||||||
assert isinstance(engine2, QdrantEngine)
|
|
||||||
|
|
||||||
# Verify initialization was called only once due to caching
|
|
||||||
mock_settings_class.assert_called_once()
|
|
||||||
mock_client_class.assert_called_once()
|
|
||||||
|
|
||||||
def test_engine_type_string_values(self):
|
|
||||||
"""Test EngineType string representations"""
|
|
||||||
assert str(Backend.QDRANT) == "qdrant"
|
|
||||||
assert str(Backend.COSMOS) == "cosmos"
|
|
||||||
|
|
||||||
# Test that it can be used in string contexts
|
|
||||||
engine_name = f"engine_{Backend.QDRANT}"
|
|
||||||
assert engine_name == "engine_qdrant"
|
|
||||||
|
|
||||||
def test_engine_type_iteration(self):
|
|
||||||
"""Test that EngineType can be iterated over"""
|
|
||||||
engine_types = list(Backend)
|
|
||||||
assert len(engine_types) == 2
|
|
||||||
assert Backend.QDRANT in engine_types
|
|
||||||
assert Backend.COSMOS in engine_types
|
|
||||||
|
|
||||||
def test_engine_factory_integration(self):
|
|
||||||
"""Test complete factory integration with engine functionality"""
|
|
||||||
with (
|
|
||||||
patch(
|
|
||||||
"searchbox.engine.qdrant_engine.Settings"
|
|
||||||
) as mock_settings_class,
|
|
||||||
patch(
|
|
||||||
"searchbox.engine.qdrant_engine.AsyncQdrantClient"
|
|
||||||
) as mock_client_class,
|
|
||||||
):
|
|
||||||
# Setup mocks
|
|
||||||
mock_settings = MagicMock()
|
|
||||||
mock_settings_class.return_value = mock_settings
|
|
||||||
mock_client_class.return_value = MagicMock()
|
|
||||||
|
|
||||||
# Create engine through factory
|
|
||||||
engine = get_engine(Backend.QDRANT)
|
|
||||||
|
|
||||||
# Verify engine has all required methods from BaseEngine
|
|
||||||
assert hasattr(engine, "transform_conditions")
|
|
||||||
assert hasattr(engine, "transform_response")
|
|
||||||
assert hasattr(engine, "run_similarity_query")
|
|
||||||
assert hasattr(engine, "semantic_search")
|
|
||||||
|
|
||||||
# Verify methods are callable
|
|
||||||
assert callable(engine.transform_conditions)
|
|
||||||
assert callable(engine.transform_response)
|
|
||||||
assert callable(engine.run_similarity_query)
|
|
||||||
assert callable(engine.semantic_search)
|
|
||||||
|
|
||||||
def test_future_engine_extensibility(self):
|
|
||||||
"""Test structure supports future engine additions"""
|
|
||||||
# Verify that EngineType is a StrEnum and can be extended
|
|
||||||
assert issubclass(Backend, str)
|
|
||||||
|
|
||||||
# Verify the factory function structure can handle new engines
|
|
||||||
# (This is more of a design verification)
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
sig = inspect.signature(get_engine)
|
|
||||||
|
|
||||||
# Should take Backend and return BaseEngine
|
|
||||||
params = list(sig.parameters.values())
|
|
||||||
assert len(params) == 1
|
|
||||||
assert params[0].name == "backend"
|
|
||||||
|
|
||||||
|
|
||||||
class TestEngineTypeEnum:
|
|
||||||
"""Test suite specifically for Backend enum"""
|
|
||||||
|
|
||||||
def test_engine_type_is_str_enum(self):
|
|
||||||
"""Test that Backend is a StrEnum"""
|
|
||||||
from enum import StrEnum
|
|
||||||
|
|
||||||
assert issubclass(Backend, StrEnum)
|
|
||||||
|
|
||||||
# Should behave like strings
|
|
||||||
assert Backend.QDRANT == "qdrant"
|
|
||||||
assert f"{Backend.QDRANT}" == "qdrant"
|
|
||||||
|
|
||||||
def test_engine_type_comparison(self):
|
|
||||||
"""Test EngineType comparison operations"""
|
|
||||||
# Should equal string value
|
|
||||||
assert Backend.QDRANT == "qdrant"
|
|
||||||
|
|
||||||
# Should not equal other strings
|
|
||||||
assert Backend.QDRANT != "other"
|
|
||||||
assert Backend.QDRANT != "QDRANT" # Case sensitive
|
|
||||||
|
|
||||||
def test_engine_type_in_collections(self):
|
|
||||||
"""Test EngineType works in collections"""
|
|
||||||
engine_list = [Backend.QDRANT]
|
|
||||||
assert Backend.QDRANT in engine_list
|
|
||||||
assert "qdrant" in engine_list # StrEnum benefit
|
|
||||||
|
|
||||||
engine_set = {Backend.QDRANT}
|
|
||||||
assert Backend.QDRANT in engine_set
|
|
||||||
|
|
||||||
def test_engine_type_json_serializable(self):
|
|
||||||
"""Test that Backend can be JSON serialized"""
|
|
||||||
import json
|
|
||||||
|
|
||||||
data = {"engine": Backend.QDRANT}
|
|
||||||
json_str = json.dumps(data, default=str)
|
|
||||||
assert '"engine": "qdrant"' in json_str
|
|
||||||
|
|
||||||
def test_engine_type_immutable(self):
|
|
||||||
"""Test that Backend values cannot be modified"""
|
|
||||||
original_value = Backend.QDRANT
|
|
||||||
|
|
||||||
# Enum values should be immutable
|
|
||||||
with pytest.raises(AttributeError):
|
|
||||||
Backend.QDRANT = "modified" # type: ignore
|
|
||||||
|
|
||||||
# Original should be unchanged
|
|
||||||
assert Backend.QDRANT == original_value
|
|
||||||
|
|
||||||
|
|
||||||
class TestEngineFactoryErrorHandling:
|
|
||||||
"""Test suite for error handling in engine factory"""
|
|
||||||
|
|
||||||
def test_none_backend_type(self):
|
|
||||||
"""Test get_engine with None raises appropriate error"""
|
|
||||||
with pytest.raises((TypeError, UnknownEngineError)):
|
|
||||||
get_engine(None) # type: ignore
|
|
||||||
|
|
||||||
def test_empty_string_backend_type(self):
|
|
||||||
"""Test get_engine with empty string"""
|
|
||||||
with pytest.raises(UnknownEngineError, match="Unknown engine type"):
|
|
||||||
get_engine("") # type: ignore
|
|
||||||
|
|
||||||
def test_numeric_backend_type(self):
|
|
||||||
"""Test get_engine with numeric input"""
|
|
||||||
with pytest.raises((TypeError, UnknownEngineError)):
|
|
||||||
get_engine(123) # type: ignore
|
|
||||||
|
|
||||||
def test_boolean_backend_type(self):
|
|
||||||
"""Test get_engine with boolean input"""
|
|
||||||
with pytest.raises((TypeError, UnknownEngineError)):
|
|
||||||
get_engine(True) # type: ignore
|
|
||||||
|
|
||||||
def test_get_engine_cosmos_not_implemented(self):
|
|
||||||
"""Test that COSMOS engine raises NotImplementedError"""
|
|
||||||
with pytest.raises(
|
|
||||||
NotImplementedError, match="Cosmos engine is not implemented yet"
|
|
||||||
):
|
|
||||||
get_engine(Backend.COSMOS)
|
|
||||||
|
|
||||||
def test_engine_initialization_failure(self):
|
|
||||||
"""Test handling of engine initialization failures"""
|
|
||||||
with (
|
|
||||||
patch("searchbox.engine.qdrant_engine.Settings") as mock_settings,
|
|
||||||
patch("searchbox.engine.qdrant_engine.AsyncQdrantClient"),
|
|
||||||
):
|
|
||||||
# Make Settings initialization raise an exception
|
|
||||||
mock_settings.side_effect = Exception("Settings initialization failed")
|
|
||||||
|
|
||||||
with pytest.raises(Exception, match="Settings initialization failed"):
|
|
||||||
get_engine(Backend.QDRANT)
|
|
||||||
|
|
||||||
def test_case_sensitive_backend_type(self):
|
|
||||||
"""Test that backend type matching is case sensitive"""
|
|
||||||
with pytest.raises(UnknownEngineError, match="Unknown engine type"):
|
|
||||||
get_engine("QDRANT") # type: ignore
|
|
||||||
|
|
||||||
with pytest.raises(UnknownEngineError, match="Unknown engine type"):
|
|
||||||
get_engine("Qdrant") # type: ignore
|
|
||||||
|
|
||||||
def test_whitespace_backend_type(self):
|
|
||||||
"""Test backend type with whitespace"""
|
|
||||||
with pytest.raises(UnknownEngineError, match="Unknown engine type"):
|
|
||||||
get_engine(" qdrant ") # type: ignore
|
|
||||||
|
|
||||||
with pytest.raises(UnknownEngineError, match="Unknown engine type"):
|
|
||||||
get_engine("\tqdrant\n") # type: ignore
|
|
||||||
@@ -1,361 +0,0 @@
|
|||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from qdrant_client import models
|
|
||||||
|
|
||||||
from searchbox.engine import Backend, get_engine
|
|
||||||
from searchbox.models import Match, MatchAny, MatchExclude, SearchRow
|
|
||||||
|
|
||||||
|
|
||||||
class TestEngineIntegration:
|
|
||||||
"""Integration tests for the complete engine workflow"""
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_complete_engine_setup(self):
|
|
||||||
"""Setup complete mocked engine environment"""
|
|
||||||
with (
|
|
||||||
patch(
|
|
||||||
"searchbox.engine.qdrant_engine.Settings"
|
|
||||||
) as mock_settings_class,
|
|
||||||
patch(
|
|
||||||
"searchbox.engine.qdrant_engine.AsyncQdrantClient"
|
|
||||||
) as mock_client_class,
|
|
||||||
):
|
|
||||||
# Setup settings
|
|
||||||
mock_settings = MagicMock()
|
|
||||||
mock_settings.url = "http://localhost:6333"
|
|
||||||
mock_settings.api_key = "test_api_key"
|
|
||||||
mock_settings_class.return_value = mock_settings
|
|
||||||
|
|
||||||
# Setup client with realistic response
|
|
||||||
mock_client = AsyncMock()
|
|
||||||
mock_client.search.return_value = [
|
|
||||||
models.ScoredPoint(
|
|
||||||
id="doc_1",
|
|
||||||
score=0.95,
|
|
||||||
payload={
|
|
||||||
"text": "Advanced Python programming techniques for data science",
|
|
||||||
"category": "programming",
|
|
||||||
"language": "python",
|
|
||||||
"difficulty": "advanced",
|
|
||||||
"tags": ["python", "data-science", "machine-learning"],
|
|
||||||
},
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
models.ScoredPoint(
|
|
||||||
id="doc_2",
|
|
||||||
score=0.87,
|
|
||||||
payload={
|
|
||||||
"text": "Rust systems programming for performance-critical applications",
|
|
||||||
"category": "programming",
|
|
||||||
"language": "rust",
|
|
||||||
"difficulty": "intermediate",
|
|
||||||
"tags": ["rust", "systems", "performance"],
|
|
||||||
},
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
models.ScoredPoint(
|
|
||||||
id="doc_3",
|
|
||||||
score=0.78,
|
|
||||||
payload={
|
|
||||||
"text": "Introduction to machine learning with Python",
|
|
||||||
"category": "programming",
|
|
||||||
"language": "python",
|
|
||||||
"difficulty": "beginner",
|
|
||||||
"tags": ["python", "machine-learning", "tutorial"],
|
|
||||||
},
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
mock_client_class.return_value = mock_client
|
|
||||||
|
|
||||||
yield {
|
|
||||||
"settings": mock_settings,
|
|
||||||
"client": mock_client,
|
|
||||||
"settings_class": mock_settings_class,
|
|
||||||
"client_class": mock_client_class,
|
|
||||||
}
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_complete_semantic_search_workflow(self, mock_complete_engine_setup):
|
|
||||||
"""Test the complete workflow from factory to results"""
|
|
||||||
mocks = mock_complete_engine_setup
|
|
||||||
|
|
||||||
# 1. Create engine through factory
|
|
||||||
engine = get_engine(Backend.QDRANT)
|
|
||||||
|
|
||||||
# 2. Prepare search parameters
|
|
||||||
query_vector = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
|
|
||||||
collection_name = "programming_docs"
|
|
||||||
search_conditions = [
|
|
||||||
Match(key="category", value="programming"),
|
|
||||||
MatchAny(key="language", any=["python", "rust"]),
|
|
||||||
MatchExclude(key="difficulty", exclude=["expert"]),
|
|
||||||
]
|
|
||||||
|
|
||||||
# 3. Execute semantic search
|
|
||||||
results = await engine.semantic_search(
|
|
||||||
embedding=query_vector,
|
|
||||||
collection=collection_name,
|
|
||||||
limit=5,
|
|
||||||
conditions=search_conditions,
|
|
||||||
threshold=0.7,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 4. Verify the complete flow
|
|
||||||
|
|
||||||
# Check that client.search was called with correct parameters
|
|
||||||
client_mock = mocks["client"]
|
|
||||||
client_mock.search.assert_called_once()
|
|
||||||
|
|
||||||
call_args = client_mock.search.call_args
|
|
||||||
assert call_args[1]["collection_name"] == collection_name
|
|
||||||
assert call_args[1]["query_vector"] == query_vector
|
|
||||||
assert call_args[1]["limit"] == 5
|
|
||||||
assert call_args[1]["score_threshold"] == 0.7
|
|
||||||
assert call_args[1]["with_payload"] is True
|
|
||||||
assert call_args[1]["with_vectors"] is False
|
|
||||||
|
|
||||||
# Verify conditions were transformed to Qdrant filter
|
|
||||||
qdrant_filter = call_args[1]["query_filter"]
|
|
||||||
assert isinstance(qdrant_filter, models.Filter)
|
|
||||||
assert len(qdrant_filter.must) == 3
|
|
||||||
|
|
||||||
# Check individual conditions
|
|
||||||
conditions = qdrant_filter.must
|
|
||||||
|
|
||||||
# Match condition
|
|
||||||
match_condition = next(c for c in conditions if c.key == "category")
|
|
||||||
assert isinstance(match_condition.match, models.MatchValue)
|
|
||||||
assert match_condition.match.value == "programming"
|
|
||||||
|
|
||||||
# MatchAny condition
|
|
||||||
match_any_condition = next(c for c in conditions if c.key == "language")
|
|
||||||
assert isinstance(match_any_condition.match, models.MatchAny)
|
|
||||||
assert match_any_condition.match.any == ["python", "rust"]
|
|
||||||
|
|
||||||
# MatchExclude condition
|
|
||||||
match_exclude_condition = next(c for c in conditions if c.key == "difficulty")
|
|
||||||
assert isinstance(match_exclude_condition.match, models.MatchExcept)
|
|
||||||
|
|
||||||
# 5. Verify results transformation
|
|
||||||
assert isinstance(results, list)
|
|
||||||
assert len(results) == 3
|
|
||||||
assert all(isinstance(result, SearchRow) for result in results)
|
|
||||||
|
|
||||||
# Check first result
|
|
||||||
assert results[0].chunk_id == "doc_1"
|
|
||||||
assert results[0].score == 0.95
|
|
||||||
assert (
|
|
||||||
results[0].payload["text"]
|
|
||||||
== "Advanced Python programming techniques for data science"
|
|
||||||
)
|
|
||||||
assert results[0].payload["category"] == "programming"
|
|
||||||
|
|
||||||
# Check second result
|
|
||||||
assert results[1].chunk_id == "doc_2"
|
|
||||||
assert results[1].score == 0.87
|
|
||||||
assert results[1].payload["language"] == "rust"
|
|
||||||
|
|
||||||
# Check third result
|
|
||||||
assert results[2].chunk_id == "doc_3"
|
|
||||||
assert results[2].score == 0.78
|
|
||||||
assert results[2].payload["difficulty"] == "beginner"
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_search_with_no_conditions(self, mock_complete_engine_setup):
|
|
||||||
"""Test semantic search without any conditions"""
|
|
||||||
engine = get_engine(Backend.QDRANT)
|
|
||||||
|
|
||||||
results = await engine.semantic_search(
|
|
||||||
embedding=[0.1, 0.2, 0.3], collection="test_collection"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Verify no filter was applied
|
|
||||||
client_mock = mock_complete_engine_setup["client"]
|
|
||||||
call_args = client_mock.search.call_args
|
|
||||||
assert call_args[1]["query_filter"] is None
|
|
||||||
|
|
||||||
# Results should still be transformed
|
|
||||||
assert len(results) == 3
|
|
||||||
assert all(isinstance(result, SearchRow) for result in results)
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_search_with_empty_conditions(self, mock_complete_engine_setup):
|
|
||||||
"""Test semantic search with empty conditions list"""
|
|
||||||
engine = get_engine(Backend.QDRANT)
|
|
||||||
|
|
||||||
results = await engine.semantic_search(
|
|
||||||
embedding=[0.1, 0.2, 0.3], collection="test_collection", conditions=[]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Verify no filter was applied
|
|
||||||
client_mock = mock_complete_engine_setup["client"]
|
|
||||||
call_args = client_mock.search.call_args
|
|
||||||
assert call_args[1]["query_filter"] is None
|
|
||||||
|
|
||||||
assert len(results) == 3
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_search_filters_null_payloads(self, mock_complete_engine_setup):
|
|
||||||
"""Test that results with null payloads are filtered out"""
|
|
||||||
# Override the mock response to include null payload
|
|
||||||
client_mock = mock_complete_engine_setup["client"]
|
|
||||||
client_mock.search.return_value = [
|
|
||||||
models.ScoredPoint(
|
|
||||||
id="valid_1",
|
|
||||||
score=0.95,
|
|
||||||
payload={"text": "Valid document"},
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
models.ScoredPoint(
|
|
||||||
id="invalid",
|
|
||||||
score=0.90,
|
|
||||||
payload=None, # This should be filtered out
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
models.ScoredPoint(
|
|
||||||
id="valid_2",
|
|
||||||
score=0.85,
|
|
||||||
payload={"text": "Another valid document"},
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
engine = get_engine(Backend.QDRANT)
|
|
||||||
results = await engine.semantic_search(
|
|
||||||
embedding=[0.1, 0.2, 0.3], collection="test_collection"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Should only have 2 results (null payload filtered out)
|
|
||||||
assert len(results) == 2
|
|
||||||
assert results[0].chunk_id == "valid_1"
|
|
||||||
assert results[1].chunk_id == "valid_2"
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_error_propagation_from_client(self, mock_complete_engine_setup):
|
|
||||||
"""Test that client errors are properly propagated"""
|
|
||||||
# Make the client raise an exception
|
|
||||||
client_mock = mock_complete_engine_setup["client"]
|
|
||||||
client_mock.search.side_effect = Exception("Qdrant connection timeout")
|
|
||||||
|
|
||||||
engine = get_engine(Backend.QDRANT)
|
|
||||||
|
|
||||||
with pytest.raises(Exception, match="Qdrant connection timeout"):
|
|
||||||
await engine.semantic_search(
|
|
||||||
embedding=[0.1, 0.2, 0.3], collection="test_collection"
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_search_with_named_vector(self, mock_complete_engine_setup):
|
|
||||||
"""Test semantic search with NamedVector instead of regular vector"""
|
|
||||||
engine = get_engine(Backend.QDRANT)
|
|
||||||
|
|
||||||
named_vector = models.NamedVector(
|
|
||||||
name="text_embedding", vector=[0.1, 0.2, 0.3, 0.4, 0.5]
|
|
||||||
)
|
|
||||||
|
|
||||||
results = await engine.semantic_search(
|
|
||||||
embedding=named_vector, # type: ignore - Testing duck typing
|
|
||||||
collection="test_collection",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Verify named vector was passed through
|
|
||||||
client_mock = mock_complete_engine_setup["client"]
|
|
||||||
call_args = client_mock.search.call_args
|
|
||||||
assert call_args[1]["query_vector"] == named_vector
|
|
||||||
|
|
||||||
assert len(results) == 3
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_search_parameter_defaults(self, mock_complete_engine_setup):
|
|
||||||
"""Test that default parameters are applied correctly"""
|
|
||||||
engine = get_engine(Backend.QDRANT)
|
|
||||||
|
|
||||||
await engine.semantic_search(
|
|
||||||
embedding=[0.1, 0.2, 0.3], collection="test_collection"
|
|
||||||
)
|
|
||||||
|
|
||||||
client_mock = mock_complete_engine_setup["client"]
|
|
||||||
call_args = client_mock.search.call_args
|
|
||||||
|
|
||||||
# Check defaults
|
|
||||||
assert call_args[1]["limit"] == 10 # default limit
|
|
||||||
assert call_args[1]["score_threshold"] is None # default threshold
|
|
||||||
assert call_args[1]["query_filter"] is None # default conditions
|
|
||||||
assert call_args[1]["with_payload"] is True
|
|
||||||
assert call_args[1]["with_vectors"] is False
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_multiple_engine_instances_independence(
|
|
||||||
self, mock_complete_engine_setup
|
|
||||||
):
|
|
||||||
"""Test that multiple engine instances work independently"""
|
|
||||||
# Create two engines
|
|
||||||
engine1 = get_engine(Backend.QDRANT)
|
|
||||||
engine2 = get_engine(Backend.QDRANT)
|
|
||||||
|
|
||||||
# Verify they are the same instance due to caching
|
|
||||||
assert engine1 is engine2
|
|
||||||
|
|
||||||
# Both should work with the same instance
|
|
||||||
results1 = await engine1.semantic_search(
|
|
||||||
embedding=[0.1, 0.2, 0.3], collection="collection1"
|
|
||||||
)
|
|
||||||
|
|
||||||
results2 = await engine2.semantic_search(
|
|
||||||
embedding=[0.4, 0.5, 0.6], collection="collection2"
|
|
||||||
)
|
|
||||||
|
|
||||||
assert len(results1) == 3
|
|
||||||
assert len(results2) == 3
|
|
||||||
|
|
||||||
# Verify client was called twice (same instance, multiple calls)
|
|
||||||
client_mock = mock_complete_engine_setup["client"]
|
|
||||||
assert client_mock.search.call_count == 2
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_large_result_set_handling(self, mock_complete_engine_setup):
|
|
||||||
"""Test handling of large result sets"""
|
|
||||||
# Create a large mock response
|
|
||||||
large_response = []
|
|
||||||
for i in range(100):
|
|
||||||
large_response.append(
|
|
||||||
models.ScoredPoint(
|
|
||||||
id=f"doc_{i}",
|
|
||||||
score=0.9 - (i * 0.001), # Decreasing scores
|
|
||||||
payload={"text": f"Document {i}", "index": i},
|
|
||||||
version=1,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
client_mock = mock_complete_engine_setup["client"]
|
|
||||||
client_mock.search.return_value = large_response
|
|
||||||
|
|
||||||
engine = get_engine(Backend.QDRANT)
|
|
||||||
results = await engine.semantic_search(
|
|
||||||
embedding=[0.1, 0.2, 0.3], collection="large_collection", limit=100
|
|
||||||
)
|
|
||||||
|
|
||||||
# Should handle all 100 results
|
|
||||||
assert len(results) == 100
|
|
||||||
assert results[0].chunk_id == "doc_0"
|
|
||||||
assert results[0].score == 0.9
|
|
||||||
assert results[99].chunk_id == "doc_99"
|
|
||||||
assert results[99].score == 0.801 # 0.9 - (99 * 0.001)
|
|
||||||
|
|
||||||
def test_engine_type_consistency(self):
|
|
||||||
"""Test that engine types are consistent across multiple calls"""
|
|
||||||
with (
|
|
||||||
patch("searchbox.engine.qdrant_engine.Settings"),
|
|
||||||
patch("searchbox.engine.qdrant_engine.AsyncQdrantClient"),
|
|
||||||
):
|
|
||||||
engines = [get_engine(Backend.QDRANT) for _ in range(5)]
|
|
||||||
|
|
||||||
# All should be the same instance due to caching
|
|
||||||
assert all(engine is engines[0] for engine in engines)
|
|
||||||
|
|
||||||
# All should be QdrantEngine instances
|
|
||||||
from searchbox.engine.qdrant_engine import QdrantEngine
|
|
||||||
|
|
||||||
assert all(isinstance(engine, QdrantEngine) for engine in engines)
|
|
||||||
@@ -1,505 +1,62 @@
|
|||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from uuid import uuid4
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from qdrant_client import models
|
|
||||||
from qdrant_client.models import ScoredPoint
|
|
||||||
|
|
||||||
from searchbox.engine.base_engine import BaseEngine
|
|
||||||
from searchbox.engine.qdrant_engine import QdrantEngine
|
from searchbox.engine.qdrant_engine import QdrantEngine
|
||||||
from searchbox.models import Match, MatchAny, MatchExclude, SearchRow
|
from searchbox.models import Chunk, Match, MatchAny, MatchExclude
|
||||||
|
|
||||||
|
|
||||||
class TestQdrantEngine:
|
@pytest.fixture(scope="module")
|
||||||
"""Test suite for QdrantEngine"""
|
def engine():
|
||||||
|
return QdrantEngine(":memory:")
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_client(self):
|
|
||||||
"""Create a mock Qdrant client"""
|
|
||||||
return AsyncMock()
|
|
||||||
|
|
||||||
@pytest.fixture
|
async def test_create_index(engine: QdrantEngine):
|
||||||
def mock_settings(self):
|
result = await engine.create_index("test_index", 3)
|
||||||
"""Create mock settings"""
|
|
||||||
settings = MagicMock()
|
|
||||||
settings.url = "http://localhost:6333"
|
|
||||||
settings.api_key = "test_api_key"
|
|
||||||
return settings
|
|
||||||
|
|
||||||
@pytest.fixture
|
assert result is True
|
||||||
def qdrant_engine(self, mock_client, mock_settings):
|
|
||||||
"""Create a QdrantEngine instance with mocked dependencies"""
|
|
||||||
with (
|
|
||||||
patch(
|
|
||||||
"searchbox.engine.qdrant_engine.Settings"
|
|
||||||
) as mock_settings_class,
|
|
||||||
patch(
|
|
||||||
"searchbox.engine.qdrant_engine.AsyncQdrantClient"
|
|
||||||
) as mock_client_class,
|
|
||||||
):
|
|
||||||
mock_settings_class.return_value = mock_settings
|
|
||||||
mock_client_class.return_value = mock_client
|
|
||||||
|
|
||||||
engine = QdrantEngine()
|
|
||||||
engine.client = mock_client # Ensure we use our mock
|
|
||||||
return engine
|
|
||||||
|
|
||||||
def test_inheritance(self, qdrant_engine):
|
async def test_upload_chunk(engine: QdrantEngine):
|
||||||
"""Test that QdrantEngine properly inherits from BaseEngine"""
|
chunk = Chunk.model_validate(
|
||||||
assert isinstance(qdrant_engine, BaseEngine)
|
{
|
||||||
assert isinstance(qdrant_engine, QdrantEngine)
|
"id": uuid4(),
|
||||||
|
"vector": [0.0, 0.1, 0.3],
|
||||||
def test_typing_parameters(self):
|
"payload": {
|
||||||
"""Test that QdrantEngine has correct generic type parameters"""
|
"page_content": "This is a test page content.",
|
||||||
# QdrantEngine should be BaseEngine[list[models.ScoredPoint], models.Filter]
|
"filename": "test.txt",
|
||||||
# This is verified by the type checker, but we can test the methods exist
|
"page": 1,
|
||||||
assert hasattr(QdrantEngine, "transform_conditions")
|
},
|
||||||
assert hasattr(QdrantEngine, "transform_response")
|
|
||||||
assert hasattr(QdrantEngine, "run_similarity_query")
|
|
||||||
|
|
||||||
def test_transform_conditions_none(self, qdrant_engine):
|
|
||||||
"""Test transform_conditions with None input"""
|
|
||||||
result = qdrant_engine.transform_conditions(None)
|
|
||||||
assert result is None
|
|
||||||
|
|
||||||
def test_transform_conditions_empty_list(self, qdrant_engine):
|
|
||||||
"""Test transform_conditions with empty list"""
|
|
||||||
result = qdrant_engine.transform_conditions([])
|
|
||||||
assert result is None
|
|
||||||
|
|
||||||
def test_transform_conditions_with_match(self, qdrant_engine):
|
|
||||||
"""Test transform_conditions with Match condition"""
|
|
||||||
conditions = [Match(key="category", value="document")]
|
|
||||||
|
|
||||||
result = qdrant_engine.transform_conditions(conditions)
|
|
||||||
|
|
||||||
assert isinstance(result, models.Filter)
|
|
||||||
assert len(result.must) == 1
|
|
||||||
|
|
||||||
condition = result.must[0]
|
|
||||||
assert isinstance(condition, models.FieldCondition)
|
|
||||||
assert condition.key == "category"
|
|
||||||
assert isinstance(condition.match, models.MatchValue)
|
|
||||||
assert condition.match.value == "document"
|
|
||||||
|
|
||||||
def test_transform_conditions_with_match_any(self, qdrant_engine):
|
|
||||||
"""Test transform_conditions with MatchAny condition"""
|
|
||||||
conditions = [MatchAny(key="tags", any=["python", "rust", "javascript"])]
|
|
||||||
|
|
||||||
result = qdrant_engine.transform_conditions(conditions)
|
|
||||||
|
|
||||||
assert isinstance(result, models.Filter)
|
|
||||||
assert len(result.must) == 1
|
|
||||||
|
|
||||||
condition = result.must[0]
|
|
||||||
assert isinstance(condition, models.FieldCondition)
|
|
||||||
assert condition.key == "tags"
|
|
||||||
assert isinstance(condition.match, models.MatchAny)
|
|
||||||
assert condition.match.any == ["python", "rust", "javascript"]
|
|
||||||
|
|
||||||
def test_transform_conditions_with_match_exclude(self, qdrant_engine):
|
|
||||||
"""Test transform_conditions with MatchExclude condition"""
|
|
||||||
conditions = [MatchExclude(key="status", exclude=["deleted", "archived"])]
|
|
||||||
|
|
||||||
result = qdrant_engine.transform_conditions(conditions)
|
|
||||||
|
|
||||||
assert isinstance(result, models.Filter)
|
|
||||||
assert len(result.must) == 1
|
|
||||||
|
|
||||||
condition = result.must[0]
|
|
||||||
assert isinstance(condition, models.FieldCondition)
|
|
||||||
assert condition.key == "status"
|
|
||||||
assert isinstance(condition.match, models.MatchExcept)
|
|
||||||
# MatchExcept uses 'except' parameter which conflicts with Python keyword
|
|
||||||
assert hasattr(condition.match, "except_")
|
|
||||||
|
|
||||||
def test_transform_conditions_multiple(self, qdrant_engine):
|
|
||||||
"""Test transform_conditions with multiple conditions"""
|
|
||||||
conditions = [
|
|
||||||
Match(key="type", value="article"),
|
|
||||||
MatchAny(key="language", any=["en", "es"]),
|
|
||||||
MatchExclude(key="status", exclude=["draft"]),
|
|
||||||
]
|
|
||||||
|
|
||||||
result = qdrant_engine.transform_conditions(conditions)
|
|
||||||
|
|
||||||
assert isinstance(result, models.Filter)
|
|
||||||
assert len(result.must) == 3
|
|
||||||
|
|
||||||
# Verify all conditions are FieldCondition instances
|
|
||||||
assert all(isinstance(cond, models.FieldCondition) for cond in result.must)
|
|
||||||
|
|
||||||
def test_transform_response_empty(self, qdrant_engine):
|
|
||||||
"""Test transform_response with empty results"""
|
|
||||||
response = []
|
|
||||||
result = qdrant_engine.transform_response(response)
|
|
||||||
assert result == []
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_create_index(self, qdrant_engine, mock_client):
|
|
||||||
"""Test create_index method"""
|
|
||||||
mock_client.create_collection.return_value = True
|
|
||||||
|
|
||||||
result = await qdrant_engine.create_index("test_collection", 384)
|
|
||||||
|
|
||||||
assert result is True
|
|
||||||
mock_client.create_collection.assert_called_once_with(
|
|
||||||
collection_name="test_collection",
|
|
||||||
vectors_config=models.VectorParams(
|
|
||||||
size=384, distance=models.Distance.COSINE
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_create_index_failure(self, qdrant_engine, mock_client):
|
|
||||||
"""Test create_index method when it fails"""
|
|
||||||
mock_client.create_collection.side_effect = Exception("Collection creation failed")
|
|
||||||
|
|
||||||
with pytest.raises(Exception, match="Collection creation failed"):
|
|
||||||
await qdrant_engine.create_index("failing_collection", 512)
|
|
||||||
|
|
||||||
def test_transform_chunk(self, qdrant_engine):
|
|
||||||
"""Test transform_chunk method"""
|
|
||||||
from searchbox.models import Chunk, ChunkData
|
|
||||||
|
|
||||||
chunk = Chunk(
|
|
||||||
id="test-chunk-1",
|
|
||||||
vector=[0.1, 0.2, 0.3, 0.4, 0.5],
|
|
||||||
payload=ChunkData(
|
|
||||||
page_content="This is test content",
|
|
||||||
filename="test_doc.pdf",
|
|
||||||
page=42
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
result = qdrant_engine.transform_chunk(chunk)
|
|
||||||
|
|
||||||
assert isinstance(result, models.PointStruct)
|
|
||||||
assert result.id == "test-chunk-1"
|
|
||||||
assert result.vector == [0.1, 0.2, 0.3, 0.4, 0.5]
|
|
||||||
assert result.payload == {
|
|
||||||
"page_content": "This is test content",
|
|
||||||
"filename": "test_doc.pdf",
|
|
||||||
"page": 42
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
result = await engine.upload_chunk("test_index", chunk)
|
||||||
async def test_run_upload_chunk(self, qdrant_engine, mock_client):
|
|
||||||
"""Test run_upload_chunk method"""
|
|
||||||
# Setup mock response
|
|
||||||
mock_response = MagicMock()
|
|
||||||
mock_response.status = models.UpdateStatus.ACKNOWLEDGED
|
|
||||||
mock_client.upsert.return_value = mock_response
|
|
||||||
|
|
||||||
# Create test point
|
assert result is True
|
||||||
test_point = models.PointStruct(
|
|
||||||
id="test-point-1",
|
|
||||||
vector=[0.1, 0.2, 0.3],
|
|
||||||
payload={"content": "test"}
|
|
||||||
)
|
|
||||||
|
|
||||||
result = await qdrant_engine.run_upload_chunk("test_index", test_point)
|
|
||||||
|
|
||||||
assert result is True
|
async def test_search_chunk(engine: QdrantEngine):
|
||||||
mock_client.upsert.assert_called_once_with(
|
result = await engine.semantic_search([0.0, 0.1, 0.3], "test_index")
|
||||||
collection_name="test_index",
|
|
||||||
points=[test_point]
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
assert len(result) == 1
|
||||||
async def test_run_upload_chunk_failure(self, qdrant_engine, mock_client):
|
|
||||||
"""Test run_upload_chunk method when upload fails"""
|
|
||||||
# Setup mock response with failure status
|
|
||||||
mock_response = MagicMock()
|
|
||||||
mock_response.status = models.UpdateStatus.COMPLETED # Not ACKNOWLEDGED
|
|
||||||
mock_client.upsert.return_value = mock_response
|
|
||||||
|
|
||||||
test_point = models.PointStruct(
|
first_result = result[0]
|
||||||
id="test-point-1",
|
assert first_result.chunk_id is not None
|
||||||
vector=[0.1, 0.2, 0.3],
|
assert first_result.score > 0.9
|
||||||
payload={"content": "test"}
|
assert first_result.payload == {
|
||||||
)
|
"page_content": "This is a test page content.",
|
||||||
|
"filename": "test.txt",
|
||||||
|
"page": 1,
|
||||||
|
}
|
||||||
|
|
||||||
result = await qdrant_engine.run_upload_chunk("test_index", test_point)
|
|
||||||
|
|
||||||
assert result is False
|
async def test_search_chunk_with_conditions(engine: QdrantEngine):
|
||||||
|
conditions = [
|
||||||
|
Match(key="filename", value="test.md"),
|
||||||
|
MatchAny(key="filename", any=["test.md", "test.docx"]),
|
||||||
|
MatchExclude(key="filename", exclude=["test.txt"]),
|
||||||
|
]
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
result = await engine.semantic_search(
|
||||||
async def test_upload_chunk_integration(self, qdrant_engine, mock_client):
|
[0.0, 0.1, 0.3], "test_index", conditions=conditions
|
||||||
"""Test the complete upload_chunk workflow"""
|
)
|
||||||
from searchbox.models import Chunk, ChunkData
|
|
||||||
|
|
||||||
# Setup mock response
|
assert len(result) == 0
|
||||||
mock_response = MagicMock()
|
|
||||||
mock_response.status = models.UpdateStatus.ACKNOWLEDGED
|
|
||||||
mock_client.upsert.return_value = mock_response
|
|
||||||
|
|
||||||
chunk = Chunk(
|
|
||||||
id="integration-test-chunk",
|
|
||||||
vector=[0.5, 0.4, 0.3, 0.2, 0.1],
|
|
||||||
payload=ChunkData(
|
|
||||||
page_content="Integration test content",
|
|
||||||
filename="integration_test.pdf",
|
|
||||||
page=1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
result = await qdrant_engine.upload_chunk("integration_collection", chunk)
|
|
||||||
|
|
||||||
assert result is True
|
|
||||||
# Verify the complete workflow: transform_chunk -> run_upload_chunk
|
|
||||||
mock_client.upsert.assert_called_once()
|
|
||||||
args, kwargs = mock_client.upsert.call_args
|
|
||||||
|
|
||||||
assert kwargs["collection_name"] == "integration_collection"
|
|
||||||
assert len(kwargs["points"]) == 1
|
|
||||||
|
|
||||||
uploaded_point = kwargs["points"][0]
|
|
||||||
assert uploaded_point.id == "integration-test-chunk"
|
|
||||||
assert uploaded_point.vector == [0.5, 0.4, 0.3, 0.2, 0.1]
|
|
||||||
assert uploaded_point.payload == {
|
|
||||||
"page_content": "Integration test content",
|
|
||||||
"filename": "integration_test.pdf",
|
|
||||||
"page": 1
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_transform_response_with_scored_points(self, qdrant_engine):
|
|
||||||
"""Test transform_response with valid ScoredPoint objects"""
|
|
||||||
response = [
|
|
||||||
ScoredPoint(
|
|
||||||
id=1,
|
|
||||||
score=0.95,
|
|
||||||
payload={"text": "First document", "category": "tech"},
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
ScoredPoint(
|
|
||||||
id=2,
|
|
||||||
score=0.87,
|
|
||||||
payload={"text": "Second document", "category": "science"},
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
result = qdrant_engine.transform_response(response)
|
|
||||||
|
|
||||||
assert isinstance(result, list)
|
|
||||||
assert len(result) == 2
|
|
||||||
|
|
||||||
# Check first result
|
|
||||||
assert isinstance(result[0], SearchRow)
|
|
||||||
assert result[0].chunk_id == "1"
|
|
||||||
assert result[0].score == 0.95
|
|
||||||
assert result[0].payload == {"text": "First document", "category": "tech"}
|
|
||||||
|
|
||||||
# Check second result
|
|
||||||
assert isinstance(result[1], SearchRow)
|
|
||||||
assert result[1].chunk_id == "2"
|
|
||||||
assert result[1].score == 0.87
|
|
||||||
assert result[1].payload == {"text": "Second document", "category": "science"}
|
|
||||||
|
|
||||||
def test_transform_response_filters_none_payload(self, qdrant_engine):
|
|
||||||
"""Test transform_response filters out points with None payload"""
|
|
||||||
response = [
|
|
||||||
ScoredPoint(
|
|
||||||
id=1, score=0.95, payload={"text": "Valid document"}, version=1
|
|
||||||
),
|
|
||||||
ScoredPoint(
|
|
||||||
id=2,
|
|
||||||
score=0.87,
|
|
||||||
payload=None, # This should be filtered out
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
ScoredPoint(
|
|
||||||
id=3, score=0.75, payload={"text": "Another valid document"}, version=1
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
result = qdrant_engine.transform_response(response)
|
|
||||||
|
|
||||||
assert isinstance(result, list)
|
|
||||||
assert len(result) == 2 # Only 2 valid results
|
|
||||||
assert result[0].chunk_id == "1"
|
|
||||||
assert result[1].chunk_id == "3"
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_run_similarity_query_basic(self, qdrant_engine, mock_client):
|
|
||||||
"""Test run_similarity_query with basic parameters"""
|
|
||||||
# Setup mock response
|
|
||||||
mock_response = [
|
|
||||||
ScoredPoint(id=1, score=0.9, payload={"text": "Test document"}, version=1)
|
|
||||||
]
|
|
||||||
mock_client.search.return_value = mock_response
|
|
||||||
|
|
||||||
embedding = [0.1, 0.2, 0.3, 0.4, 0.5]
|
|
||||||
collection = "test_collection"
|
|
||||||
|
|
||||||
result = await qdrant_engine.run_similarity_query(
|
|
||||||
embedding=embedding, collection=collection
|
|
||||||
)
|
|
||||||
|
|
||||||
# Verify client.search was called with correct parameters
|
|
||||||
mock_client.search.assert_called_once_with(
|
|
||||||
collection_name=collection,
|
|
||||||
query_vector=embedding,
|
|
||||||
query_filter=None,
|
|
||||||
limit=10, # default
|
|
||||||
with_payload=True,
|
|
||||||
with_vectors=False,
|
|
||||||
score_threshold=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result == mock_response
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_run_similarity_query_with_all_parameters(
|
|
||||||
self, qdrant_engine, mock_client
|
|
||||||
):
|
|
||||||
"""Test run_similarity_query with all parameters"""
|
|
||||||
mock_response = []
|
|
||||||
mock_client.search.return_value = mock_response
|
|
||||||
|
|
||||||
embedding = [0.1, 0.2, 0.3]
|
|
||||||
collection = "test_collection"
|
|
||||||
limit = 5
|
|
||||||
conditions = models.Filter(must=[])
|
|
||||||
threshold = 0.75
|
|
||||||
|
|
||||||
result = await qdrant_engine.run_similarity_query(
|
|
||||||
embedding=embedding,
|
|
||||||
collection=collection,
|
|
||||||
limit=limit,
|
|
||||||
conditions=conditions,
|
|
||||||
threshold=threshold,
|
|
||||||
)
|
|
||||||
|
|
||||||
mock_client.search.assert_called_once_with(
|
|
||||||
collection_name=collection,
|
|
||||||
query_vector=embedding,
|
|
||||||
query_filter=conditions,
|
|
||||||
limit=limit,
|
|
||||||
with_payload=True,
|
|
||||||
with_vectors=False,
|
|
||||||
score_threshold=threshold,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result == mock_response
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_run_similarity_query_with_named_vector(
|
|
||||||
self, qdrant_engine, mock_client
|
|
||||||
):
|
|
||||||
"""Test run_similarity_query with NamedVector"""
|
|
||||||
mock_response = []
|
|
||||||
mock_client.search.return_value = mock_response
|
|
||||||
|
|
||||||
named_vector = models.NamedVector(name="text", vector=[0.1, 0.2, 0.3])
|
|
||||||
collection = "test_collection"
|
|
||||||
|
|
||||||
result = await qdrant_engine.run_similarity_query(
|
|
||||||
embedding=named_vector, collection=collection
|
|
||||||
)
|
|
||||||
|
|
||||||
mock_client.search.assert_called_once_with(
|
|
||||||
collection_name=collection,
|
|
||||||
query_vector=named_vector,
|
|
||||||
query_filter=None,
|
|
||||||
limit=10,
|
|
||||||
with_payload=True,
|
|
||||||
with_vectors=False,
|
|
||||||
score_threshold=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result == mock_response
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_semantic_search_integration(self, qdrant_engine, mock_client):
|
|
||||||
"""Test the full semantic_search flow through QdrantEngine"""
|
|
||||||
# Setup mock response
|
|
||||||
mock_search_response = [
|
|
||||||
ScoredPoint(
|
|
||||||
id=1,
|
|
||||||
score=0.95,
|
|
||||||
payload={"text": "Python programming guide", "category": "tech"},
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
ScoredPoint(
|
|
||||||
id=2,
|
|
||||||
score=0.87,
|
|
||||||
payload={"text": "Rust systems programming", "category": "tech"},
|
|
||||||
version=1,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
mock_client.search.return_value = mock_search_response
|
|
||||||
|
|
||||||
# Test data
|
|
||||||
vector = [0.1, 0.2, 0.3, 0.4, 0.5]
|
|
||||||
collection = "documents"
|
|
||||||
conditions = [
|
|
||||||
Match(key="category", value="tech"),
|
|
||||||
MatchAny(key="language", any=["python", "rust"]),
|
|
||||||
]
|
|
||||||
|
|
||||||
result = await qdrant_engine.semantic_search(
|
|
||||||
embedding=vector,
|
|
||||||
collection=collection,
|
|
||||||
limit=5,
|
|
||||||
conditions=conditions,
|
|
||||||
threshold=0.8,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Verify the search was called with transformed conditions
|
|
||||||
assert mock_client.search.called
|
|
||||||
call_args = mock_client.search.call_args
|
|
||||||
assert call_args[1]["collection_name"] == collection
|
|
||||||
assert call_args[1]["query_vector"] == vector
|
|
||||||
assert call_args[1]["limit"] == 5
|
|
||||||
assert call_args[1]["score_threshold"] == 0.8
|
|
||||||
assert isinstance(call_args[1]["query_filter"], models.Filter)
|
|
||||||
|
|
||||||
# Verify the response was transformed correctly
|
|
||||||
assert isinstance(result, list)
|
|
||||||
assert len(result) == 2
|
|
||||||
assert all(isinstance(row, SearchRow) for row in result)
|
|
||||||
assert result[0].chunk_id == "1"
|
|
||||||
assert result[0].score == 0.95
|
|
||||||
assert result[1].chunk_id == "2"
|
|
||||||
assert result[1].score == 0.87
|
|
||||||
|
|
||||||
def test_initialization_with_settings(self, mock_settings):
|
|
||||||
"""Test QdrantEngine initialization uses settings correctly"""
|
|
||||||
with (
|
|
||||||
patch(
|
|
||||||
"searchbox.engine.qdrant_engine.Settings"
|
|
||||||
) as mock_settings_class,
|
|
||||||
patch(
|
|
||||||
"searchbox.engine.qdrant_engine.AsyncQdrantClient"
|
|
||||||
) as mock_client_class,
|
|
||||||
):
|
|
||||||
mock_settings_class.return_value = mock_settings
|
|
||||||
mock_client = AsyncMock()
|
|
||||||
mock_client_class.return_value = mock_client
|
|
||||||
|
|
||||||
engine = QdrantEngine()
|
|
||||||
|
|
||||||
# Verify Settings was instantiated
|
|
||||||
mock_settings_class.assert_called_once()
|
|
||||||
|
|
||||||
# Verify AsyncQdrantClient was created with correct parameters
|
|
||||||
mock_client_class.assert_called_once_with(
|
|
||||||
url=mock_settings.url, api_key=mock_settings.api_key
|
|
||||||
)
|
|
||||||
|
|
||||||
assert engine.client == mock_client
|
|
||||||
assert engine.settings == mock_settings
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_client_search_exception_propagation(
|
|
||||||
self, qdrant_engine, mock_client
|
|
||||||
):
|
|
||||||
"""Test that exceptions from client.search are properly propagated"""
|
|
||||||
# Setup mock to raise an exception
|
|
||||||
mock_client.search.side_effect = Exception("Qdrant connection failed")
|
|
||||||
|
|
||||||
with pytest.raises(Exception, match="Qdrant connection failed"):
|
|
||||||
await qdrant_engine.run_similarity_query(
|
|
||||||
embedding=[0.1, 0.2, 0.3], collection="test_collection"
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
import subprocess
|
|
||||||
import time
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from fastembed import TextEmbedding
|
|
||||||
from qdrant_client import QdrantClient
|
|
||||||
from qdrant_client.models import Distance, PointStruct, VectorParams
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def embedding_model():
|
|
||||||
return TextEmbedding()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def qdrant_client(embedding_model: TextEmbedding):
|
|
||||||
client = QdrantClient(":memory:")
|
|
||||||
|
|
||||||
documents: list[str] = [
|
|
||||||
"Rick es el mas guapo",
|
|
||||||
"Los pulpos tienen tres corazones y sangre azul",
|
|
||||||
"Las cucarachas pueden vivir hasta una semana sin cabeza",
|
|
||||||
"Los koalas tienen huellas dactilares casi idénticas a las humanas",
|
|
||||||
"La miel nunca se echa a perder, incluso después de miles de años",
|
|
||||||
]
|
|
||||||
embeddings = list(embedding_model.embed(documents))
|
|
||||||
size = len(embeddings[0])
|
|
||||||
|
|
||||||
_ = client.recreate_collection(
|
|
||||||
collection_name="dummy_collection",
|
|
||||||
vectors_config=VectorParams(distance=Distance.COSINE, size=size),
|
|
||||||
)
|
|
||||||
|
|
||||||
for idx, (emb, document) in enumerate(zip(embeddings, documents)):
|
|
||||||
_ = client.upsert(
|
|
||||||
collection_name="dummy_collection",
|
|
||||||
points=[
|
|
||||||
PointStruct(id=idx, vector=emb.tolist(), payload={"text": document})
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
yield client
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
|
||||||
def run_mcp():
|
|
||||||
# Start the MCP server in the background
|
|
||||||
process = subprocess.Popen(
|
|
||||||
["uv", "run", "searchbox-mcp"],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Give the server more time to start up properly
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
# Check if process is still running
|
|
||||||
if process.poll() is not None:
|
|
||||||
stdout, stderr = process.communicate()
|
|
||||||
pytest.fail(f"MCP server failed to start. stdout: {stdout}, stderr: {stderr}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
yield "http://localhost:8000/sse"
|
|
||||||
finally:
|
|
||||||
# Clean up the process when tests are done
|
|
||||||
process.terminate()
|
|
||||||
try:
|
|
||||||
_ = process.wait(timeout=5)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
process.kill()
|
|
||||||
_ = process.wait()
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
"""Tests for the MCP server implementation."""
|
|
||||||
|
|
||||||
import json
|
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from fastembed import TextEmbedding
|
|
||||||
from fastmcp import Client
|
|
||||||
from mcp.types import TextContent
|
|
||||||
|
|
||||||
|
|
||||||
class TestMCPServer:
|
|
||||||
"""Test the MCP server implementation."""
|
|
||||||
|
|
||||||
def test_server_import(self):
|
|
||||||
"""Test that MCP server can be imported successfully."""
|
|
||||||
from searchbox.mcp_server import server
|
|
||||||
|
|
||||||
assert hasattr(server, 'mcp')
|
|
||||||
assert hasattr(server, 'engine')
|
|
||||||
|
|
||||||
def test_server_initialization(self):
|
|
||||||
"""Test that the MCP server initializes correctly."""
|
|
||||||
from searchbox.mcp_server import server
|
|
||||||
from searchbox.engine import Backend
|
|
||||||
|
|
||||||
# Verify server module attributes exist
|
|
||||||
assert hasattr(server, 'mcp')
|
|
||||||
assert hasattr(server, 'engine')
|
|
||||||
|
|
||||||
# The engine should be created during module import
|
|
||||||
# We can't easily test the exact call without complex mocking
|
|
||||||
# but we can verify the engine exists and is properly typed
|
|
||||||
assert server.engine is not None
|
|
||||||
|
|
||||||
def test_run_function_exists(self):
|
|
||||||
"""Test that the run function exists in the package init."""
|
|
||||||
from searchbox.mcp_server import run
|
|
||||||
|
|
||||||
assert callable(run)
|
|
||||||
|
|
||||||
def test_run_function_signature(self):
|
|
||||||
"""Test that run function has correct signature and docstring."""
|
|
||||||
from searchbox.mcp_server import run
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
# Check function signature
|
|
||||||
sig = inspect.signature(run)
|
|
||||||
params = list(sig.parameters.values())
|
|
||||||
|
|
||||||
assert len(params) == 1
|
|
||||||
assert params[0].name == "transport"
|
|
||||||
assert params[0].default == "sse"
|
|
||||||
|
|
||||||
# Check docstring
|
|
||||||
assert run.__doc__ is not None
|
|
||||||
assert "transport" in run.__doc__.lower()
|
|
||||||
|
|
||||||
def test_run_function_type_annotations(self):
|
|
||||||
"""Test that run function has proper type annotations."""
|
|
||||||
from searchbox.mcp_server import run
|
|
||||||
|
|
||||||
# Verify function exists and is callable
|
|
||||||
assert callable(run)
|
|
||||||
|
|
||||||
# The function should accept Transport type
|
|
||||||
import inspect
|
|
||||||
sig = inspect.signature(run)
|
|
||||||
assert "transport" in sig.parameters
|
|
||||||
|
|
||||||
|
|
||||||
class TestMCPIntegration:
|
|
||||||
"""Integration tests for the MCP server."""
|
|
||||||
|
|
||||||
async def test_call_tool(self, embedding_model: TextEmbedding, run_mcp: str):
|
|
||||||
"""Test calling the semantic search tool via MCP."""
|
|
||||||
input = "Quien es el mas guapo?"
|
|
||||||
collection = "dummy_collection"
|
|
||||||
|
|
||||||
embedding: list[float] = list(embedding_model.embed(input))[0].tolist()
|
|
||||||
|
|
||||||
client = Client(run_mcp)
|
|
||||||
|
|
||||||
async with client:
|
|
||||||
name = "semantic_search"
|
|
||||||
body = {"embedding": embedding, "collection": collection}
|
|
||||||
result = await client.call_tool(name, body)
|
|
||||||
|
|
||||||
content_block = result.content[0]
|
|
||||||
|
|
||||||
assert isinstance(content_block, TextContent)
|
|
||||||
|
|
||||||
deserialized_result = json.loads(content_block.text)
|
|
||||||
|
|
||||||
top_result = deserialized_result[0]
|
|
||||||
|
|
||||||
assert top_result["chunk_id"] == "0"
|
|
||||||
assert top_result["score"] > 0.7
|
|
||||||
assert top_result["payload"] == {"text": "Rick es el mas guapo"}
|
|
||||||
|
|
||||||
def test_semantic_search_tool_registration(self):
|
|
||||||
"""Test that semantic_search tool registration is accessible."""
|
|
||||||
from searchbox.mcp_server.server import mcp
|
|
||||||
|
|
||||||
# Just verify the mcp object exists and is properly configured
|
|
||||||
# The actual tool registration happens during import
|
|
||||||
assert mcp is not None
|
|
||||||
assert hasattr(mcp, 'tool') # Has the decorator method
|
|
||||||
|
|
||||||
def test_server_module_attributes(self):
|
|
||||||
"""Test that server module has expected attributes."""
|
|
||||||
from searchbox.mcp_server import server
|
|
||||||
|
|
||||||
assert hasattr(server, 'mcp')
|
|
||||||
assert hasattr(server, 'engine')
|
|
||||||
|
|
||||||
# Verify mcp is a FastMCP instance
|
|
||||||
from fastmcp import FastMCP
|
|
||||||
assert isinstance(server.mcp, FastMCP)
|
|
||||||
|
|
||||||
def test_package_init_exports(self):
|
|
||||||
"""Test that package __init__ exports the run function."""
|
|
||||||
from searchbox.mcp_server import run
|
|
||||||
|
|
||||||
assert callable(run)
|
|
||||||
|
|
||||||
# Test the docstring exists
|
|
||||||
assert run.__doc__ is not None
|
|
||||||
assert "transport" in run.__doc__.lower()
|
|
||||||
33
tests/test_mcp/test_qdrant_mcp.py
Normal file
33
tests/test_mcp/test_qdrant_mcp.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import json
|
||||||
|
import pytest
|
||||||
|
from fastmcp import Client
|
||||||
|
from fastembed import TextEmbedding
|
||||||
|
|
||||||
|
from searchbox.mcp_server.server import mcp
|
||||||
|
|
||||||
|
embedding_model = TextEmbedding()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def mcp_client():
|
||||||
|
async with Client(mcp) as client:
|
||||||
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
async def test_mcp_qdrant_backend(mcp_client):
|
||||||
|
embedding = list(embedding_model.embed("Quien es el mas guapo"))[0].tolist()
|
||||||
|
|
||||||
|
result = await mcp_client.call_tool(
|
||||||
|
name="get_information",
|
||||||
|
arguments={
|
||||||
|
"query": "dummy value",
|
||||||
|
"collection": "dummy_collection",
|
||||||
|
"embedding": embedding,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
content = json.loads(result.content[0].text)[0]
|
||||||
|
|
||||||
|
assert content["chunk_id"] == "0"
|
||||||
|
assert content["score"] >= 0.7
|
||||||
|
assert content["payload"] == {"text": "Rick es el mas guapo"}
|
||||||
716
uv.lock
generated
716
uv.lock
generated
@@ -2,61 +2,6 @@ version = 1
|
|||||||
revision = 3
|
revision = 3
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aiohappyeyeballs"
|
|
||||||
version = "2.6.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aiohttp"
|
|
||||||
version = "3.12.15"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "aiohappyeyeballs" },
|
|
||||||
{ name = "aiosignal" },
|
|
||||||
{ name = "attrs" },
|
|
||||||
{ name = "frozenlist" },
|
|
||||||
{ name = "multidict" },
|
|
||||||
{ name = "propcache" },
|
|
||||||
{ name = "yarl" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aiosignal"
|
|
||||||
version = "1.4.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "frozenlist" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "annotated-types"
|
name = "annotated-types"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -100,81 +45,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/0e/aa/91355b5f539caf1b94f0e66ff1e4ee39373b757fce08204981f7829ede51/authlib-1.6.4-py2.py3-none-any.whl", hash = "sha256:39313d2a2caac3ecf6d8f95fbebdfd30ae6ea6ae6a6db794d976405fdd9aa796", size = 243076, upload-time = "2025-09-17T09:59:22.259Z" },
|
{ url = "https://files.pythonhosted.org/packages/0e/aa/91355b5f539caf1b94f0e66ff1e4ee39373b757fce08204981f7829ede51/authlib-1.6.4-py2.py3-none-any.whl", hash = "sha256:39313d2a2caac3ecf6d8f95fbebdfd30ae6ea6ae6a6db794d976405fdd9aa796", size = 243076, upload-time = "2025-09-17T09:59:22.259Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "azure-ai-inference"
|
|
||||||
version = "1.0.0b9"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "azure-core" },
|
|
||||||
{ name = "isodate" },
|
|
||||||
{ name = "typing-extensions" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/6a/ed85592e5c64e08c291992f58b1a94dab6869f28fb0f40fd753dced73ba6/azure_ai_inference-1.0.0b9.tar.gz", hash = "sha256:1feb496bd84b01ee2691befc04358fa25d7c344d8288e99364438859ad7cd5a4", size = 182408, upload-time = "2025-02-15T00:37:28.464Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4f/0f/27520da74769db6e58327d96c98e7b9a07ce686dff582c9a5ec60b03f9dd/azure_ai_inference-1.0.0b9-py3-none-any.whl", hash = "sha256:49823732e674092dad83bb8b0d1b65aa73111fab924d61349eb2a8cdc0493990", size = 124885, upload-time = "2025-02-15T00:37:29.964Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.optional-dependencies]
|
|
||||||
opentelemetry = [
|
|
||||||
{ name = "azure-core-tracing-opentelemetry" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "azure-core"
|
|
||||||
version = "1.35.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "requests" },
|
|
||||||
{ name = "six" },
|
|
||||||
{ name = "typing-extensions" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/15/6b/2653adc0f33adba8f11b1903701e6b1c10d34ce5d8e25dfa13a422f832b0/azure_core-1.35.1.tar.gz", hash = "sha256:435d05d6df0fff2f73fb3c15493bb4721ede14203f1ff1382aa6b6b2bdd7e562", size = 345290, upload-time = "2025-09-11T22:58:04.481Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/27/52/805980aa1ba18282077c484dba634ef0ede1e84eec8be9c92b2e162d0ed6/azure_core-1.35.1-py3-none-any.whl", hash = "sha256:12da0c9e08e48e198f9158b56ddbe33b421477e1dc98c2e1c8f9e254d92c468b", size = 211800, upload-time = "2025-09-11T22:58:06.281Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "azure-core-tracing-opentelemetry"
|
|
||||||
version = "1.0.0b12"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "azure-core" },
|
|
||||||
{ name = "opentelemetry-api" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/7f/5de13a331a5f2919417819cc37dcf7c897018f02f83aa82b733e6629a6a6/azure_core_tracing_opentelemetry-1.0.0b12.tar.gz", hash = "sha256:bb454142440bae11fd9d68c7c1d67ae38a1756ce808c5e4d736730a7b4b04144", size = 26010, upload-time = "2025-03-21T00:18:37.346Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/76/5e/97a471f66935e7f89f521d0e11ae49c7f0871ca38f5c319dccae2155c8d8/azure_core_tracing_opentelemetry-1.0.0b12-py3-none-any.whl", hash = "sha256:38fd42709f1cc4bbc4f2797008b1c30a6a01617e49910c05daa3a0d0c65053ac", size = 11962, upload-time = "2025-03-21T00:18:38.581Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "azure-cosmos"
|
|
||||||
version = "4.9.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "azure-core" },
|
|
||||||
{ name = "typing-extensions" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/be/7c/a4e7810f85e7f83d94265ef5ff0fb1efad55a768de737d940151ea2eec45/azure_cosmos-4.9.0.tar.gz", hash = "sha256:c70db4cbf55b0ff261ed7bb8aa325a5dfa565d3c6eaa43d75d26ae5e2ad6d74f", size = 1824155, upload-time = "2024-11-19T04:09:30.195Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/61/dc/380f843744535497acd0b85aacb59565c84fc28bf938c8d6e897a858cd95/azure_cosmos-4.9.0-py3-none-any.whl", hash = "sha256:3b60eaa01a16a857d0faf0cec304bac6fa8620a81bc268ce760339032ef617fe", size = 303157, upload-time = "2024-11-19T04:09:32.148Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "azure-identity"
|
|
||||||
version = "1.25.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "azure-core" },
|
|
||||||
{ name = "cryptography" },
|
|
||||||
{ name = "msal" },
|
|
||||||
{ name = "msal-extensions" },
|
|
||||||
{ name = "typing-extensions" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/9e/4c9682a286c3c89e437579bd9f64f311020e5125c1321fd3a653166b5716/azure_identity-1.25.0.tar.gz", hash = "sha256:4177df34d684cddc026e6cf684e1abb57767aa9d84e7f2129b080ec45eee7733", size = 278507, upload-time = "2025-09-12T01:30:04.418Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/75/54/81683b6756676a22e037b209695b08008258e603f7e47c56834029c5922a/azure_identity-1.25.0-py3-none-any.whl", hash = "sha256:becaec086bbdf8d1a6aa4fb080c2772a0f824a97d50c29637ec8cc4933f1e82d", size = 190861, upload-time = "2025-09-12T01:30:06.474Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2025.8.3"
|
version = "2025.8.3"
|
||||||
@@ -425,15 +295,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/f0/8b/2c95f0645c6f40211896375e6fa51f504b8ccb29c21f6ae661fe87ab044e/cyclopts-3.24.0-py3-none-any.whl", hash = "sha256:809d04cde9108617106091140c3964ee6fceb33cecdd537f7ffa360bde13ed71", size = 86154, upload-time = "2025-09-08T15:40:56.41Z" },
|
{ url = "https://files.pythonhosted.org/packages/f0/8b/2c95f0645c6f40211896375e6fa51f504b8ccb29c21f6ae661fe87ab044e/cyclopts-3.24.0-py3-none-any.whl", hash = "sha256:809d04cde9108617106091140c3964ee6fceb33cecdd537f7ffa360bde13ed71", size = 86154, upload-time = "2025-09-08T15:40:56.41Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "distro"
|
|
||||||
version = "1.9.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
|
|
||||||
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]]
|
[[package]]
|
||||||
name = "dnspython"
|
name = "dnspython"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@@ -544,49 +405,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ee/1b/00a78aa2e8fbd63f9af08c9c19e6deb3d5d66b4dda677a0f61654680ee89/flatbuffers-25.9.23-py2.py3-none-any.whl", hash = "sha256:255538574d6cb6d0a79a17ec8bc0d30985913b87513a01cce8bcdb6b4c44d0e2", size = 30869, upload-time = "2025-09-24T05:25:28.912Z" },
|
{ url = "https://files.pythonhosted.org/packages/ee/1b/00a78aa2e8fbd63f9af08c9c19e6deb3d5d66b4dda677a0f61654680ee89/flatbuffers-25.9.23-py2.py3-none-any.whl", hash = "sha256:255538574d6cb6d0a79a17ec8bc0d30985913b87513a01cce8bcdb6b4c44d0e2", size = 30869, upload-time = "2025-09-24T05:25:28.912Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "frozenlist"
|
|
||||||
version = "1.7.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsspec"
|
name = "fsspec"
|
||||||
version = "2025.9.0"
|
version = "2025.9.0"
|
||||||
@@ -789,18 +607,6 @@ 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/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "importlib-metadata"
|
|
||||||
version = "8.7.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "zipp" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@@ -819,63 +625,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" },
|
{ url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jiter"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/9d/c0/a3bb4cc13aced219dd18191ea66e874266bd8aa7b96744e495e1c733aa2d/jiter-0.11.0.tar.gz", hash = "sha256:1d9637eaf8c1d6a63d6562f2a6e5ab3af946c66037eb1b894e8fad75422266e4", size = 167094, upload-time = "2025-09-15T09:20:38.212Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/97/c4/d530e514d0f4f29b2b68145e7b389cbc7cac7f9c8c23df43b04d3d10fa3e/jiter-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:4441a91b80a80249f9a6452c14b2c24708f139f64de959943dfeaa6cb915e8eb", size = 305021, upload-time = "2025-09-15T09:19:43.523Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7a/77/796a19c567c5734cbfc736a6f987affc0d5f240af8e12063c0fb93990ffa/jiter-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ff85fc6d2a431251ad82dbd1ea953affb5a60376b62e7d6809c5cd058bb39471", size = 314384, upload-time = "2025-09-15T09:19:44.849Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/14/9c/824334de0b037b91b6f3fa9fe5a191c83977c7ec4abe17795d3cb6d174cf/jiter-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5e86126d64706fd28dfc46f910d496923c6f95b395138c02d0e252947f452bd", size = 337389, upload-time = "2025-09-15T09:19:46.094Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a2/95/ed4feab69e6cf9b2176ea29d4ef9d01a01db210a3a2c8a31a44ecdc68c38/jiter-0.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad8bd82165961867a10f52010590ce0b7a8c53da5ddd8bbb62fef68c181b921", size = 360519, upload-time = "2025-09-15T09:19:47.494Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b5/0c/2ad00f38d3e583caba3909d95b7da1c3a7cd82c0aa81ff4317a8016fb581/jiter-0.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b42c2cd74273455ce439fd9528db0c6e84b5623cb74572305bdd9f2f2961d3df", size = 487198, upload-time = "2025-09-15T09:19:49.116Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ea/8b/919b64cf3499b79bdfba6036da7b0cac5d62d5c75a28fb45bad7819e22f0/jiter-0.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0062dab98172dd0599fcdbf90214d0dcde070b1ff38a00cc1b90e111f071982", size = 377835, upload-time = "2025-09-15T09:19:50.468Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/29/7f/8ebe15b6e0a8026b0d286c083b553779b4dd63db35b43a3f171b544de91d/jiter-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb948402821bc76d1f6ef0f9e19b816f9b09f8577844ba7140f0b6afe994bc64", size = 347655, upload-time = "2025-09-15T09:19:51.726Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8e/64/332127cef7e94ac75719dda07b9a472af6158ba819088d87f17f3226a769/jiter-0.11.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25a5b1110cca7329fd0daf5060faa1234be5c11e988948e4f1a1923b6a457fe1", size = 386135, upload-time = "2025-09-15T09:19:53.075Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/c8/557b63527442f84c14774159948262a9d4fabb0d61166f11568f22fc60d2/jiter-0.11.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:bf11807e802a214daf6c485037778843fadd3e2ec29377ae17e0706ec1a25758", size = 516063, upload-time = "2025-09-15T09:19:54.447Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/86/13/4164c819df4a43cdc8047f9a42880f0ceef5afeb22e8b9675c0528ebdccd/jiter-0.11.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:dbb57da40631c267861dd0090461222060960012d70fd6e4c799b0f62d0ba166", size = 508139, upload-time = "2025-09-15T09:19:55.764Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fa/70/6e06929b401b331d41ddb4afb9f91cd1168218e3371972f0afa51c9f3c31/jiter-0.11.0-cp313-cp313-win32.whl", hash = "sha256:8e36924dad32c48d3c5e188d169e71dc6e84d6cb8dedefea089de5739d1d2f80", size = 206369, upload-time = "2025-09-15T09:19:57.048Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/0d/8185b8e15de6dce24f6afae63380e16377dd75686d56007baa4f29723ea1/jiter-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:452d13e4fd59698408087235259cebe67d9d49173b4dacb3e8d35ce4acf385d6", size = 202538, upload-time = "2025-09-15T09:19:58.35Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/13/3a/d61707803260d59520721fa326babfae25e9573a88d8b7b9cb54c5423a59/jiter-0.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:089f9df9f69532d1339e83142438668f52c97cd22ee2d1195551c2b1a9e6cf33", size = 313737, upload-time = "2025-09-15T09:19:59.638Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cd/cc/c9f0eec5d00f2a1da89f6bdfac12b8afdf8d5ad974184863c75060026457/jiter-0.11.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29ed1fe69a8c69bf0f2a962d8d706c7b89b50f1332cd6b9fbda014f60bd03a03", size = 346183, upload-time = "2025-09-15T09:20:01.442Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a6/87/fc632776344e7aabbab05a95a0075476f418c5d29ab0f2eec672b7a1f0ac/jiter-0.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a4d71d7ea6ea8786291423fe209acf6f8d398a0759d03e7f24094acb8ab686ba", size = 204225, upload-time = "2025-09-15T09:20:03.102Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ee/3b/e7f45be7d3969bdf2e3cd4b816a7a1d272507cd0edd2d6dc4b07514f2d9a/jiter-0.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9a6dff27eca70930bdbe4cbb7c1a4ba8526e13b63dc808c0670083d2d51a4a72", size = 304414, upload-time = "2025-09-15T09:20:04.357Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/06/32/13e8e0d152631fcc1907ceb4943711471be70496d14888ec6e92034e2caf/jiter-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ae2a7593a62132c7d4c2abbee80bbbb94fdc6d157e2c6cc966250c564ef774", size = 314223, upload-time = "2025-09-15T09:20:05.631Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0c/7e/abedd5b5a20ca083f778d96bba0d2366567fcecb0e6e34ff42640d5d7a18/jiter-0.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b13a431dba4b059e9e43019d3022346d009baf5066c24dcdea321a303cde9f0", size = 337306, upload-time = "2025-09-15T09:20:06.917Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ac/e2/30d59bdc1204c86aa975ec72c48c482fee6633120ee9c3ab755e4dfefea8/jiter-0.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:af62e84ca3889604ebb645df3b0a3f3bcf6b92babbff642bd214616f57abb93a", size = 360565, upload-time = "2025-09-15T09:20:08.283Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fe/88/567288e0d2ed9fa8f7a3b425fdaf2cb82b998633c24fe0d98f5417321aa8/jiter-0.11.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6f3b32bb723246e6b351aecace52aba78adb8eeb4b2391630322dc30ff6c773", size = 486465, upload-time = "2025-09-15T09:20:09.613Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/18/6e/7b72d09273214cadd15970e91dd5ed9634bee605176107db21e1e4205eb1/jiter-0.11.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:adcab442f4a099a358a7f562eaa54ed6456fb866e922c6545a717be51dbed7d7", size = 377581, upload-time = "2025-09-15T09:20:10.884Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/58/52/4db456319f9d14deed325f70102577492e9d7e87cf7097bda9769a1fcacb/jiter-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9967c2ab338ee2b2c0102fd379ec2693c496abf71ffd47e4d791d1f593b68e2", size = 347102, upload-time = "2025-09-15T09:20:12.175Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ce/b4/433d5703c38b26083aec7a733eb5be96f9c6085d0e270a87ca6482cbf049/jiter-0.11.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e7d0bed3b187af8b47a981d9742ddfc1d9b252a7235471ad6078e7e4e5fe75c2", size = 386477, upload-time = "2025-09-15T09:20:13.428Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c8/7a/a60bfd9c55b55b07c5c441c5085f06420b6d493ce9db28d069cc5b45d9f3/jiter-0.11.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:f6fe0283e903ebc55f1a6cc569b8c1f3bf4abd026fed85e3ff8598a9e6f982f0", size = 516004, upload-time = "2025-09-15T09:20:14.848Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2e/46/f8363e5ecc179b4ed0ca6cb0a6d3bfc266078578c71ff30642ea2ce2f203/jiter-0.11.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5821e3d66606b29ae5b497230b304f1376f38137d69e35f8d2bd5f310ff73", size = 507855, upload-time = "2025-09-15T09:20:16.176Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/90/33/396083357d51d7ff0f9805852c288af47480d30dd31d8abc74909b020761/jiter-0.11.0-cp314-cp314-win32.whl", hash = "sha256:c2d13ba7567ca8799f17c76ed56b1d49be30df996eb7fa33e46b62800562a5e2", size = 205802, upload-time = "2025-09-15T09:20:17.661Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e7/ab/eb06ca556b2551d41de7d03bf2ee24285fa3d0c58c5f8d95c64c9c3281b1/jiter-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb4790497369d134a07fc763cc88888c46f734abdd66f9fdf7865038bf3a8f40", size = 313405, upload-time = "2025-09-15T09:20:18.918Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/af/22/7ab7b4ec3a1c1f03aef376af11d23b05abcca3fb31fbca1e7557053b1ba2/jiter-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e2bbf24f16ba5ad4441a9845e40e4ea0cb9eed00e76ba94050664ef53ef4406", size = 347102, upload-time = "2025-09-15T09:20:20.16Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jsonpatch"
|
|
||||||
version = "1.33"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "jsonpointer" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jsonpointer"
|
|
||||||
version = "3.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonschema"
|
name = "jsonschema"
|
||||||
version = "4.25.1"
|
version = "4.25.1"
|
||||||
@@ -918,75 +667,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
|
{ url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "langchain-azure-ai"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "aiohttp" },
|
|
||||||
{ name = "azure-ai-inference", extra = ["opentelemetry"] },
|
|
||||||
{ name = "azure-core" },
|
|
||||||
{ name = "azure-cosmos" },
|
|
||||||
{ name = "azure-identity" },
|
|
||||||
{ name = "langchain-core" },
|
|
||||||
{ name = "langchain-openai" },
|
|
||||||
{ name = "numpy" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/ef/64/4ebdf85619bc3668544c68f3f2df1ac6e56825e5d670a685e13008c0d617/langchain_azure_ai-0.1.4.tar.gz", hash = "sha256:26e91cfa49ad3b64a858ed8501a591a5ecc2fac4fb4938283fb7ec63076f624e", size = 37078, upload-time = "2025-06-04T20:48:22.735Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/94/e3/967b411dabe8b9134066e6ba9f95b3f3a6b843ef55c9d9f7f3b4adacc1fb/langchain_azure_ai-0.1.4-py3-none-any.whl", hash = "sha256:89d94472564203f9e032d9eb0a1c617960af7a5eb4310a344d6db5352afd409f", size = 45380, upload-time = "2025-06-04T20:48:21.806Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "langchain-core"
|
|
||||||
version = "0.3.76"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "jsonpatch" },
|
|
||||||
{ name = "langsmith" },
|
|
||||||
{ name = "packaging" },
|
|
||||||
{ name = "pydantic" },
|
|
||||||
{ name = "pyyaml" },
|
|
||||||
{ name = "tenacity" },
|
|
||||||
{ name = "typing-extensions" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/4f/4d/5e2ea7754ee0a1f524c412801c6ba9ad49318ecb58b0d524903c3d9efe0a/langchain_core-0.3.76.tar.gz", hash = "sha256:71136a122dd1abae2c289c5809d035cf12b5f2bb682d8a4c1078cd94feae7419", size = 573568, upload-time = "2025-09-10T14:49:39.863Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/77/b5/501c0ffcb09c734457ceaa86bc7b1dd37b6a261147bd653add03b838aacb/langchain_core-0.3.76-py3-none-any.whl", hash = "sha256:46e0eb48c7ac532432d51f8ca1ece1804c82afe9ae3dcf027b867edadf82b3ec", size = 447508, upload-time = "2025-09-10T14:49:38.179Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "langchain-openai"
|
|
||||||
version = "0.3.33"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "langchain-core" },
|
|
||||||
{ name = "openai" },
|
|
||||||
{ name = "tiktoken" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/ff/66/4245ffe1fed8cd3812be9d228be50baa406cbb0ef5bd7cb1d98d82dcfe37/langchain_openai-0.3.33.tar.gz", hash = "sha256:2dec058332ea9e8977cd91df6515b95952e187ac7484f349c3fe91d936a92375", size = 784533, upload-time = "2025-09-10T16:03:12.965Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/31/af0486b7ad8a49f3c5c852ca2b3a7f6d8526cc71a405045dd959c36ec5db/langchain_openai-0.3.33-py3-none-any.whl", hash = "sha256:2d52aab6d2af61da9bb9470616ce782128f4be59df965caee3dece30ae6b2bc4", size = 74961, upload-time = "2025-09-10T16:03:11.732Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "langsmith"
|
|
||||||
version = "0.4.31"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "httpx" },
|
|
||||||
{ name = "orjson", marker = "platform_python_implementation != 'PyPy'" },
|
|
||||||
{ name = "packaging" },
|
|
||||||
{ name = "pydantic" },
|
|
||||||
{ name = "requests" },
|
|
||||||
{ name = "requests-toolbelt" },
|
|
||||||
{ name = "zstandard" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/55/f5/edbdf89a162ee025348b3b2080fb3b88f4a1040a5a186f32d34aca913994/langsmith-0.4.31.tar.gz", hash = "sha256:5fb3729e22bd9a225391936cb9d1080322e6c375bb776514af06b56d6c46ed3e", size = 959698, upload-time = "2025-09-25T04:18:19.55Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3e/8e/e7a43d907a147e1f87eebdd6737483f9feba52a5d4b20f69d0bd6f2fa22f/langsmith-0.4.31-py3-none-any.whl", hash = "sha256:64f340bdead21defe5f4a6ca330c11073e35444989169f669508edf45a19025f", size = 386347, upload-time = "2025-09-25T04:18:16.69Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy-object-proxy"
|
name = "lazy-object-proxy"
|
||||||
version = "1.12.0"
|
version = "1.12.0"
|
||||||
@@ -1179,77 +859,6 @@ 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" },
|
{ 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 = "msal"
|
|
||||||
version = "1.34.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "cryptography" },
|
|
||||||
{ name = "pyjwt", extra = ["crypto"] },
|
|
||||||
{ name = "requests" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/cf/0e/c857c46d653e104019a84f22d4494f2119b4fe9f896c92b4b864b3b045cc/msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f", size = 153961, upload-time = "2025-09-22T23:05:48.989Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/dc/18d48843499e278538890dc709e9ee3dea8375f8be8e82682851df1b48b5/msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1", size = 116987, upload-time = "2025-09-22T23:05:47.294Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "msal-extensions"
|
|
||||||
version = "1.3.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "msal" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "multidict"
|
|
||||||
version = "6.6.4"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843, upload-time = "2025-08-11T12:08:48.217Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3a/5d/e1db626f64f60008320aab00fbe4f23fc3300d75892a3381275b3d284580/multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e", size = 75848, upload-time = "2025-08-11T12:07:19.912Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657", size = 45060, upload-time = "2025-08-11T12:07:21.163Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da", size = 43269, upload-time = "2025-08-11T12:07:22.392Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/dc/31/d54eb0c62516776f36fe67f84a732f97e0b0e12f98d5685bebcc6d396910/multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa", size = 237158, upload-time = "2025-08-11T12:07:23.636Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c4/1c/8a10c1c25b23156e63b12165a929d8eb49a6ed769fdbefb06e6f07c1e50d/multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f", size = 257076, upload-time = "2025-08-11T12:07:25.049Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ad/86/90e20b5771d6805a119e483fd3d1e8393e745a11511aebca41f0da38c3e2/multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0", size = 240694, upload-time = "2025-08-11T12:07:26.458Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e7/49/484d3e6b535bc0555b52a0a26ba86e4d8d03fd5587d4936dc59ba7583221/multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879", size = 266350, upload-time = "2025-08-11T12:07:27.94Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bf/b4/aa4c5c379b11895083d50021e229e90c408d7d875471cb3abf721e4670d6/multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a", size = 267250, upload-time = "2025-08-11T12:07:29.303Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f", size = 254900, upload-time = "2025-08-11T12:07:30.764Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/17/38/58b27fed927c07035abc02befacab42491e7388ca105e087e6e0215ead64/multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5", size = 252355, upload-time = "2025-08-11T12:07:32.205Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d0/a1/dad75d23a90c29c02b5d6f3d7c10ab36c3197613be5d07ec49c7791e186c/multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438", size = 250061, upload-time = "2025-08-11T12:07:33.623Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b8/1a/ac2216b61c7f116edab6dc3378cca6c70dc019c9a457ff0d754067c58b20/multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e", size = 249675, upload-time = "2025-08-11T12:07:34.958Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d4/79/1916af833b800d13883e452e8e0977c065c4ee3ab7a26941fbfdebc11895/multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7", size = 261247, upload-time = "2025-08-11T12:07:36.588Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c5/65/d1f84fe08ac44a5fc7391cbc20a7cedc433ea616b266284413fd86062f8c/multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812", size = 257960, upload-time = "2025-08-11T12:07:39.735Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/13/b5/29ec78057d377b195ac2c5248c773703a6b602e132a763e20ec0457e7440/multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a", size = 250078, upload-time = "2025-08-11T12:07:41.525Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c4/0e/7e79d38f70a872cae32e29b0d77024bef7834b0afb406ddae6558d9e2414/multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69", size = 41708, upload-time = "2025-08-11T12:07:43.405Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf", size = 45912, upload-time = "2025-08-11T12:07:45.082Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c7/87/3bac136181e271e29170d8d71929cdeddeb77f3e8b6a0c08da3a8e9da114/multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605", size = 43076, upload-time = "2025-08-11T12:07:46.746Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/64/94/0a8e63e36c049b571c9ae41ee301ada29c3fee9643d9c2548d7d558a1d99/multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb", size = 82812, upload-time = "2025-08-11T12:07:48.402Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/25/1a/be8e369dfcd260d2070a67e65dd3990dd635cbd735b98da31e00ea84cd4e/multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e", size = 48313, upload-time = "2025-08-11T12:07:49.679Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/26/5a/dd4ade298674b2f9a7b06a32c94ffbc0497354df8285f27317c66433ce3b/multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f", size = 46777, upload-time = "2025-08-11T12:07:51.318Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/db/98aa28bc7e071bfba611ac2ae803c24e96dd3a452b4118c587d3d872c64c/multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773", size = 229321, upload-time = "2025-08-11T12:07:52.965Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c7/bc/01ddda2a73dd9d167bd85d0e8ef4293836a8f82b786c63fb1a429bc3e678/multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e", size = 249954, upload-time = "2025-08-11T12:07:54.423Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/06/78/6b7c0f020f9aa0acf66d0ab4eb9f08375bac9a50ff5e3edb1c4ccd59eafc/multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0", size = 228612, upload-time = "2025-08-11T12:07:55.914Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/00/44/3faa416f89b2d5d76e9d447296a81521e1c832ad6e40b92f990697b43192/multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395", size = 257528, upload-time = "2025-08-11T12:07:57.371Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/05/5f/77c03b89af0fcb16f018f668207768191fb9dcfb5e3361a5e706a11db2c9/multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45", size = 256329, upload-time = "2025-08-11T12:07:58.844Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cf/e9/ed750a2a9afb4f8dc6f13dc5b67b514832101b95714f1211cd42e0aafc26/multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb", size = 247928, upload-time = "2025-08-11T12:08:01.037Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1f/b5/e0571bc13cda277db7e6e8a532791d4403dacc9850006cb66d2556e649c0/multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5", size = 245228, upload-time = "2025-08-11T12:08:02.96Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f3/a3/69a84b0eccb9824491f06368f5b86e72e4af54c3067c37c39099b6687109/multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141", size = 235869, upload-time = "2025-08-11T12:08:04.746Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a9/9d/28802e8f9121a6a0804fa009debf4e753d0a59969ea9f70be5f5fdfcb18f/multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d", size = 243446, upload-time = "2025-08-11T12:08:06.332Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/38/ea/6c98add069b4878c1d66428a5f5149ddb6d32b1f9836a826ac764b9940be/multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d", size = 252299, upload-time = "2025-08-11T12:08:07.931Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3a/09/8fe02d204473e14c0af3affd50af9078839dfca1742f025cca765435d6b4/multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0", size = 246926, upload-time = "2025-08-11T12:08:09.467Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/37/3d/7b1e10d774a6df5175ecd3c92bff069e77bed9ec2a927fdd4ff5fe182f67/multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92", size = 243383, upload-time = "2025-08-11T12:08:10.981Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/b0/a6fae46071b645ae98786ab738447de1ef53742eaad949f27e960864bb49/multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e", size = 47775, upload-time = "2025-08-11T12:08:12.439Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/0a/2436550b1520091af0600dff547913cb2d66fbac27a8c33bc1b1bccd8d98/multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4", size = 53100, upload-time = "2025-08-11T12:08:13.823Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/97/ea/43ac51faff934086db9c072a94d327d71b7d8b40cd5dcb47311330929ef0/multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad", size = 45501, upload-time = "2025-08-11T12:08:15.173Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "numpy"
|
name = "numpy"
|
||||||
version = "2.3.3"
|
version = "2.3.3"
|
||||||
@@ -1324,25 +933,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/eb/59/0db51308fa479f9325ade08c343a5164153ad01dbb83b62ff661e1129d2e/onnxruntime-1.23.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ed85686e08cfb29ee96365b9a49e8a350aff7557c13d63d9f07ca3ad68975074", size = 17281939, upload-time = "2025-09-25T19:16:16.16Z" },
|
{ url = "https://files.pythonhosted.org/packages/eb/59/0db51308fa479f9325ade08c343a5164153ad01dbb83b62ff661e1129d2e/onnxruntime-1.23.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ed85686e08cfb29ee96365b9a49e8a350aff7557c13d63d9f07ca3ad68975074", size = 17281939, upload-time = "2025-09-25T19:16:16.16Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openai"
|
|
||||||
version = "1.109.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "anyio" },
|
|
||||||
{ name = "distro" },
|
|
||||||
{ name = "httpx" },
|
|
||||||
{ name = "jiter" },
|
|
||||||
{ name = "pydantic" },
|
|
||||||
{ name = "sniffio" },
|
|
||||||
{ 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" }
|
|
||||||
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" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openapi-core"
|
name = "openapi-core"
|
||||||
version = "0.19.5"
|
version = "0.19.5"
|
||||||
@@ -1404,53 +994,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/27/dd/b3fd642260cb17532f66cc1e8250f3507d1e580483e209dc1e9d13bd980d/openapi_spec_validator-0.7.2-py3-none-any.whl", hash = "sha256:4bbdc0894ec85f1d1bea1d6d9c8b2c3c8d7ccaa13577ef40da9c006c9fd0eb60", size = 39713, upload-time = "2025-06-07T14:48:54.077Z" },
|
{ url = "https://files.pythonhosted.org/packages/27/dd/b3fd642260cb17532f66cc1e8250f3507d1e580483e209dc1e9d13bd980d/openapi_spec_validator-0.7.2-py3-none-any.whl", hash = "sha256:4bbdc0894ec85f1d1bea1d6d9c8b2c3c8d7ccaa13577ef40da9c006c9fd0eb60", size = 39713, upload-time = "2025-06-07T14:48:54.077Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "opentelemetry-api"
|
|
||||||
version = "1.37.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "importlib-metadata" },
|
|
||||||
{ name = "typing-extensions" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/63/04/05040d7ce33a907a2a02257e601992f0cdf11c73b33f13c4492bf6c3d6d5/opentelemetry_api-1.37.0.tar.gz", hash = "sha256:540735b120355bd5112738ea53621f8d5edb35ebcd6fe21ada3ab1c61d1cd9a7", size = 64923, upload-time = "2025-09-11T10:29:01.662Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/91/48/28ed9e55dcf2f453128df738210a980e09f4e468a456fa3c763dbc8be70a/opentelemetry_api-1.37.0-py3-none-any.whl", hash = "sha256:accf2024d3e89faec14302213bc39550ec0f4095d1cf5ca688e1bfb1c8612f47", size = 65732, upload-time = "2025-09-11T10:28:41.826Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "orjson"
|
|
||||||
version = "3.11.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/be/4d/8df5f83256a809c22c4d6792ce8d43bb503be0fb7a8e4da9025754b09658/orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a", size = 5482394, upload-time = "2025-08-26T17:46:43.171Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fc/79/8932b27293ad35919571f77cb3693b5906cf14f206ef17546052a241fdf6/orjson-3.11.3-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:af40c6612fd2a4b00de648aa26d18186cd1322330bd3a3cc52f87c699e995810", size = 238127, upload-time = "2025-08-26T17:45:38.146Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1c/82/cb93cd8cf132cd7643b30b6c5a56a26c4e780c7a145db6f83de977b540ce/orjson-3.11.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:9f1587f26c235894c09e8b5b7636a38091a9e6e7fe4531937534749c04face43", size = 127494, upload-time = "2025-08-26T17:45:39.57Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/b8/2d9eb181a9b6bb71463a78882bcac1027fd29cf62c38a40cc02fc11d3495/orjson-3.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61dcdad16da5bb486d7227a37a2e789c429397793a6955227cedbd7252eb5a27", size = 123017, upload-time = "2025-08-26T17:45:40.876Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b4/14/a0e971e72d03b509190232356d54c0f34507a05050bd026b8db2bf2c192c/orjson-3.11.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11c6d71478e2cbea0a709e8a06365fa63da81da6498a53e4c4f065881d21ae8f", size = 127898, upload-time = "2025-08-26T17:45:42.188Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8e/af/dc74536722b03d65e17042cc30ae586161093e5b1f29bccda24765a6ae47/orjson-3.11.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff94112e0098470b665cb0ed06efb187154b63649403b8d5e9aedeb482b4548c", size = 130742, upload-time = "2025-08-26T17:45:43.511Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/62/e6/7a3b63b6677bce089fe939353cda24a7679825c43a24e49f757805fc0d8a/orjson-3.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b756575aaa2a855a75192f356bbda11a89169830e1439cfb1a3e1a6dde7be", size = 132377, upload-time = "2025-08-26T17:45:45.525Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fc/cd/ce2ab93e2e7eaf518f0fd15e3068b8c43216c8a44ed82ac2b79ce5cef72d/orjson-3.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9416cc19a349c167ef76135b2fe40d03cea93680428efee8771f3e9fb66079d", size = 135313, upload-time = "2025-08-26T17:45:46.821Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d0/b4/f98355eff0bd1a38454209bbc73372ce351ba29933cb3e2eba16c04b9448/orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2", size = 132908, upload-time = "2025-08-26T17:45:48.126Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/eb/92/8f5182d7bc2a1bed46ed960b61a39af8389f0ad476120cd99e67182bfb6d/orjson-3.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:414f71e3bdd5573893bf5ecdf35c32b213ed20aa15536fe2f588f946c318824f", size = 130905, upload-time = "2025-08-26T17:45:49.414Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1a/60/c41ca753ce9ffe3d0f67b9b4c093bdd6e5fdb1bc53064f992f66bb99954d/orjson-3.11.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:828e3149ad8815dc14468f36ab2a4b819237c155ee1370341b91ea4c8672d2ee", size = 403812, upload-time = "2025-08-26T17:45:51.085Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/dd/13/e4a4f16d71ce1868860db59092e78782c67082a8f1dc06a3788aef2b41bc/orjson-3.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac9e05f25627ffc714c21f8dfe3a579445a5c392a9c8ae7ba1d0e9fb5333f56e", size = 146277, upload-time = "2025-08-26T17:45:52.851Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8d/8b/bafb7f0afef9344754a3a0597a12442f1b85a048b82108ef2c956f53babd/orjson-3.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e44fbe4000bd321d9f3b648ae46e0196d21577cf66ae684a96ff90b1f7c93633", size = 135418, upload-time = "2025-08-26T17:45:54.806Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/60/d4/bae8e4f26afb2c23bea69d2f6d566132584d1c3a5fe89ee8c17b718cab67/orjson-3.11.3-cp313-cp313-win32.whl", hash = "sha256:2039b7847ba3eec1f5886e75e6763a16e18c68a63efc4b029ddf994821e2e66b", size = 136216, upload-time = "2025-08-26T17:45:57.182Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/88/76/224985d9f127e121c8cad882cea55f0ebe39f97925de040b75ccd4b33999/orjson-3.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:29be5ac4164aa8bdcba5fa0700a3c9c316b411d8ed9d39ef8a882541bd452fae", size = 131362, upload-time = "2025-08-26T17:45:58.56Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e2/cf/0dce7a0be94bd36d1346be5067ed65ded6adb795fdbe3abd234c8d576d01/orjson-3.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:18bd1435cb1f2857ceb59cfb7de6f92593ef7b831ccd1b9bfb28ca530e539dce", size = 125989, upload-time = "2025-08-26T17:45:59.95Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ef/77/d3b1fef1fc6aaeed4cbf3be2b480114035f4df8fa1a99d2dac1d40d6e924/orjson-3.11.3-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cf4b81227ec86935568c7edd78352a92e97af8da7bd70bdfdaa0d2e0011a1ab4", size = 238115, upload-time = "2025-08-26T17:46:01.669Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e4/6d/468d21d49bb12f900052edcfbf52c292022d0a323d7828dc6376e6319703/orjson-3.11.3-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:bc8bc85b81b6ac9fc4dae393a8c159b817f4c2c9dee5d12b773bddb3b95fc07e", size = 127493, upload-time = "2025-08-26T17:46:03.466Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/46/1e2588700d354aacdf9e12cc2d98131fb8ac6f31ca65997bef3863edb8ff/orjson-3.11.3-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:88dcfc514cfd1b0de038443c7b3e6a9797ffb1b3674ef1fd14f701a13397f82d", size = 122998, upload-time = "2025-08-26T17:46:04.803Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3b/94/11137c9b6adb3779f1b34fd98be51608a14b430dbc02c6d41134fbba484c/orjson-3.11.3-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d61cd543d69715d5fc0a690c7c6f8dcc307bc23abef9738957981885f5f38229", size = 132915, upload-time = "2025-08-26T17:46:06.237Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/10/61/dccedcf9e9bcaac09fdabe9eaee0311ca92115699500efbd31950d878833/orjson-3.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2b7b153ed90ababadbef5c3eb39549f9476890d339cf47af563aea7e07db2451", size = 130907, upload-time = "2025-08-26T17:46:07.581Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0e/fd/0e935539aa7b08b3ca0f817d73034f7eb506792aae5ecc3b7c6e679cdf5f/orjson-3.11.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7909ae2460f5f494fecbcd10613beafe40381fd0316e35d6acb5f3a05bfda167", size = 403852, upload-time = "2025-08-26T17:46:08.982Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4a/2b/50ae1a5505cd1043379132fdb2adb8a05f37b3e1ebffe94a5073321966fd/orjson-3.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2030c01cbf77bc67bee7eef1e7e31ecf28649353987775e3583062c752da0077", size = 146309, upload-time = "2025-08-26T17:46:10.576Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cd/1d/a473c158e380ef6f32753b5f39a69028b25ec5be331c2049a2201bde2e19/orjson-3.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0169ebd1cbd94b26c7a7ad282cf5c2744fce054133f959e02eb5265deae1872", size = 135424, upload-time = "2025-08-26T17:46:12.386Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/da/09/17d9d2b60592890ff7382e591aa1d9afb202a266b180c3d4049b1ec70e4a/orjson-3.11.3-cp314-cp314-win32.whl", hash = "sha256:0c6d7328c200c349e3a4c6d8c83e0a5ad029bdc2d417f234152bf34842d0fc8d", size = 136266, upload-time = "2025-08-26T17:46:13.853Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/15/58/358f6846410a6b4958b74734727e582ed971e13d335d6c7ce3e47730493e/orjson-3.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:317bbe2c069bbc757b1a2e4105b64aacd3bc78279b66a6b9e51e846e4809f804", size = 131351, upload-time = "2025-08-26T17:46:15.27Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/28/01/d6b274a0635be0468d4dbd9cafe80c47105937a0d42434e805e67cd2ed8b/orjson-3.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:e8f6a7a27d7b7bec81bd5924163e9af03d49bbb63013f107b48eb5d16db711bc", size = 125985, upload-time = "2025-08-26T17:46:16.67Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "25.0"
|
version = "25.0"
|
||||||
@@ -1554,47 +1097,6 @@ 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" },
|
{ 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 = "propcache"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "protobuf"
|
name = "protobuf"
|
||||||
version = "6.32.1"
|
version = "6.32.1"
|
||||||
@@ -1707,20 +1209,6 @@ 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/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyjwt"
|
|
||||||
version = "2.10.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.optional-dependencies]
|
|
||||||
crypto = [
|
|
||||||
{ name = "cryptography" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyperclip"
|
name = "pyperclip"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
@@ -1892,70 +1380,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" },
|
{ url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex"
|
|
||||||
version = "2025.9.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/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" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "requests"
|
name = "requests"
|
||||||
version = "2.32.5"
|
version = "2.32.5"
|
||||||
@@ -1971,18 +1395,6 @@ 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/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "requests-toolbelt"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "requests" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rfc3339-validator"
|
name = "rfc3339-validator"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -2125,8 +1537,6 @@ dependencies = [
|
|||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
mcp = [
|
mcp = [
|
||||||
{ name = "fastmcp" },
|
{ name = "fastmcp" },
|
||||||
{ name = "langchain-azure-ai" },
|
|
||||||
{ name = "mcp" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
@@ -2142,8 +1552,6 @@ dev = [
|
|||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "fastmcp", marker = "extra == 'mcp'", specifier = ">=2.12.4" },
|
{ name = "fastmcp", marker = "extra == 'mcp'", specifier = ">=2.12.4" },
|
||||||
{ name = "langchain-azure-ai", marker = "extra == 'mcp'", specifier = ">=0.1.4" },
|
|
||||||
{ name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.15.0" },
|
|
||||||
{ name = "qdrant-client", specifier = "==1.13" },
|
{ name = "qdrant-client", specifier = "==1.13" },
|
||||||
{ name = "vault-settings", specifier = ">=0.1.0" },
|
{ name = "vault-settings", specifier = ">=0.1.0" },
|
||||||
]
|
]
|
||||||
@@ -2222,15 +1630,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
|
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tenacity"
|
|
||||||
version = "9.1.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "3.1.0"
|
version = "3.1.0"
|
||||||
@@ -2240,24 +1639,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" },
|
{ url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tiktoken"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "regex" },
|
|
||||||
{ name = "requests" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a7/86/ad0155a37c4f310935d5ac0b1ccf9bdb635dcb906e0a9a26b616dd55825a/tiktoken-0.11.0.tar.gz", hash = "sha256:3c518641aee1c52247c2b97e74d8d07d780092af79d5911a6ab5e79359d9b06a", size = 37648, upload-time = "2025-08-08T23:58:08.495Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cc/cd/a9034bcee638716d9310443818d73c6387a6a96db93cbcb0819b77f5b206/tiktoken-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a5f3f25ffb152ee7fec78e90a5e5ea5b03b4ea240beed03305615847f7a6ace2", size = 1055339, upload-time = "2025-08-08T23:57:51.802Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f1/91/9922b345f611b4e92581f234e64e9661e1c524875c8eadd513c4b2088472/tiktoken-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dc6e9ad16a2a75b4c4be7208055a1f707c9510541d94d9cc31f7fbdc8db41d8", size = 997080, upload-time = "2025-08-08T23:57:53.442Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d0/9d/49cd047c71336bc4b4af460ac213ec1c457da67712bde59b892e84f1859f/tiktoken-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a0517634d67a8a48fd4a4ad73930c3022629a85a217d256a6e9b8b47439d1e4", size = 1128501, upload-time = "2025-08-08T23:57:54.808Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/52/d5/a0dcdb40dd2ea357e83cb36258967f0ae96f5dd40c722d6e382ceee6bba9/tiktoken-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fb4effe60574675118b73c6fbfd3b5868e5d7a1f570d6cc0d18724b09ecf318", size = 1182743, upload-time = "2025-08-08T23:57:56.307Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3b/17/a0fc51aefb66b7b5261ca1314afa83df0106b033f783f9a7bcbe8e741494/tiktoken-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94f984c9831fd32688aef4348803b0905d4ae9c432303087bae370dc1381a2b8", size = 1244057, upload-time = "2025-08-08T23:57:57.628Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/79/bcf350609f3a10f09fe4fc207f132085e497fdd3612f3925ab24d86a0ca0/tiktoken-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2177ffda31dec4023356a441793fed82f7af5291120751dee4d696414f54db0c", size = 883901, upload-time = "2025-08-08T23:57:59.359Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokenizers"
|
name = "tokenizers"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
@@ -2371,100 +1752,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b66
|
|||||||
wheels = [
|
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" },
|
{ 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 = "yarl"
|
|
||||||
version = "1.20.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "idna" },
|
|
||||||
{ name = "multidict" },
|
|
||||||
{ name = "propcache" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zipp"
|
|
||||||
version = "3.23.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zstandard"
|
|
||||||
version = "0.25.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" },
|
|
||||||
]
|
|
||||||
|
|||||||
Reference in New Issue
Block a user