diff --git a/.gitignore b/.gitignore index 07eee848e2b..22bf6218691 100644 --- a/.gitignore +++ b/.gitignore @@ -207,6 +207,7 @@ temp*/ # AI .claude/ +.kiro/ .omc/ .omx/ WARP.md diff --git a/python/AGENTS.md b/python/AGENTS.md index 8dc259c42bb..e870a140d87 100644 --- a/python/AGENTS.md +++ b/python/AGENTS.md @@ -98,6 +98,7 @@ python/ ### Storage & Memory - [mem0](packages/mem0/AGENTS.md) - Mem0 memory integration - [redis](packages/redis/AGENTS.md) - Redis storage +- [valkey](packages/valkey/AGENTS.md) - Valkey storage and vector search ### Infrastructure - [copilotstudio](packages/copilotstudio/AGENTS.md) - Microsoft Copilot Studio diff --git a/python/PACKAGE_STATUS.md b/python/PACKAGE_STATUS.md index 1f336f1cd8c..b66d49495e8 100644 --- a/python/PACKAGE_STATUS.md +++ b/python/PACKAGE_STATUS.md @@ -43,6 +43,7 @@ Status is grouped into these buckets: | `agent-framework-orchestrations` | `python/packages/orchestrations` | `beta` | | `agent-framework-purview` | `python/packages/purview` | `beta` | | `agent-framework-redis` | `python/packages/redis` | `beta` | +| `agent-framework-valkey` | `python/packages/valkey` | `alpha` | ## Deprecated / removed packages diff --git a/python/packages/valkey/AGENTS.md b/python/packages/valkey/AGENTS.md new file mode 100644 index 00000000000..5d3a05790d2 --- /dev/null +++ b/python/packages/valkey/AGENTS.md @@ -0,0 +1,23 @@ +# Valkey Package (agent-framework-valkey) + +Valkey-based storage for agent conversations and context. + +## Main Classes + +- **`ValkeyChatMessageStore`** - Persistent chat history provider using Valkey +- **`ValkeyContextProvider`** - Context provider with Valkey-backed vector search retrieval + +## Usage + +```python +from agent_framework_valkey import ValkeyContextProvider, ValkeyChatMessageStore + +context_provider = ValkeyContextProvider(host="localhost", port=6379, user_id="u1") +message_store = ValkeyChatMessageStore(host="localhost", port=6379) +``` + +## Import Path + +```python +from agent_framework_valkey import ValkeyContextProvider, ValkeyChatMessageStore +``` diff --git a/python/packages/valkey/LICENSE b/python/packages/valkey/LICENSE new file mode 100644 index 00000000000..9e841e7a26e --- /dev/null +++ b/python/packages/valkey/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/python/packages/valkey/README.md b/python/packages/valkey/README.md new file mode 100644 index 00000000000..89cbe532d90 --- /dev/null +++ b/python/packages/valkey/README.md @@ -0,0 +1,115 @@ +# Get Started with Microsoft Agent Framework Valkey + +Please install this package via pip: + +```bash +pip install agent-framework-valkey --pre +``` + +## Server Requirements + +The `ValkeyChatMessageStore` works with any Valkey (or Redis OSS) server — it only uses basic key-value operations. + +The `ValkeyContextProvider` requires the **valkey-search** module (>= 1.2) for its `FT.CREATE` / `FT.SEARCH` commands. This module ships with **valkey-bundle >= 9.1.0** and is also available in managed cloud offerings (AWS ElastiCache for Valkey, GCP Memorystore for Valkey). + +For local development and testing, use `valkey-bundle 9.1.0-rc1`: + +```bash +docker run -d --name valkey -p 6379:6379 valkey/valkey-bundle:9.1.0-rc1 +``` + +## Components + +### Valkey Context Provider + +The `ValkeyContextProvider` enables persistent context and memory capabilities for your agents, +allowing them to remember user preferences and conversation context across sessions and threads. +It uses Valkey's native vector search capabilities (`FT.CREATE` / `FT.SEARCH`) for semantic +retrieval of past conversation context. + +#### Basic Usage + +```python +from agent_framework_valkey import ValkeyContextProvider + +# Text-only search (no embeddings required) +context_provider = ValkeyContextProvider( + host="localhost", + port=6379, + user_id="user-123", +) + +# With vector search (requires an embedding function) +async def my_embed_fn(text: str) -> list[float]: + # Your embedding logic here + ... + +context_provider = ValkeyContextProvider( + host="localhost", + port=6379, + user_id="user-123", + embed_fn=my_embed_fn, + vector_field_name="embedding", + vector_dims=1536, +) +``` + +### Valkey Chat Message Store + +The `ValkeyChatMessageStore` provides persistent conversation storage using Valkey Lists, +enabling chat history to survive application restarts and support distributed applications. + +#### Key Features + +- **Persistent Storage**: Messages survive application restarts +- **Session Isolation**: Each conversation session has its own Valkey key +- **Message Limits**: Configurable automatic trimming of old messages +- **Lightweight**: Uses only basic Valkey key-value operations (no search module required) +- **valkey-glide**: Built on the official Valkey Python client + +#### Basic Usage + +```python +from agent_framework_valkey import ValkeyChatMessageStore + +store = ValkeyChatMessageStore( + host="localhost", + port=6379, + max_messages=100, +) +``` + +## Installing and Running Valkey + +### Option A: Local Valkey with Docker + +For basic chat history storage (no search needed): + +```bash +docker run --name valkey -p 6379:6379 -d valkey/valkey:8.1 +``` + +For full functionality including the `ValkeyContextProvider` (requires valkey-search): + +```bash +docker run --name valkey -p 6379:6379 -d valkey/valkey-bundle:9.1.0-rc1 +``` + +### Option B: AWS ElastiCache for Valkey + +Create a serverless or node-based [ElastiCache for Valkey](https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/WhatIs.html) cluster. + +### Option C: Google Cloud Memorystore for Valkey + +Create a [Memorystore for Valkey](https://cloud.google.com/memorystore/docs/valkey) instance. + +## Why Valkey? + +Valkey is an open-source, Linux Foundation project that is protocol-compatible with Redis +for core operations. It provides: + +- **Open governance**: Community-driven development under the Linux Foundation +- **Performance**: Single-digit millisecond latency with high recall for vector search +- **Scaling**: Linear scaling with cluster mode support +- **Cloud support**: Managed services from AWS, GCP, and other providers +- **Migration path**: Drop-in replacement for Redis deployments diff --git a/python/packages/valkey/agent_framework_valkey/__init__.py b/python/packages/valkey/agent_framework_valkey/__init__.py new file mode 100644 index 00000000000..1d53d9b5d0b --- /dev/null +++ b/python/packages/valkey/agent_framework_valkey/__init__.py @@ -0,0 +1,27 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Valkey integration for Microsoft Agent Framework. + +This module re-exports objects from: +- agent-framework-valkey + +Supported classes: +- ValkeyContextProvider +- ValkeyChatMessageStore +""" + +import importlib.metadata + +from ._chat_message_store import ValkeyChatMessageStore +from ._context_provider import ValkeyContextProvider + +try: + __version__ = importlib.metadata.version(__name__) +except importlib.metadata.PackageNotFoundError: + __version__ = "0.0.0" # Fallback for development mode + +__all__ = [ + "ValkeyChatMessageStore", + "ValkeyContextProvider", + "__version__", +] diff --git a/python/packages/valkey/agent_framework_valkey/_chat_message_store.py b/python/packages/valkey/agent_framework_valkey/_chat_message_store.py new file mode 100644 index 00000000000..f58d56809d2 --- /dev/null +++ b/python/packages/valkey/agent_framework_valkey/_chat_message_store.py @@ -0,0 +1,235 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Valkey-backed chat message store using HistoryProvider. + +This module provides ``ValkeyChatMessageStore``, a persistent conversation +history provider built on the :class:`HistoryProvider` hooks pattern, using +valkey-glide (the official Valkey Python client) for basic key-value operations. +""" + +from __future__ import annotations + +import json +import sys +from collections.abc import Sequence +from typing import Any, ClassVar + +from agent_framework import Message +from agent_framework._sessions import HistoryProvider + +try: + from glide import GlideClient, GlideClientConfiguration, NodeAddress +except ImportError: + GlideClient = None # type: ignore[assignment,misc] + GlideClientConfiguration = None # type: ignore[assignment,misc] + NodeAddress = None # type: ignore[assignment,misc] + +if sys.version_info >= (3, 11): + from typing import Self # pragma: no cover +else: + from typing_extensions import Self # pragma: no cover + + +def _check_glide_available() -> None: + """Raise a clear error if valkey-glide is not installed.""" + if GlideClient is None: + raise ImportError( + "valkey-glide is required but not installed. " + "It is not available on Windows. " + "Install it with: pip install valkey-glide" + ) + + +class ValkeyChatMessageStore(HistoryProvider): + """Valkey-backed history provider using the HistoryProvider hooks pattern. + + Stores conversation history in Valkey Lists, with each session isolated + by a unique key. Uses valkey-glide for all operations. + """ + + DEFAULT_SOURCE_ID: ClassVar[str] = "valkey_memory" + + def __init__( + self, + source_id: str = DEFAULT_SOURCE_ID, + valkey_url: str | None = None, + host: str | None = None, + port: int | None = None, + *, + use_tls: bool = False, + key_prefix: str = "chat_messages", + max_messages: int | None = None, + load_messages: bool = True, + store_outputs: bool = True, + store_inputs: bool = True, + store_context_messages: bool = False, + store_context_from: set[str] | None = None, + client: "GlideClient | None" = None, + ) -> None: + """Initialize the Valkey chat message store. + + Args: + source_id: Unique identifier for this provider instance. + valkey_url: Valkey connection URL (e.g., "valkey://localhost:6379"). + If provided, host and port are extracted from the URL. + Mutually exclusive with explicit host/port. + host: Valkey server hostname. Defaults to "localhost" when neither + valkey_url nor client is provided. + port: Valkey server port. Defaults to 6379 when neither valkey_url + nor client is provided. + use_tls: Enable TLS for the connection. Defaults to False. + key_prefix: Prefix for Valkey keys. Defaults to "chat_messages". + max_messages: Maximum number of messages to retain per session. + When exceeded, oldest messages are automatically trimmed. + None means unlimited storage. + load_messages: Whether to load messages before invocation. + store_outputs: Whether to store response messages. + store_inputs: Whether to store input messages. + store_context_messages: Whether to store context from other providers. + store_context_from: If set, only store context from these source_ids. + client: A pre-created GlideClient instance. If provided, host/port/url + are ignored and the caller is responsible for the client lifecycle. + """ + _check_glide_available() + super().__init__( + source_id, + load_messages=load_messages, + store_outputs=store_outputs, + store_inputs=store_inputs, + store_context_messages=store_context_messages, + store_context_from=store_context_from, + ) + + # Validate mutually exclusive connection params + if client is None and valkey_url is not None and (host is not None or port is not None): + raise ValueError("valkey_url and explicit host/port are mutually exclusive.") + + self.key_prefix = key_prefix + self.max_messages = max_messages + self.valkey_url = valkey_url + self.host = host or "localhost" + self.port = port or 6379 + self.use_tls = use_tls + self._client: GlideClient | None = client # type: ignore[assignment] + self._owns_client = client is None + + async def _get_client(self) -> GlideClient: # type: ignore[return] + """Get or create the Valkey client.""" + if self._client is None: + if self.valkey_url is not None: + host, port = self._parse_url(self.valkey_url) + else: + host, port = self.host, self.port + config = GlideClientConfiguration( + addresses=[NodeAddress(host=host, port=port)], + use_tls=self.use_tls, + ) + self._client = await GlideClient.create(config) + return self._client + + @staticmethod + def _parse_url(url: str) -> tuple[str, int]: + """Parse a Valkey URL into host and port components. + + Args: + url: A URL like "valkey://host:port" or "redis://host:port". + + Returns: + A tuple of (host, port). + """ + from urllib.parse import urlparse + + parsed = urlparse(url) + host = parsed.hostname or "localhost" + port = parsed.port or 6379 + return host, port + + def _valkey_key(self, session_id: str | None) -> str: + """Get the Valkey key for a given session's messages.""" + return f"{self.key_prefix}:{session_id or 'default'}" + + async def get_messages( + self, + session_id: str | None, + *, + state: dict[str, Any] | None = None, + **kwargs: Any, + ) -> list[Message]: + """Retrieve stored messages for this session from Valkey. + + Args: + session_id: The session ID to retrieve messages for. + state: Optional session state. Unused for Valkey-backed history. + **kwargs: Additional arguments (unused). + + Returns: + List of stored Message objects in chronological order. + """ + client = await self._get_client() + key = self._valkey_key(session_id) + raw_messages = await client.lrange(key, 0, -1) + messages: list[Message] = [] + if raw_messages: + for raw in raw_messages: + decoded = raw.decode("utf-8") if isinstance(raw, bytes) else str(raw) + messages.append(Message.from_dict(json.loads(decoded))) + return messages + + async def save_messages( + self, + session_id: str | None, + messages: Sequence[Message], + *, + state: dict[str, Any] | None = None, + **kwargs: Any, + ) -> None: + """Persist messages for this session to Valkey. + + Args: + session_id: The session ID to store messages for. + messages: The messages to persist. + state: Optional session state. Unused for Valkey-backed history. + **kwargs: Additional arguments (unused). + """ + if not messages: + return + + client = await self._get_client() + key = self._valkey_key(session_id) + serialized_messages = [json.dumps(msg.to_dict()) for msg in messages] + + await client.rpush(key, serialized_messages) # pyright: ignore[reportArgumentType] + + if self.max_messages is not None: + await client.ltrim(key, -self.max_messages, -1) + + async def clear(self, session_id: str | None) -> None: + """Clear all messages for a session. + + Args: + session_id: The session ID to clear messages for. + """ + client = await self._get_client() + await client.delete([self._valkey_key(session_id)]) + + async def aclose(self) -> None: + """Close the Valkey connection if owned by this instance.""" + if self._owns_client and self._client is not None: + await self._client.close() + self._client = None + + async def __aenter__(self) -> Self: + """Async context manager entry.""" + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: Any, + ) -> None: + """Async context manager exit.""" + await self.aclose() + + +__all__ = ["ValkeyChatMessageStore"] diff --git a/python/packages/valkey/agent_framework_valkey/_context_provider.py b/python/packages/valkey/agent_framework_valkey/_context_provider.py new file mode 100644 index 00000000000..7cb7149c319 --- /dev/null +++ b/python/packages/valkey/agent_framework_valkey/_context_provider.py @@ -0,0 +1,591 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Valkey context provider using ContextProvider. + +This module provides ``ValkeyContextProvider``, built on the +:class:`ContextProvider` hooks pattern. It uses valkey-glide with Valkey's +native vector search capabilities (FT.CREATE / FT.SEARCH) for semantic +retrieval of past conversation context. +""" + +from __future__ import annotations + +import sys +import uuid +from collections.abc import Awaitable, Callable +from typing import TYPE_CHECKING, Any, ClassVar, Literal, cast + +from agent_framework import Message +from agent_framework._sessions import AgentSession, ContextProvider, SessionContext +from agent_framework.exceptions import IntegrationInvalidRequestException + +try: + from glide import GlideClient, GlideClientConfiguration, NodeAddress +except ImportError: + GlideClient = None # type: ignore[assignment,misc] + GlideClientConfiguration = None # type: ignore[assignment,misc] + NodeAddress = None # type: ignore[assignment,misc] + +if sys.version_info >= (3, 11): + from typing import Self # pragma: no cover +else: + from typing_extensions import Self # pragma: no cover + +if sys.version_info >= (3, 12): + from typing import override # type: ignore # pragma: no cover +else: + from typing_extensions import override # type: ignore[import] # pragma: no cover + +if TYPE_CHECKING: + from agent_framework._agents import SupportsAgentRun + +EmbedFn = Callable[[str], Awaitable[list[float]]] + + +def _check_glide_available() -> None: + """Raise a clear error if valkey-glide is not installed.""" + if GlideClient is None: + raise ImportError( + "valkey-glide is required but not installed. " + "It is not available on Windows. " + "Install it with: pip install valkey-glide" + ) + + +class ValkeyContextProvider(ContextProvider): + """Valkey context provider using the ContextProvider hooks pattern. + + Stores context in Valkey using HASH keys and retrieves scoped context via + full-text search or optional hybrid vector search using Valkey's native + FT.CREATE / FT.SEARCH commands through valkey-glide. + """ + + DEFAULT_CONTEXT_PROMPT = "## Memories\nConsider the following memories when answering user questions:" + DEFAULT_SOURCE_ID: ClassVar[str] = "valkey" + + def __init__( + self, + source_id: str = DEFAULT_SOURCE_ID, + valkey_url: str | None = None, + host: str | None = None, + port: int | None = None, + *, + use_tls: bool = False, + index_name: str = "context_idx", + prefix: str = "context:", + vector_dims: int | None = None, + vector_field_name: str | None = None, + vector_algorithm: Literal["FLAT", "HNSW"] = "HNSW", + vector_distance_metric: Literal["COSINE", "IP", "L2"] = "COSINE", + embed_fn: EmbedFn | None = None, + application_id: str | None = None, + agent_id: str | None = None, + user_id: str | None = None, + context_prompt: str | None = None, + client: "GlideClient | None" = None, + ): + """Create a Valkey Context Provider. + + Args: + source_id: Unique identifier for this provider instance. + valkey_url: Valkey connection URL (e.g., "valkey://localhost:6379"). + If provided, host and port are extracted from the URL. + Mutually exclusive with explicit host/port. + host: Valkey server hostname. Defaults to "localhost" when neither + valkey_url nor client is provided. + port: Valkey server port. Defaults to 6379 when neither valkey_url + nor client is provided. + use_tls: Enable TLS for the connection. Defaults to False. + index_name: The name of the search index. Defaults to "context_idx". + prefix: The key prefix for stored documents. Defaults to "context:". + vector_dims: Dimensionality of embedding vectors. Required if embed_fn is set. + vector_field_name: The name of the vector field. Required if embed_fn is set. + vector_algorithm: Vector index algorithm ("FLAT" or "HNSW"). Defaults to "HNSW". + vector_distance_metric: Distance metric ("COSINE", "IP", or "L2"). Defaults to "COSINE". + embed_fn: An async callable that takes a string and returns a list of floats. + Required for vector search. When provided, vector_field_name and + vector_dims must also be set. + application_id: The application ID to scope the context. + agent_id: The agent ID to scope the context. + user_id: The user ID to scope the context. + context_prompt: The context prompt to use for the provider. + client: A pre-created GlideClient instance. If provided, host/port/url + are ignored and the caller is responsible for the client lifecycle. + """ + _check_glide_available() + super().__init__(source_id) + + # Validate mutually exclusive connection params + if client is None and valkey_url is not None and (host is not None or port is not None): + raise ValueError("valkey_url and explicit host/port are mutually exclusive.") + + # Validate vector configuration consistency + if embed_fn is not None: + if vector_field_name is None or vector_dims is None: + raise ValueError( + "vector_field_name and vector_dims are required when embed_fn is provided." + ) + if vector_dims <= 0: + raise ValueError("vector_dims must be a positive integer.") + + self.valkey_url = valkey_url + self.host = host or "localhost" + self.port = port or 6379 + self.use_tls = use_tls + self.index_name = index_name + self.prefix = prefix + self.vector_dims = vector_dims + self.vector_field_name = vector_field_name + self.vector_algorithm = vector_algorithm + self.vector_distance_metric = vector_distance_metric + self.embed_fn = embed_fn + self.application_id = application_id + self.agent_id = agent_id + self.user_id = user_id + self.context_prompt = context_prompt or self.DEFAULT_CONTEXT_PROMPT + self._client: GlideClient | None = client # type: ignore[assignment] + self._owns_client = client is None + self._index_created: bool = False + + async def _get_client(self) -> GlideClient: # type: ignore[return] + """Get or create the Valkey client.""" + if self._client is None: + if self.valkey_url is not None: + parsed_host, parsed_port = self._parse_url(self.valkey_url) + else: + parsed_host, parsed_port = self.host, self.port + config = GlideClientConfiguration( + addresses=[NodeAddress(host=parsed_host, port=parsed_port)], + use_tls=self.use_tls, + ) + self._client = await GlideClient.create(config) + return self._client + + @staticmethod + def _parse_url(url: str) -> tuple[str, int]: + """Parse a Valkey URL into host and port components. + + Args: + url: A URL like "valkey://host:port" or "redis://host:port". + + Returns: + A tuple of (host, port). + """ + from urllib.parse import urlparse + + parsed = urlparse(url) + host = parsed.hostname or "localhost" + port = parsed.port or 6379 + return host, port + + # -- Hooks pattern --------------------------------------------------------- + + @override + async def before_run( + self, + *, + agent: SupportsAgentRun, + session: AgentSession, + context: SessionContext, + state: dict[str, Any], + ) -> None: + """Retrieve scoped context from Valkey and add to the session context.""" + self._validate_filters() + input_text = "\n".join(msg.text for msg in context.input_messages if msg and msg.text and msg.text.strip()) + if not input_text.strip(): + return + + memories = await self._search(text=input_text) + line_separated_memories = "\n".join( + str(memory.get("content", "")) for memory in memories if memory.get("content") + ) + if line_separated_memories: + context.extend_messages( + self.source_id, + [Message(role="user", contents=[f"{self.context_prompt}\n{line_separated_memories}"])], + ) + + @override + async def after_run( + self, + *, + agent: SupportsAgentRun, + session: AgentSession, + context: SessionContext, + state: dict[str, Any], + ) -> None: + """Store request/response messages to Valkey for future retrieval.""" + self._validate_filters() + + messages_to_store: list[Message] = list(context.input_messages) + if context.response and context.response.messages: + messages_to_store.extend(context.response.messages) + + docs: list[dict[str, Any]] = [] + for message in messages_to_store: + if message.role in {"user", "assistant", "system"} and message.text and message.text.strip(): + doc: dict[str, Any] = { + "role": message.role, + "content": message.text, + "conversation_id": context.session_id or "", + "message_id": message.message_id or "", + "author_name": message.author_name or "", + "application_id": self.application_id or "", + "agent_id": self.agent_id or "", + "user_id": self.user_id or "", + "thread_id": context.session_id or "", + } + docs.append(doc) + + if docs: + await self._add(data=docs) + + # -- Internal methods ------------------------------------------------------ + + async def _ensure_index(self) -> None: + """Create the search index if it does not already exist.""" + if self._index_created: + return + + client = await self._get_client() + + # Build FT.CREATE arguments + args: list[str] = [ + self.index_name, + "ON", + "HASH", + "PREFIX", + "1", + self.prefix, + "SCHEMA", + "role", + "TAG", + "content", + "TEXT", + "conversation_id", + "TAG", + "message_id", + "TAG", + "author_name", + "TAG", + "application_id", + "TAG", + "agent_id", + "TAG", + "user_id", + "TAG", + "thread_id", + "TAG", + ] + + if self.vector_field_name and self.vector_dims: + args.extend([ + self.vector_field_name, + "VECTOR", + self.vector_algorithm, + "6", + "TYPE", + "FLOAT32", + "DIM", + str(self.vector_dims), + "DISTANCE_METRIC", + self.vector_distance_metric, + ]) + + try: + await client.custom_command(["FT.CREATE", *args]) # pyright: ignore[reportUnknownMemberType] + except Exception as exc: + # Index already exists is not an error + if "Index already exists" in str(exc): + pass + else: + raise IntegrationInvalidRequestException(f"Failed to create Valkey search index: {exc}") from exc + + self._index_created = True + + async def _add(self, *, data: list[dict[str, Any]]) -> None: + """Insert documents into Valkey as HASH keys. + + Partition fields (application_id, agent_id, user_id) are defaulted + from the provider's configuration if not already present in each document. + """ + await self._ensure_index() + client = await self._get_client() + + for doc in data: + doc_id = f"{self.prefix}{uuid.uuid4().hex}" + + # Default partition fields if not already set (defensive, like Redis provider) + doc.setdefault("application_id", self.application_id or "") + doc.setdefault("agent_id", self.agent_id or "") + doc.setdefault("user_id", self.user_id or "") + + field_map: dict[str | bytes, str | bytes] = {} + for key, value in doc.items(): + if key == self.vector_field_name: + continue + field_map[key] = str(value) + + if self.embed_fn is not None and self.vector_field_name and "content" in doc: + try: + import numpy as np + except ImportError as exc: + raise IntegrationInvalidRequestException( + "Vector support requires the optional dependency 'numpy'. " + "Install agent-framework-valkey[vector] to enable vector search." + ) from exc + + embedding: list[float] = await self.embed_fn(doc["content"]) + vec_bytes: bytes = np.asarray(embedding, dtype=np.float32).tobytes() + field_map[self.vector_field_name] = vec_bytes + + await client.hset(doc_id, field_map) # pyright: ignore[reportArgumentType] + + async def _search( + self, + text: str, + *, + num_results: int = 10, + ) -> list[dict[str, Any]]: + """Run a text or hybrid vector-text search with scope filters. + + Args: + text: The search query text. + num_results: Maximum number of results to return. + + Returns: + A list of document dicts with at least a "content" field. + """ + await self._ensure_index() + client = await self._get_client() + + q = (text or "").strip() + if not q: + raise IntegrationInvalidRequestException("search requires non-empty text") + + # Build filter expression from scope fields + filter_parts: list[str] = [] + if self.application_id: + filter_parts.append(f"@application_id:{{{self._escape_tag(self.application_id)}}}") + if self.agent_id: + filter_parts.append(f"@agent_id:{{{self._escape_tag(self.agent_id)}}}") + if self.user_id: + filter_parts.append(f"@user_id:{{{self._escape_tag(self.user_id)}}}") + + filter_expr = " ".join(filter_parts) if filter_parts else "*" + + # Fields to return from FT.SEARCH — excludes the vector field to avoid + # binary data in text-oriented parsing. + return_fields = ["content", "role", "conversation_id", "message_id", "author_name", + "application_id", "agent_id", "user_id", "thread_id"] + + try: + result: Any + if self.embed_fn is not None and self.vector_field_name: + # Hybrid: vector KNN with pre-filter + try: + import numpy as np + except ImportError as exc: + raise IntegrationInvalidRequestException( + "Vector search requires the optional 'numpy' dependency. " + "Install agent-framework-valkey[vector]." + ) from exc + + embedding: list[float] = await self.embed_fn(q) + vec_bytes: bytes = np.asarray(embedding, dtype=np.float32).tobytes() + + query_str = f"({filter_expr})=>[KNN {num_results} @{self.vector_field_name} $vec AS score]" + result = await client.custom_command([ # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType] + "FT.SEARCH", + self.index_name, + query_str, + "RETURN", + str(len(return_fields) + 1), # +1 for the "score" alias + *return_fields, + "score", + "PARAMS", + "2", + "vec", + vec_bytes, + "SORTBY", + "score", + "LIMIT", + "0", + str(num_results), + "DIALECT", + "2", + ]) + else: + # Text-only search + escaped_text = self._escape_query(q) + query_str = f"{filter_expr} {escaped_text}" if filter_parts else escaped_text + result = await client.custom_command([ # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType] + "FT.SEARCH", + self.index_name, + query_str, + "RETURN", + str(len(return_fields)), + *return_fields, + "LIMIT", + "0", + str(num_results), + ]) + + return self._parse_search_results(result, vector_field_name=self.vector_field_name) + except IntegrationInvalidRequestException: + raise + except Exception as exc: + raise IntegrationInvalidRequestException(f"Valkey search failed: {exc}") from exc + + async def search_all(self, page_size: int = 200) -> list[dict[str, Any]]: + """Return all documents in the index. + + Note: This method is unscoped — it returns documents across all + application_id/agent_id/user_id partitions. Use for debugging, + testing, and administrative tasks only. + + Args: + page_size: Number of documents per page. Defaults to 200. + + Returns: + A list of all document dicts in the index. + """ + await self._ensure_index() + client = await self._get_client() + + all_docs: list[dict[str, Any]] = [] + offset = 0 + while True: + result: Any = await client.custom_command([ # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType] + "FT.SEARCH", + self.index_name, + "*", + "LIMIT", + str(offset), + str(page_size), + ]) + page = self._parse_search_results(result, vector_field_name=self.vector_field_name) + if not page: + break + all_docs.extend(page) + if len(page) < page_size: + break + offset += page_size + return all_docs + + @staticmethod + def _decode_field_value(v: Any, field_name: str, vector_field_name: str | None) -> str | None: + """Decode a field value from FT.SEARCH results, skipping vector fields. + + Args: + v: The raw value from the search result. + field_name: The name of the field being decoded. + vector_field_name: The name of the vector field to skip. + + Returns: + The decoded string value, or None if the field should be skipped. + """ + if field_name == vector_field_name: + return None + if isinstance(v, bytes): + try: + return v.decode("utf-8") + except UnicodeDecodeError: + # Binary data (e.g. leftover vector bytes) — skip gracefully + return None + return str(v) + + @staticmethod + def _parse_search_results(result: Any, *, vector_field_name: str | None = None) -> list[dict[str, Any]]: + """Parse FT.SEARCH response into a list of document dicts.""" + docs: list[dict[str, Any]] = [] + if not result or not isinstance(result, list): + return docs + + result_list = cast(list[Any], result) + if len(result_list) < 2: + return docs + + # Valkey 9.1+ returns dict format: [total_count, {doc_id: {field: value, ...}, ...}] + if isinstance(result_list[1], dict): + for _doc_id, fields in result_list[1].items(): # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType] + if isinstance(fields, dict): + doc: dict[str, Any] = {} + for k, v in fields.items(): # pyright: ignore[reportUnknownVariableType] + key: str = k.decode("utf-8") if isinstance(k, bytes) else str(k) # pyright: ignore[reportUnknownArgumentType] + value = ValkeyContextProvider._decode_field_value(v, key, vector_field_name) + if value is not None: + doc[key] = value + docs.append(doc) + return docs + + # Legacy flat list format: [total_count, doc_id, [field, value, ...], ...] + i = 1 + while i < len(result_list): + if i + 1 < len(result_list) and isinstance(result_list[i + 1], list): + fields = cast(list[Any], result_list[i + 1]) + doc = {} + for j in range(0, len(fields), 2): + key = fields[j].decode("utf-8") if isinstance(fields[j], bytes) else str(fields[j]) + value = ValkeyContextProvider._decode_field_value( + fields[j + 1], key, vector_field_name + ) + if value is not None: + doc[key] = value + docs.append(doc) + i += 2 + else: + i += 1 + + return docs + + @staticmethod + def _escape_tag(value: str) -> str: + """Escape special characters in a TAG filter value.""" + special = r".,<>{}[]\"':;!@#$%^&*()-+=~/ " + escaped: list[str] = [] + for ch in value: + if ch in special: + escaped.append(f"\\{ch}") + else: + escaped.append(ch) + return "".join(escaped) + + @staticmethod + def _escape_query(text: str) -> str: + """Escape special characters in a full-text query.""" + special = r"@!{}()|\-=~[]^\"':*$>+/" + escaped: list[str] = [] + for ch in text: + if ch in special: + escaped.append(f"\\{ch}") + else: + escaped.append(ch) + return "".join(escaped) + + def _validate_filters(self) -> None: + """Validate that at least one scope filter is provided.""" + if not self.agent_id and not self.user_id and not self.application_id: + raise ValueError("At least one of the filters: agent_id, user_id, or application_id is required.") + + async def aclose(self) -> None: + """Close the Valkey connection if owned by this instance.""" + if self._owns_client and self._client is not None: + await self._client.close() + self._client = None + + async def __aenter__(self) -> Self: + """Async context manager entry.""" + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: Any, + ) -> None: + """Async context manager exit.""" + await self.aclose() + + +__all__ = ["ValkeyContextProvider"] diff --git a/python/packages/valkey/conftest.py b/python/packages/valkey/conftest.py new file mode 100644 index 00000000000..6490a1da061 --- /dev/null +++ b/python/packages/valkey/conftest.py @@ -0,0 +1,10 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Skip valkey test collection when valkey-glide is not installed (e.g., on Windows).""" + +collect_ignore_glob: list[str] = [] + +try: + import glide # noqa: F401 +except ImportError: + collect_ignore_glob.append("tests/*.py") diff --git a/python/packages/valkey/pyproject.toml b/python/packages/valkey/pyproject.toml new file mode 100644 index 00000000000..18a02ac0920 --- /dev/null +++ b/python/packages/valkey/pyproject.toml @@ -0,0 +1,102 @@ +[project] +name = "agent-framework-valkey" +description = "Valkey integration for Microsoft Agent Framework." +authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}] +readme = "README.md" +requires-python = ">=3.10" +version = "1.0.0b260421" +license-files = ["LICENSE"] +urls.homepage = "https://aka.ms/agent-framework" +urls.source = "https://github.com/microsoft/agent-framework/tree/main/python" +urls.release_notes = "https://github.com/microsoft/agent-framework/releases?q=tag%3Apython-1&expanded=true" +urls.issues = "https://github.com/microsoft/agent-framework/issues" +classifiers = [ + "License :: OSI Approved :: MIT License", + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Typing :: Typed", +] +dependencies = [ + "agent-framework-core>=1.1.0,<2", + "valkey-glide>=2.4.0,<3; sys_platform != 'win32'", +] + +[project.optional-dependencies] +vector = [ + "numpy>=2.2.6,<3", +] + +[tool.uv] +prerelease = "if-necessary-or-explicit" +environments = [ + "sys_platform == 'darwin'", + "sys_platform == 'linux'" +] + +[tool.uv-dynamic-versioning] +fallback-version = "0.0.0" + +[tool.pytest.ini_options] +testpaths = 'tests' +addopts = "-ra -q -r fEX" +asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "function" +filterwarnings = [ + "ignore:Support for class-based `config` is deprecated:DeprecationWarning:pydantic.*" +] +timeout = 120 +markers = [ + "integration: marks tests as integration tests that require external services", +] + +[tool.ruff] +extend = "../../pyproject.toml" + +[tool.coverage.run] +omit = [ + "**/__init__.py" +] + +[tool.pyright] +extends = "../../pyproject.toml" +include = ["agent_framework_valkey"] + +[tool.mypy] +plugins = ['pydantic.mypy'] +strict = true +python_version = "3.10" +ignore_missing_imports = true +disallow_untyped_defs = true +no_implicit_optional = true +check_untyped_defs = true +warn_return_any = true +show_error_codes = true +warn_unused_ignores = false +disallow_incomplete_defs = true +disallow_untyped_decorators = true + +[tool.bandit] +targets = ["agent_framework_valkey"] +exclude_dirs = ["tests"] + +[tool.poe] +executor.type = "uv" +include = "../../shared_tasks.toml" + +[tool.poe.tasks.mypy] +help = "Run MyPy for this package." +cmd = "mypy --config-file $POE_ROOT/pyproject.toml agent_framework_valkey" + +[tool.poe.tasks.test] +help = "Run the default unit test suite for this package." +cmd = 'pytest -m "not integration" --cov=agent_framework_valkey --cov-report=term-missing:skip-covered tests' + +[build-system] +requires = ["flit-core >= 3.11,<4.0"] +build-backend = "flit_core.buildapi" diff --git a/python/packages/valkey/tests/test_providers.py b/python/packages/valkey/tests/test_providers.py new file mode 100644 index 00000000000..773339b2478 --- /dev/null +++ b/python/packages/valkey/tests/test_providers.py @@ -0,0 +1,602 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Tests for ValkeyContextProvider and ValkeyChatMessageStore.""" + +from __future__ import annotations + +import json +from unittest.mock import AsyncMock + +import pytest +from agent_framework import AgentResponse, Message +from agent_framework._sessions import AgentSession, SessionContext +from agent_framework.exceptions import IntegrationInvalidRequestException + +np = pytest.importorskip("numpy") + +from agent_framework_valkey._chat_message_store import ValkeyChatMessageStore +from agent_framework_valkey._context_provider import ValkeyContextProvider + +# --------------------------------------------------------------------------- +# Shared fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture +def mock_glide_client() -> AsyncMock: + """Create a mock GlideClient for testing.""" + client = AsyncMock() + client.lrange = AsyncMock(return_value=[]) + client.llen = AsyncMock(return_value=0) + client.ltrim = AsyncMock() + client.rpush = AsyncMock() + client.delete = AsyncMock() + client.hset = AsyncMock() + client.custom_command = AsyncMock(return_value=[0]) + client.close = AsyncMock() + return client + + +# =========================================================================== +# ValkeyChatMessageStore tests +# =========================================================================== + + +class TestValkeyChatMessageStoreInit: + def test_basic_construction(self, mock_glide_client: AsyncMock) -> None: + store = ValkeyChatMessageStore(source_id="mem", client=mock_glide_client) + assert store.source_id == "mem" + assert store.key_prefix == "chat_messages" + assert store.max_messages is None + assert store.load_messages is True + assert store.store_outputs is True + assert store.store_inputs is True + + def test_custom_params(self, mock_glide_client: AsyncMock) -> None: + store = ValkeyChatMessageStore( + source_id="mem", + client=mock_glide_client, + key_prefix="custom", + max_messages=50, + load_messages=False, + store_outputs=False, + store_inputs=False, + ) + assert store.key_prefix == "custom" + assert store.max_messages == 50 + assert store.load_messages is False + assert store.store_outputs is False + assert store.store_inputs is False + + def test_default_source_id(self, mock_glide_client: AsyncMock) -> None: + store = ValkeyChatMessageStore(client=mock_glide_client) + assert store.source_id == "valkey_memory" + + def test_mutually_exclusive_url_and_host_raises(self) -> None: + with pytest.raises(ValueError, match="mutually exclusive"): + ValkeyChatMessageStore(valkey_url="valkey://other:6380", host="myhost", port=6380) + + def test_mutually_exclusive_url_and_default_host_raises(self) -> None: + with pytest.raises(ValueError, match="mutually exclusive"): + ValkeyChatMessageStore(valkey_url="valkey://other:6380", host="localhost", port=6379) + + +class TestValkeyChatMessageStoreKey: + def test_key_format(self, mock_glide_client: AsyncMock) -> None: + store = ValkeyChatMessageStore(client=mock_glide_client, key_prefix="msgs") + assert store._valkey_key("session-123") == "msgs:session-123" + assert store._valkey_key(None) == "msgs:default" + + +class TestValkeyChatMessageStoreParseUrl: + def test_parse_valkey_url(self) -> None: + host, port = ValkeyChatMessageStore._parse_url("valkey://myhost:6380") + assert host == "myhost" + assert port == 6380 + + def test_parse_redis_url(self) -> None: + host, port = ValkeyChatMessageStore._parse_url("redis://localhost:6379") + assert host == "localhost" + assert port == 6379 + + def test_parse_url_defaults(self) -> None: + host, port = ValkeyChatMessageStore._parse_url("valkey://") + assert host == "localhost" + assert port == 6379 + + +class TestValkeyChatMessageStoreGetMessages: + async def test_returns_deserialized_messages(self, mock_glide_client: AsyncMock) -> None: + msg1 = Message(role="user", contents=["Hello"]) + msg2 = Message(role="assistant", contents=["Hi!"]) + mock_glide_client.lrange = AsyncMock(return_value=[json.dumps(msg1.to_dict()), json.dumps(msg2.to_dict())]) + store = ValkeyChatMessageStore(client=mock_glide_client) + + messages = await store.get_messages("s1") + assert len(messages) == 2 + assert messages[0].role == "user" + assert messages[0].text == "Hello" + assert messages[1].role == "assistant" + assert messages[1].text == "Hi!" + + async def test_handles_bytes_response(self, mock_glide_client: AsyncMock) -> None: + msg = Message(role="user", contents=["Hello"]) + mock_glide_client.lrange = AsyncMock(return_value=[json.dumps(msg.to_dict()).encode("utf-8")]) + store = ValkeyChatMessageStore(client=mock_glide_client) + + messages = await store.get_messages("s1") + assert len(messages) == 1 + assert messages[0].text == "Hello" + + async def test_empty_returns_empty(self, mock_glide_client: AsyncMock) -> None: + mock_glide_client.lrange = AsyncMock(return_value=[]) + store = ValkeyChatMessageStore(client=mock_glide_client) + + messages = await store.get_messages("s1") + assert messages == [] + + +class TestValkeyChatMessageStoreSaveMessages: + async def test_saves_batched_messages(self, mock_glide_client: AsyncMock) -> None: + store = ValkeyChatMessageStore(client=mock_glide_client) + msgs = [Message(role="user", contents=["Hello"]), Message(role="assistant", contents=["Hi"])] + + await store.save_messages("s1", msgs) + + # Single batched rpush call + mock_glide_client.rpush.assert_called_once() + call_args = mock_glide_client.rpush.call_args[0] + assert call_args[0] == "chat_messages:s1" + assert len(call_args[1]) == 2 + + async def test_empty_messages_noop(self, mock_glide_client: AsyncMock) -> None: + store = ValkeyChatMessageStore(client=mock_glide_client) + + await store.save_messages("s1", []) + mock_glide_client.rpush.assert_not_called() + + async def test_max_messages_trimming(self, mock_glide_client: AsyncMock) -> None: + store = ValkeyChatMessageStore(client=mock_glide_client, max_messages=10) + + await store.save_messages("s1", [Message(role="user", contents=["msg"])]) + + # LTRIM is always called unconditionally when max_messages is set + mock_glide_client.ltrim.assert_called_once_with("chat_messages:s1", -10, -1) + # No LLEN roundtrip needed + mock_glide_client.llen.assert_not_called() + + async def test_no_trim_when_no_max(self, mock_glide_client: AsyncMock) -> None: + store = ValkeyChatMessageStore(client=mock_glide_client) + + await store.save_messages("s1", [Message(role="user", contents=["msg"])]) + + mock_glide_client.ltrim.assert_not_called() + + +class TestValkeyChatMessageStoreClear: + async def test_clear_calls_delete(self, mock_glide_client: AsyncMock) -> None: + store = ValkeyChatMessageStore(client=mock_glide_client) + + await store.clear("session-1") + mock_glide_client.delete.assert_called_once_with(["chat_messages:session-1"]) + + async def test_clear_none_session_uses_default(self, mock_glide_client: AsyncMock) -> None: + store = ValkeyChatMessageStore(client=mock_glide_client) + + await store.clear(None) + mock_glide_client.delete.assert_called_once_with(["chat_messages:default"]) + + +class TestValkeyChatMessageStoreContextManager: + async def test_aenter_returns_self(self, mock_glide_client: AsyncMock) -> None: + store = ValkeyChatMessageStore(client=mock_glide_client) + async with store as s: + assert s is store + + +class TestValkeyChatMessageStoreBeforeAfterRun: + """Test before_run/after_run integration via HistoryProvider defaults.""" + + async def test_before_run_loads_history(self, mock_glide_client: AsyncMock) -> None: + msg = Message(role="user", contents=["old msg"]) + mock_glide_client.lrange = AsyncMock(return_value=[json.dumps(msg.to_dict())]) + store = ValkeyChatMessageStore(client=mock_glide_client) + + session = AgentSession(session_id="test") + ctx = SessionContext(input_messages=[Message(role="user", contents=["new msg"])], session_id="s1") + + await store.before_run( + agent=None, session=session, context=ctx, state=session.state.setdefault(store.source_id, {}) + ) # type: ignore[arg-type] + + assert store.source_id in ctx.context_messages + assert len(ctx.context_messages[store.source_id]) == 1 + assert ctx.context_messages[store.source_id][0].text == "old msg" + + async def test_after_run_stores_input_and_response(self, mock_glide_client: AsyncMock) -> None: + store = ValkeyChatMessageStore(client=mock_glide_client) + + session = AgentSession(session_id="test") + ctx = SessionContext(input_messages=[Message(role="user", contents=["hi"])], session_id="s1") + ctx._response = AgentResponse(messages=[Message(role="assistant", contents=["hello"])]) + + await store.after_run( + agent=None, session=session, context=ctx, state=session.state.setdefault(store.source_id, {}) + ) # type: ignore[arg-type] + + # Batched into single rpush call + mock_glide_client.rpush.assert_called_once() + + async def test_after_run_skips_when_no_messages(self, mock_glide_client: AsyncMock) -> None: + store = ValkeyChatMessageStore(client=mock_glide_client, store_inputs=False, store_outputs=False) + + session = AgentSession(session_id="test") + ctx = SessionContext(input_messages=[Message(role="user", contents=["hi"])], session_id="s1") + + await store.after_run( + agent=None, session=session, context=ctx, state=session.state.setdefault(store.source_id, {}) + ) # type: ignore[arg-type] + + mock_glide_client.rpush.assert_not_called() + + +# =========================================================================== +# ValkeyContextProvider tests +# =========================================================================== + + +class TestValkeyContextProviderInit: + def test_basic_construction(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + assert provider.source_id == "ctx" + assert provider.user_id == "u1" + assert provider.host == "localhost" + assert provider.port == 6379 + assert provider.index_name == "context_idx" + assert provider.prefix == "context:" + + def test_custom_params(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider( + source_id="ctx", + host="custom-host", + port=6380, + index_name="my_idx", + prefix="my_prefix:", + application_id="app1", + agent_id="agent1", + user_id="user1", + context_prompt="Custom prompt", + client=mock_glide_client, + ) + assert provider.host == "custom-host" + assert provider.port == 6380 + assert provider.index_name == "my_idx" + assert provider.prefix == "my_prefix:" + assert provider.application_id == "app1" + assert provider.agent_id == "agent1" + assert provider.context_prompt == "Custom prompt" + + def test_default_context_prompt(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + assert "Memories" in provider.context_prompt + + def test_valkey_url_support(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider( + source_id="ctx", user_id="u1", valkey_url="valkey://myhost:6380", client=mock_glide_client + ) + assert provider.valkey_url == "valkey://myhost:6380" + + def test_mutually_exclusive_url_and_host_raises(self) -> None: + with pytest.raises(ValueError, match="mutually exclusive"): + ValkeyContextProvider( + source_id="ctx", user_id="u1", valkey_url="valkey://other:6380", host="myhost", port=6380 + ) + + def test_mutually_exclusive_url_and_default_host_raises(self) -> None: + with pytest.raises(ValueError, match="mutually exclusive"): + ValkeyContextProvider( + source_id="ctx", user_id="u1", valkey_url="valkey://other:6380", host="localhost", port=6379 + ) + + def test_embed_fn_requires_vector_config(self, mock_glide_client: AsyncMock) -> None: + mock_embed = AsyncMock(return_value=[0.1] * 128) + with pytest.raises(ValueError, match="vector_field_name and vector_dims are required"): + ValkeyContextProvider( + source_id="ctx", user_id="u1", client=mock_glide_client, embed_fn=mock_embed + ) + + def test_embed_fn_requires_positive_dims(self, mock_glide_client: AsyncMock) -> None: + mock_embed = AsyncMock(return_value=[0.1] * 128) + with pytest.raises(ValueError, match="vector_dims must be a positive integer"): + ValkeyContextProvider( + source_id="ctx", + user_id="u1", + client=mock_glide_client, + embed_fn=mock_embed, + vector_field_name="embedding", + vector_dims=0, + ) + + +class TestValkeyContextProviderValidateFilters: + def test_no_filters_raises(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider(source_id="ctx", client=mock_glide_client) + with pytest.raises(ValueError, match="(?i)at least one"): + provider._validate_filters() + + def test_any_single_filter_ok(self, mock_glide_client: AsyncMock) -> None: + for kwargs in [{"user_id": "u"}, {"agent_id": "a"}, {"application_id": "app"}]: + provider = ValkeyContextProvider(source_id="ctx", client=mock_glide_client, **kwargs) + provider._validate_filters() # should not raise + + +class TestValkeyContextProviderBeforeRun: + async def test_search_results_added_to_context(self, mock_glide_client: AsyncMock) -> None: + mock_glide_client.custom_command = AsyncMock( + return_value=[2, b"doc:1", [b"content", b"Memory A"], b"doc:2", [b"content", b"Memory B"]] + ) + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + session = AgentSession(session_id="test-session") + ctx = SessionContext(input_messages=[Message(role="user", contents=["test query"])], session_id="s1") + + await provider.before_run( + agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {}) + ) # type: ignore[arg-type] + + assert "ctx" in ctx.context_messages + msgs = ctx.context_messages["ctx"] + assert len(msgs) == 1 + assert "Memory A" in msgs[0].text + assert "Memory B" in msgs[0].text + + async def test_empty_input_no_search(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + session = AgentSession(session_id="test-session") + ctx = SessionContext(input_messages=[Message(role="user", contents=[" "])], session_id="s1") + + await provider.before_run( + agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {}) + ) # type: ignore[arg-type] + + mock_glide_client.custom_command.assert_not_called() + assert "ctx" not in ctx.context_messages + + async def test_empty_results_no_messages(self, mock_glide_client: AsyncMock) -> None: + mock_glide_client.custom_command = AsyncMock(return_value=[0]) + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + session = AgentSession(session_id="test-session") + ctx = SessionContext(input_messages=[Message(role="user", contents=["hello"])], session_id="s1") + + await provider.before_run( + agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {}) + ) # type: ignore[arg-type] + + assert "ctx" not in ctx.context_messages + + +class TestValkeyContextProviderAfterRun: + async def test_stores_messages(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + session = AgentSession(session_id="test-session") + response = AgentResponse(messages=[Message(role="assistant", contents=["response text"])]) + ctx = SessionContext(input_messages=[Message(role="user", contents=["user input"])], session_id="s1") + ctx._response = response + + await provider.after_run( + agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {}) + ) # type: ignore[arg-type] + + assert mock_glide_client.hset.call_count == 2 + + async def test_skips_empty_conversations(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + session = AgentSession(session_id="test-session") + ctx = SessionContext(input_messages=[Message(role="user", contents=[" "])], session_id="s1") + + await provider.after_run( + agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {}) + ) # type: ignore[arg-type] + + mock_glide_client.hset.assert_not_called() + + async def test_stores_partition_fields(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider( + source_id="ctx", application_id="app", agent_id="ag", user_id="u1", client=mock_glide_client + ) + session = AgentSession(session_id="test-session") + ctx = SessionContext(input_messages=[Message(role="user", contents=["hello"])], session_id="s1") + + await provider.after_run( + agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {}) + ) # type: ignore[arg-type] + + assert mock_glide_client.hset.call_count == 1 + field_dict = mock_glide_client.hset.call_args[0][1] + assert field_dict["application_id"] == "app" + assert field_dict["agent_id"] == "ag" + assert field_dict["user_id"] == "u1" + + +class TestValkeyContextProviderContextManager: + async def test_aenter_returns_self(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + async with provider as p: + assert p is provider + + async def test_aclose_closes_owned_client(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + # Simulate owned client + provider._owns_client = True + await provider.aclose() + mock_glide_client.close.assert_called_once() + assert provider._client is None + + +class TestValkeyContextProviderEnsureIndex: + async def test_creates_index_on_first_call(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + await provider._ensure_index() + + mock_glide_client.custom_command.assert_called_once() + cmd_args = mock_glide_client.custom_command.call_args[0][0] + assert cmd_args[0] == "FT.CREATE" + assert provider._index_created is True + + async def test_skips_on_subsequent_calls(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + await provider._ensure_index() + await provider._ensure_index() + + assert mock_glide_client.custom_command.call_count == 1 + + async def test_handles_index_already_exists(self, mock_glide_client: AsyncMock) -> None: + mock_glide_client.custom_command = AsyncMock(side_effect=Exception("Index already exists")) + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + + await provider._ensure_index() # should not raise + assert provider._index_created is True + + async def test_includes_vector_field_in_schema(self, mock_glide_client: AsyncMock) -> None: + mock_embed = AsyncMock(return_value=[0.1] * 128) + provider = ValkeyContextProvider( + source_id="ctx", + user_id="u1", + client=mock_glide_client, + embed_fn=mock_embed, + vector_field_name="embedding", + vector_dims=128, + ) + await provider._ensure_index() + + cmd_args = mock_glide_client.custom_command.call_args[0][0] + assert "embedding" in cmd_args + assert "VECTOR" in cmd_args + assert "128" in cmd_args + + +class TestValkeyContextProviderHybridSearch: + """Tests for the vector/hybrid search path (embed_fn provided).""" + + async def test_add_stores_raw_bytes_embedding(self, mock_glide_client: AsyncMock) -> None: + mock_embed = AsyncMock(return_value=[0.1, 0.2, 0.3]) + provider = ValkeyContextProvider( + source_id="ctx", + user_id="u1", + client=mock_glide_client, + embed_fn=mock_embed, + vector_field_name="embedding", + vector_dims=3, + ) + + await provider._add(data=[{"content": "test", "role": "user"}]) + + mock_embed.assert_called_once_with("test") + # Verify the embedding is stored as raw bytes, not hex string + hset_call = mock_glide_client.hset.call_args[0] + field_map = hset_call[1] + stored_embedding = field_map["embedding"] + assert isinstance(stored_embedding, bytes) + expected = np.asarray([0.1, 0.2, 0.3], dtype=np.float32).tobytes() + assert stored_embedding == expected + + async def test_search_passes_raw_bytes_vector(self, mock_glide_client: AsyncMock) -> None: + mock_embed = AsyncMock(return_value=[0.1, 0.2, 0.3]) + mock_glide_client.custom_command = AsyncMock(return_value=[0]) + provider = ValkeyContextProvider( + source_id="ctx", + user_id="u1", + client=mock_glide_client, + embed_fn=mock_embed, + vector_field_name="embedding", + vector_dims=3, + ) + provider._index_created = True + + await provider._search(text="test query") + + # Verify FT.SEARCH was called with raw bytes in PARAMS + search_call = mock_glide_client.custom_command.call_args[0][0] + assert search_call[0] == "FT.SEARCH" + # Find the "vec" param value (follows "vec" in the args) + vec_idx = search_call.index("vec") + 1 + vec_value = search_call[vec_idx] + assert isinstance(vec_value, bytes) + + async def test_hybrid_search_constructs_knn_query(self, mock_glide_client: AsyncMock) -> None: + mock_embed = AsyncMock(return_value=[0.1] * 128) + mock_glide_client.custom_command = AsyncMock(return_value=[1, b"doc:1", [b"content", b"result"]]) + provider = ValkeyContextProvider( + source_id="ctx", + user_id="u1", + client=mock_glide_client, + embed_fn=mock_embed, + vector_field_name="embedding", + vector_dims=128, + ) + provider._index_created = True + + results = await provider._search(text="test") + + search_call = mock_glide_client.custom_command.call_args[0][0] + query_str = search_call[2] + assert "KNN" in query_str + assert "@embedding" in query_str + assert len(results) == 1 + assert results[0]["content"] == "result" + + +class TestValkeyContextProviderSearchErrors: + async def test_empty_text_raises_directly(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + provider._index_created = True + + with pytest.raises(IntegrationInvalidRequestException, match="non-empty text"): + await provider._search(text="") + + async def test_connection_error_wrapped(self, mock_glide_client: AsyncMock) -> None: + # First call succeeds (FT.CREATE), second fails (FT.SEARCH) + mock_glide_client.custom_command = AsyncMock(side_effect=[None, ConnectionError("connection lost")]) + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + + with pytest.raises(IntegrationInvalidRequestException, match="Valkey search failed"): + await provider._search(text="test") + + async def test_direct_integration_exception_not_double_wrapped(self, mock_glide_client: AsyncMock) -> None: + provider = ValkeyContextProvider(source_id="ctx", user_id="u1", client=mock_glide_client) + provider._index_created = True + + # Empty text should raise IntegrationInvalidRequestException directly, not wrapped + with pytest.raises(IntegrationInvalidRequestException, match="non-empty text"): + await provider._search(text=" ") + + +class TestValkeyContextProviderParseResults: + def test_parse_empty_results(self) -> None: + assert ValkeyContextProvider._parse_search_results([0]) == [] + assert ValkeyContextProvider._parse_search_results(None) == [] + assert ValkeyContextProvider._parse_search_results([]) == [] + + def test_parse_results_with_docs(self) -> None: + result = [2, b"doc:1", [b"content", b"Hello"], b"doc:2", [b"content", b"World"]] + docs = ValkeyContextProvider._parse_search_results(result) + assert len(docs) == 2 + assert docs[0]["content"] == "Hello" + assert docs[1]["content"] == "World" + + def test_parse_results_with_string_fields(self) -> None: + result = [1, "doc:1", ["content", "Hello", "role", "user"]] + docs = ValkeyContextProvider._parse_search_results(result) + assert len(docs) == 1 + assert docs[0]["content"] == "Hello" + assert docs[0]["role"] == "user" + + +class TestValkeyContextProviderEscaping: + def test_escape_tag(self) -> None: + assert ValkeyContextProvider._escape_tag("simple") == "simple" + assert "\\" in ValkeyContextProvider._escape_tag("has space") + assert "\\" in ValkeyContextProvider._escape_tag("has@special") + + def test_escape_query(self) -> None: + assert ValkeyContextProvider._escape_query("simple text") == "simple text" + assert "\\" in ValkeyContextProvider._escape_query("@mention") diff --git a/python/pyproject.toml b/python/pyproject.toml index 49a28434561..bb837a414aa 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -215,6 +215,7 @@ executionEnvironments = [ { root = "packages/orchestrations/tests", reportPrivateUsage = "none" }, { root = "packages/purview/tests", reportPrivateUsage = "none" }, { root = "packages/redis/tests", reportPrivateUsage = "none" }, + { root = "packages/valkey/tests", reportPrivateUsage = "none" }, { root = "tests", reportPrivateUsage = "none" }, ] diff --git a/python/samples/02-agents/context_providers/valkey_sample.py b/python/samples/02-agents/context_providers/valkey_sample.py new file mode 100644 index 00000000000..a60890995f6 --- /dev/null +++ b/python/samples/02-agents/context_providers/valkey_sample.py @@ -0,0 +1,283 @@ +# Copyright (c) Microsoft. All rights reserved. + +""" +Valkey Integration Sample — ValkeyChatMessageStore + ValkeyContextProvider + +Demonstrates both Valkey-backed components against a local Valkey instance: + 1. ValkeyChatMessageStore — persistent chat history across sessions + 2. ValkeyContextProvider — long-term memory via text search (no embeddings needed) + +Prerequisites: + - Valkey running locally (9.1+ required for full-text search): + docker run -d --name valkey -p 6379:6379 valkey/valkey-bundle:9.1.0-rc1 + - AWS credentials configured (for Bedrock) + - Install: uv pip install agent-framework-valkey agent-framework-bedrock python-dotenv +""" + +from __future__ import annotations + +import asyncio +import contextlib +import os + +from agent_framework import Agent, AgentSession, tool +from agent_framework_bedrock import BedrockChatClient +from agent_framework_valkey import ValkeyChatMessageStore, ValkeyContextProvider +from dotenv import load_dotenv + +load_dotenv() + +VALKEY_HOST = os.getenv("VALKEY_HOST", "localhost") +VALKEY_PORT = int(os.getenv("VALKEY_PORT", "6379")) +BEDROCK_REGION = os.getenv("BEDROCK_REGION", "us-east-2") +BEDROCK_MODEL = os.getenv("BEDROCK_CHAT_MODEL", "us.anthropic.claude-opus-4-5-20251101-v1:0") + + +@tool +def get_current_time() -> str: + """Get the current date and time.""" + from datetime import datetime + + return datetime.now().isoformat() + + +def make_agent( + name: str, + instructions: str, + context_providers: list, +) -> Agent: + return Agent( + client=BedrockChatClient(region=BEDROCK_REGION, model=BEDROCK_MODEL), + name=name, + instructions=instructions, + context_providers=context_providers, + tools=[get_current_time], + ) + + +# --------------------------------------------------------------------------- +# Part 1: Chat Message Store — persistent history +# --------------------------------------------------------------------------- +async def demo_chat_message_store() -> None: + print("=" * 60) + print("Part 1: ValkeyChatMessageStore — Persistent Chat History") + print("=" * 60) + + history = ValkeyChatMessageStore( + source_id="valkey_demo_history", + host=VALKEY_HOST, + port=VALKEY_PORT, + key_prefix="demo_chat", + max_messages=20, + ) + + agent = make_agent( + name="HistoryBot", + instructions="You are a helpful assistant. Be concise.", + context_providers=[history], + ) + + # --- Session 1: establish facts --- + session = agent.create_session() + session_id = session.session_id + + print(f"\n[Session {session_id[:8]}…] Starting conversation") + + for msg in [ + "Hi! My name is [insert name here] and I work on AI agent frameworks.", + "I really enjoy cycling and live in Seattle.", + ]: + print(f" User: {msg}") + resp = await agent.run(msg, session=session) + print(f" Agent: {resp.text}\n") + + # Serialize session (simulates app restart) + saved = session.to_dict() + + # --- Session 2: resume and verify recall --- + print(f"[Session {session_id[:8]}…] Resuming after 'restart'") + restored = AgentSession.from_dict(saved) + + msg = "What do you remember about me?" + print(f" User: {msg}") + resp = await agent.run(msg, session=restored) + print(f" Agent: {resp.text}\n") + + # Verify data in Valkey + from glide import GlideClient, GlideClientConfiguration, NodeAddress + + client = await GlideClient.create( + GlideClientConfiguration(addresses=[NodeAddress(host=VALKEY_HOST, port=VALKEY_PORT)]) + ) + key = f"demo_chat:{session_id}" + count = await client.llen(key) + print(f" ✓ Valkey key '{key}' has {count} messages stored") + await client.close() + + await history.aclose() + + +# --------------------------------------------------------------------------- +# Part 2: Context Provider — long-term memory via text search +# --------------------------------------------------------------------------- +async def demo_context_provider() -> None: + print("\n" + "=" * 60) + print("Part 2: ValkeyContextProvider — Long-Term Memory (Text Search)") + print("=" * 60) + + context_provider = ValkeyContextProvider( + source_id="valkey_demo_memory", + host=VALKEY_HOST, + port=VALKEY_PORT, + index_name="demo_memory_idx", + prefix="demo_mem:", + agent_id="demo_agent", + ) + + agent = make_agent( + name="MemoryBot", + instructions=( + "You are a helpful assistant with long-term memory. " + "Use the memories provided to personalize your responses. Be concise." + ), + context_providers=[context_provider], + ) + + # --- Conversation 1: teach the agent some facts --- + session1 = agent.create_session() + print(f"\n[Conversation 1 — {session1.session_id[:8]}…] Teaching facts") + + for msg in [ + "I'm building a Valkey integration for the Microsoft Agent Framework.", + "My favorite programming language is Python and I use Bedrock for LLMs.", + ]: + print(f" User: {msg}") + resp = await agent.run(msg, session=session1) + print(f" Agent: {resp.text}\n") + + # --- Conversation 2: new session, agent should recall from Valkey --- + session2 = agent.create_session() + print(f"[Conversation 2 — {session2.session_id[:8]}…] Testing recall") + + msg = "What do you know about my projects and preferences?" + print(f" User: {msg}") + resp = await agent.run(msg, session=session2) + print(f" Agent: {resp.text}\n") + + # Verify data in Valkey + from glide import GlideClient, GlideClientConfiguration, NodeAddress + + client = await GlideClient.create( + GlideClientConfiguration(addresses=[NodeAddress(host=VALKEY_HOST, port=VALKEY_PORT)]) + ) + result = await client.custom_command(["FT.INFO", "demo_memory_idx"]) # noqa: F841 + print(" ✓ Valkey search index 'demo_memory_idx' exists") + + # Count stored documents + scan_result = await client.scan("0", match="demo_mem:*", count=1000) + doc_count = len(scan_result[1]) if scan_result[1] else 0 + print(f" ✓ {doc_count} memory documents stored in Valkey") + await client.close() + + await context_provider.aclose() + + +# --------------------------------------------------------------------------- +# Part 3: Both together +# --------------------------------------------------------------------------- +async def demo_combined() -> None: + print("\n" + "=" * 60) + print("Part 3: Combined — History + Context Provider") + print("=" * 60) + + history = ValkeyChatMessageStore( + source_id="valkey_combined_history", + host=VALKEY_HOST, + port=VALKEY_PORT, + key_prefix="combined_chat", + ) + + context = ValkeyContextProvider( + source_id="valkey_combined_memory", + host=VALKEY_HOST, + port=VALKEY_PORT, + index_name="combined_mem_idx", + prefix="combined_mem:", + agent_id="combined_agent", + ) + + agent = make_agent( + name="FullBot", + instructions=( + "You are a helpful assistant with both conversation history and long-term memory. " + "Be concise." + ), + context_providers=[history, context], + ) + + session = agent.create_session() + print(f"\n[Session {session.session_id[:8]}…]") + + for msg in [ + "I'm evaluating Valkey for our agent infrastructure.", + "What are the key advantages of Valkey?", + ]: + print(f" User: {msg}") + resp = await agent.run(msg, session=session) + print(f" Agent: {resp.text}\n") + + await history.aclose() + await context.aclose() + print(" ✓ Both providers worked together successfully") + + +# --------------------------------------------------------------------------- +# Cleanup +# --------------------------------------------------------------------------- +async def cleanup() -> None: + """Remove demo keys from Valkey.""" + from glide import GlideClient, GlideClientConfiguration, NodeAddress + + client = await GlideClient.create( + GlideClientConfiguration(addresses=[NodeAddress(host=VALKEY_HOST, port=VALKEY_PORT)]) + ) + + # Drop indexes + for idx in ["demo_memory_idx", "combined_mem_idx"]: + with contextlib.suppress(Exception): + await client.custom_command(["FT.DROPINDEX", idx]) + + # Delete keys by pattern + for pattern in ["demo_chat:*", "demo_mem:*", "combined_chat:*", "combined_mem:*"]: + cursor: str | bytes = "0" + while True: + result = await client.scan(cursor, match=pattern, count=1000) + cursor = result[0] + keys = result[1] + if keys: + str_keys = [k.decode("utf-8") if isinstance(k, bytes) else str(k) for k in keys] + await client.delete(str_keys) + cursor_str = cursor.decode("utf-8") if isinstance(cursor, bytes) else str(cursor) + if cursor_str == "0": + break + + await client.close() + print("\n✓ Cleanup complete — demo keys removed from Valkey") + + +async def main() -> None: + print("Valkey Integration Sample") + print(f"Prerequisites: Valkey on {VALKEY_HOST}:{VALKEY_PORT}, AWS credentials configured\n") + + try: + await demo_chat_message_store() + await demo_context_provider() + await demo_combined() + finally: + await cleanup() + + print("\n🎉 All demos completed successfully!") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/python/uv.lock b/python/uv.lock index 4ac7509f35d..dfdb97159ea 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -57,6 +57,7 @@ members = [ "agent-framework-purview", "agent-framework-redis", "agent-framework-tools", + "agent-framework-valkey", ] constraints = [ { name = "fastapi-sso", specifier = ">=0.19.0" }, @@ -830,6 +831,29 @@ requires-dist = [ { name = "psutil", specifier = ">=5.9" }, ] +[[package]] +name = "agent-framework-valkey" +version = "1.0.0b260421" +source = { editable = "packages/valkey" } +dependencies = [ + { name = "agent-framework-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "valkey-glide", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] + +[package.optional-dependencies] +vector = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform == 'win32')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and sys_platform == 'darwin') or (python_full_version >= '3.11' and sys_platform == 'linux') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, +] + +[package.metadata] +requires-dist = [ + { name = "agent-framework-core", editable = "packages/core" }, + { name = "numpy", marker = "extra == 'vector'", specifier = ">=2.2.6,<3" }, + { name = "valkey-glide", marker = "sys_platform != 'win32'", specifier = ">=2.3.1,<3" }, +] +provides-extras = ["vector"] + [[package]] name = "agentlightning" version = "0.2.2" @@ -884,11 +908,11 @@ wheels = [ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, + { url = "https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" }, ] [[package]] @@ -1016,9 +1040,9 @@ name = "aiologic" version = "0.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sniffio", marker = "(python_full_version < '3.13' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform == 'linux') or (python_full_version < '3.13' and sys_platform == 'win32')" }, + { name = "sniffio", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "(python_full_version < '3.13' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform == 'linux') or (python_full_version < '3.13' and sys_platform == 'win32')" }, - { name = "wrapt", marker = "(python_full_version < '3.13' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform == 'linux') or (python_full_version < '3.13' and sys_platform == 'win32')" }, + { name = "wrapt", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/13/50b91a3ea6b030d280d2654be97c48b6ed81753a50286ee43c646ba36d3c/aiologic-0.16.0.tar.gz", hash = "sha256:c267ccbd3ff417ec93e78d28d4d577ccca115d5797cdbd16785a551d9658858f", size = 225952, upload-time = "2025-11-27T23:48:41.195Z" } wheels = [ @@ -1151,36 +1175,35 @@ wheels = [ [[package]] name = "azure-ai-agentserver-core" -version = "2.0.0b3" +version = "2.0.0b5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "azure-monitor-opentelemetry-exporter", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "hypercorn", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "microsoft-opentelemetry", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "opentelemetry-exporter-otlp-proto-grpc", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "opentelemetry-sdk", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "starlette", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/84/29/1a9606d5252b02d77070a1b633dd0c26fe65a0f4a0fb0cfdaa751e2ed458/azure_ai_agentserver_core-2.0.0b3.tar.gz", hash = "sha256:e295b19a65d53c513929f52f0862bbb815cc9e9fc29d2a2825452f3136260123", size = 42573, upload-time = "2026-04-23T04:13:16.717Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/06/7c88b6506d26ee625a967cef762e6a155ed7ab8812f3f1e45ec1a950b8ae/azure_ai_agentserver_core-2.0.0b5.tar.gz", hash = "sha256:f03dc737351e5d847e9fc18c5b78b261436de368f1317a0c29957cc2179c37d1", size = 46273, upload-time = "2026-05-25T12:48:01.739Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/9b/1fc87c05b55821f33c46c5e8a3b97a573aa2fc4bff387e75cca1a87800b4/azure_ai_agentserver_core-2.0.0b3-py3-none-any.whl", hash = "sha256:5ef921eb9fd9c0f15682fe930320fae50dccfa915d7518f9a16d99014bbcb3cb", size = 29127, upload-time = "2026-04-23T04:13:17.976Z" }, + { url = "https://files.pythonhosted.org/packages/68/80/a43a269601512793b220c36dc0864b44d806b969dbfe14f1ecc3b5f5202b/azure_ai_agentserver_core-2.0.0b5-py3-none-any.whl", hash = "sha256:0d00c298892e2ff466b32235d5d9c55b57054f0e8fcedb0726eacd7684e1aa89", size = 31521, upload-time = "2026-05-25T12:48:03.072Z" }, ] [[package]] name = "azure-ai-agentserver-invocations" -version = "1.0.0b3" +version = "1.0.0b4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-ai-agentserver-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/95/ebab2b06777352b33dd4c407fa5624765b7443d3b4b5fb6cb1f51660643b/azure_ai_agentserver_invocations-1.0.0b3.tar.gz", hash = "sha256:1eaad3ae8dc6a28038b9a16c7b5f853fda33202c1ea57559992a6c6fe71952a4", size = 31002, upload-time = "2026-04-23T04:30:29.449Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/8a/1ec5e3212d0fc6ffd11dbbfef252a1f80e3305993f57069654abffa3ac0a/azure_ai_agentserver_invocations-1.0.0b4.tar.gz", hash = "sha256:4d78748e36b6120fd24161baea6c54de9687b3fe3f607a97766dab30e4f810ed", size = 56727, upload-time = "2026-05-21T22:45:25.581Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/43/a421671296ae33b62af3a034869fa82ff1979e5f455a29924d30ae1b8307/azure_ai_agentserver_invocations-1.0.0b3-py3-none-any.whl", hash = "sha256:771a15a3509e049b56f71c43c87a3fdeecd12addddcae0f80339990adc41e678", size = 11433, upload-time = "2026-04-23T04:30:30.412Z" }, + { url = "https://files.pythonhosted.org/packages/6b/c7/882e349459a0e3bfe63ca681f722cc7d5807df5e4aa348d879e3dc05d4c0/azure_ai_agentserver_invocations-1.0.0b4-py3-none-any.whl", hash = "sha256:3da2712b4be44afa8f3f25f00f5268588edcddaee08686e40b3589fafa71cdbe", size = 18782, upload-time = "2026-05-21T22:45:26.547Z" }, ] [[package]] name = "azure-ai-agentserver-responses" -version = "1.0.0b5" +version = "1.0.0b7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -1188,9 +1211,9 @@ dependencies = [ { name = "azure-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "isodate", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/27/3ecb7fe704ff8764199bfbe4cc1e584a520a9affe042470d9d50b6e1e73a/azure_ai_agentserver_responses-1.0.0b5.tar.gz", hash = "sha256:0b627b810359c792ea7b6fa6782abaf6df32d9bc9e5a569ad722afcffd0ce8d9", size = 410908, upload-time = "2026-04-23T04:31:15.414Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/53/febb6f3453f5dc1e0b6dc47d4e5198b64605d1f83c847255946f74bc300e/azure_ai_agentserver_responses-1.0.0b7.tar.gz", hash = "sha256:2f67cdfc0219cb0ab86800dadb1cfdb40ab4aa0413dae7ffa5ea4ea84eec3eb0", size = 419032, upload-time = "2026-05-25T12:48:38.81Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/91/1e5c0d7ce95ca8b022e69e4ca6b23e413fc2d57f0191429c4633e02213d2/azure_ai_agentserver_responses-1.0.0b5-py3-none-any.whl", hash = "sha256:4c2a6ab56e71eeb330aa52b7cb2cc71b8ec6b5bbe0e7dc84310f2c7fbda393a3", size = 268362, upload-time = "2026-04-23T04:31:17.014Z" }, + { url = "https://files.pythonhosted.org/packages/b3/94/48825357e009f7db3b6b5d0a9344a7ab3304e32f06f50328b2393e3b06cb/azure_ai_agentserver_responses-1.0.0b7-py3-none-any.whl", hash = "sha256:efb5271f24a297bacde9769359308e54e870f66ad4d3b4826ae97a77e40e94d4", size = 268063, upload-time = "2026-05-25T12:48:40.817Z" }, ] [[package]] @@ -1446,16 +1469,16 @@ wheels = [ [[package]] name = "botocore" -version = "1.43.11" +version = "1.43.14" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "python-dateutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "urllib3", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/fa/4bec16fa5a4cde7b593e549238bfeb8ed1bdba9d427888a18c460a1f2352/botocore-1.43.11.tar.gz", hash = "sha256:d7d479cc2809ec2728f2898521003adfb79bfe6a4615c59dfd222ec52b0cee6b", size = 15364020, upload-time = "2026-05-19T19:39:58.317Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/3c/798d2f7deb118241930c7c6bcfb0b970d3f0245bf580700663199aeed2c3/botocore-1.43.14.tar.gz", hash = "sha256:b9e500737e43d2f147c9d4e23b54360335e77d4c0ba90a318f51b65e06cb8516", size = 15382604, upload-time = "2026-05-22T19:28:36.363Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/9a/9f1d955c2eebefb6bd20de740ae7a05e7b015c63f0f01dba338dcf29cc68/botocore-1.43.11-py3-none-any.whl", hash = "sha256:0108b5604df5a26918936c845e1e761866ee9ea8d1c1f9358ed3c69afdc37436", size = 15043467, upload-time = "2026-05-19T19:39:53.176Z" }, + { url = "https://files.pythonhosted.org/packages/27/7e/6e64821077cd2efc4aa51b7d638fb6d48e1c7c450201c529fbaf1de8bfd3/botocore-1.43.14-py3-none-any.whl", hash = "sha256:1f4a2a95ea78c10398e78431e98c1fe47adb54a7b10a32975144c1f541186658", size = 15061424, upload-time = "2026-05-22T19:28:32.682Z" }, ] [[package]] @@ -1579,11 +1602,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, ] [[package]] @@ -1792,14 +1815,14 @@ wheels = [ [[package]] name = "click" -version = "8.4.0" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/e4/796662cd90cf80e3a363c99db2b88e0e394b988a575f60a17e16440cd011/click-8.4.0.tar.gz", hash = "sha256:638f1338fe1235c8f4e008e4a8a254fb5c5fbdcbb40ece3c9142ebb78e792973", size = 350843, upload-time = "2026-05-17T00:47:58.425Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/ae/8e92f8058baf87f6c7d86ee7e457668690195cc77efedb8d3797a06e3940/click-8.4.0-py3-none-any.whl", hash = "sha256:40c50b7c6c6adac2823d411041ec84f3f103f1b280d5e9ce0d7f998995832f81", size = 116147, upload-time = "2026-05-17T00:47:56.842Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, ] [[package]] @@ -1807,7 +1830,7 @@ name = "clr-loader" version = "0.2.10" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, + { name = "cffi", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/18/24/c12faf3f61614b3131b5c98d3bf0d376b49c7feaa73edca559aeb2aee080/clr_loader-0.2.10.tar.gz", hash = "sha256:81f114afbc5005bafc5efe5af1341d400e22137e275b042a8979f3feb9fc9446", size = 83605, upload-time = "2026-01-03T23:13:06.984Z" } wheels = [ @@ -2186,7 +2209,7 @@ name = "culsans" version = "0.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiologic", marker = "(python_full_version < '3.13' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform == 'linux') or (python_full_version < '3.13' and sys_platform == 'win32')" }, + { name = "aiologic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "(python_full_version < '3.13' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform == 'linux') or (python_full_version < '3.13' and sys_platform == 'win32')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/e3/49afa1bc180e0d28008ec6bcdf82a4072d1c7a41032b5b759b60814ca4b0/culsans-0.11.0.tar.gz", hash = "sha256:0b43d0d05dce6106293d114c86e3fb4bfc63088cfe8ff08ed3fe36891447fe33", size = 107546, upload-time = "2025-12-31T23:15:38.196Z" } @@ -2790,56 +2813,72 @@ wheels = [ [[package]] name = "greenlet" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3c/3f/dbf99fb14bfeb88c28f16729215478c0e265cacd6dc22270c8f31bb6892f/greenlet-3.5.0.tar.gz", hash = "sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4", size = 196995, upload-time = "2026-04-27T13:37:15.544Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/03/84359833f7e1d49a883e92777637c592306030e30cee5e2b1e6476f95c88/greenlet-3.5.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:29ea813b2e1f45fa9649a17853b2b5465c4072fbcb072e5af6cd3a288216574a", size = 283502, upload-time = "2026-04-27T12:20:55.213Z" }, - { url = "https://files.pythonhosted.org/packages/25/ce/6f9f008266273aa14a2e011945797ac5802b97b8b40efe7afe1ee6c1afc9/greenlet-3.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:804a70b328e706b785c6ef16187051c394a63dd1a906d89be24b6ad77759f13f", size = 600508, upload-time = "2026-04-27T12:52:37.876Z" }, - { url = "https://files.pythonhosted.org/packages/e0/6d/b0f3272c2368ea2c1aa19a5ad70db0be8f8dff6e6d3d1eb82efa00cbcf19/greenlet-3.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:884f649de075b84739713d41dd4dfd41e2b910bfb769c4a3ea02ec1da52cd9bb", size = 613283, upload-time = "2026-04-27T12:59:37.957Z" }, - { url = "https://files.pythonhosted.org/packages/ed/ac/0b509b6fb93551ce5a01612ee1acda7f7dda4bbb66c99aeb2ab403d205dc/greenlet-3.5.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b28037cb07768933c54d81bfe47a85f9f402f57d7d69743b991a713b63954eb", size = 613418, upload-time = "2026-04-27T12:25:23.852Z" }, - { url = "https://files.pythonhosted.org/packages/03/03/2b2b680ec87aaa97998fb5b8d76658d4d3560386864f17efab33ba7c2e24/greenlet-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cda05425526240807408156b6960a17a79a0c760b813573b67027823be760977", size = 1572229, upload-time = "2026-04-27T12:53:23.509Z" }, - { url = "https://files.pythonhosted.org/packages/61/e4/42b259e7a19aff1a270a4bd82caf6353109ed6860c9454e18f37162b83ae/greenlet-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9c615f869163e14bb1ced20322d8038fb680b08236521ac3f30cd4c1288785a0", size = 1639886, upload-time = "2026-04-27T12:25:22.325Z" }, - { url = "https://files.pythonhosted.org/packages/6f/b4/733ca47b883b67c57f90d3ecb21055c9ec753597d10754ac201644061f9d/greenlet-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:ba8f0bdc2fae6ce915dfd0c16d2d00bca7e4247c1eae4416e06430e522137858", size = 237795, upload-time = "2026-04-27T12:21:40.118Z" }, - { url = "https://files.pythonhosted.org/packages/8b/0f/a91f143f356523ff682309732b175765a9bc2836fd7c081c2c67fedc1ad4/greenlet-3.5.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8f1cc966c126639cd152fdaa52624d2655f492faa79e013fea161de3e6dda082", size = 284726, upload-time = "2026-04-27T12:20:51.402Z" }, - { url = "https://files.pythonhosted.org/packages/95/82/800646c7ffc5dbabd75ddd2f6b519bb898c0c9c969e5d0473bfe5d20bcce/greenlet-3.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:362624e6a8e5bca3b8233e45eef33903a100e9539a2b995c364d595dbc4018b3", size = 604264, upload-time = "2026-04-27T12:52:39.494Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ac/354867c0bba812fc33b15bc55aedafedd0aee3c7dd91dfca22444157dc0c/greenlet-3.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5ecd83806b0f4c2f53b1018e0005cd82269ea01d42befc0368730028d850ed1c", size = 616099, upload-time = "2026-04-27T12:59:39.623Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b0/815bece7399e01cadb69014219eebd0042339875c59a59b0820a46ece356/greenlet-3.5.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ff251e9a0279522e62f6176412869395a64ddf2b5c5f782ff609a8216a4e662", size = 615198, upload-time = "2026-04-27T12:25:25.928Z" }, - { url = "https://files.pythonhosted.org/packages/10/80/3b2c0a895d6698f6ddb31b07942ebfa982f3e30888bc5546a5b5990de8b2/greenlet-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d874e79afd41a96e11ff4c5d0bc90a80973e476fda1c2c64985667397df432b", size = 1574927, upload-time = "2026-04-27T12:53:25.81Z" }, - { url = "https://files.pythonhosted.org/packages/44/0e/f354af514a4c61454dbc68e44d47544a5a4d6317e30b77ddfa3a09f4c5f3/greenlet-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0ed006e4b86c59de7467eb2601cd1b77b5a7d657d1ee55e30fe30d76451edba4", size = 1642683, upload-time = "2026-04-27T12:25:23.9Z" }, - { url = "https://files.pythonhosted.org/packages/fa/6a/87f38255201e993a1915265ebb80cd7c2c78b04a45744995abbf6b259fd8/greenlet-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:703cb211b820dbffbbc55a16bfc6e4583a6e6e990f33a119d2cc8b83211119c8", size = 238115, upload-time = "2026-04-27T12:21:48.845Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f8/450fe3c5938fa737ea4d22699772e6e34e8e24431a47bf4e8a1ceed4a98e/greenlet-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:6c18dfb59c70f5a94acd271c72e90128c3c776e41e5f07767908c8c1b74ad339", size = 235017, upload-time = "2026-04-27T12:22:26.768Z" }, - { url = "https://files.pythonhosted.org/packages/ef/32/f2ce6d4cac3e55bc6173f92dbe627e782e1850f89d986c3606feb63aafa7/greenlet-3.5.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:db2910d3c809444e0a20147361f343fe2798e106af8d9d8506f5305302655a9f", size = 286228, upload-time = "2026-04-27T12:20:34.421Z" }, - { url = "https://files.pythonhosted.org/packages/b7/aa/caed9e5adf742315fc7be2a84196373aab4816e540e38ba0d76cb7584d68/greenlet-3.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec9ea74e7268ace7f9aab1b1a4e730193fc661b39a993cd91c606c32d4a3628", size = 601775, upload-time = "2026-04-27T12:52:41.045Z" }, - { url = "https://files.pythonhosted.org/packages/c7/af/90ae08497400a941595d12774447f752d3dfe0fbb012e35b76bc5c0ff37e/greenlet-3.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54d243512da35485fc7a6bf3c178fdda6327a9d6506fcdd62b1abd1e41b2927b", size = 614436, upload-time = "2026-04-27T12:59:41.595Z" }, - { url = "https://files.pythonhosted.org/packages/2b/e0/2e13df68f367e2f9960616927d60857dd7e56aaadd59a47c644216b2f920/greenlet-3.5.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d280a7f5c331622c69f97eb167f33577ff2d1df282c41cd15907fc0a3ca198c", size = 611388, upload-time = "2026-04-27T12:25:28.008Z" }, - { url = "https://files.pythonhosted.org/packages/82/f7/393c64055132ac0d488ef6be549253b7e6274194863967ddc0bc8f5b87b8/greenlet-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1eb67d5adefb5bd2e182d42678a328979a209e4e82eb93575708185d31d1f588", size = 1570768, upload-time = "2026-04-27T12:53:28.099Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4b/eaf7735253522cf56d1b74d672a58f54fc114702ceaf05def59aae72f6e1/greenlet-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2628d6c86f6cb0cb45e0c3c54058bbec559f57eaae699447748cb3928150577e", size = 1635983, upload-time = "2026-04-27T12:25:26.903Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fe/4fb3a0805bd5165da5ebf858da7cc01cce8061674106d2cf5bdab32cbfde/greenlet-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4d9f0624c775f2dfc56ba54d515a8c771044346852a918b405914f6b19d7fd8", size = 238840, upload-time = "2026-04-27T12:23:54.806Z" }, - { url = "https://files.pythonhosted.org/packages/cb/cb/baa584cb00532126ffe12d9787db0a60c5a4f55c27bfe2666df5d4c30a32/greenlet-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:83ed9f27f1680b50e89f40f6df348a290ea234b249a4003d366663a12eab94f2", size = 235615, upload-time = "2026-04-27T12:21:38.57Z" }, - { url = "https://files.pythonhosted.org/packages/0c/58/fc576f99037ce19c5aa16628e4c3226b6d1419f72a62c79f5f40576e6eb3/greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106", size = 285066, upload-time = "2026-04-27T12:23:05.033Z" }, - { url = "https://files.pythonhosted.org/packages/4a/ba/b28ddbe6bfad6a8ac196ef0e8cff37bc65b79735995b9e410923fffeeb70/greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b", size = 604414, upload-time = "2026-04-27T12:52:42.358Z" }, - { url = "https://files.pythonhosted.org/packages/09/06/4b69f8f0b67603a8be2790e55107a190b376f2627fe0eaf5695d85ffb3cd/greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e", size = 617349, upload-time = "2026-04-27T12:59:43.32Z" }, - { url = "https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13", size = 613927, upload-time = "2026-04-27T12:25:30.28Z" }, - { url = "https://files.pythonhosted.org/packages/ee/e1/bd0af6213c7dd33175d8a462d4c1fe1175124ebed4855bc1475a5b5242c2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba", size = 1570893, upload-time = "2026-04-27T12:53:29.483Z" }, - { url = "https://files.pythonhosted.org/packages/9b/2a/0789702f864f5382cb476b93d7a9c823c10472658102ccd65f415747d2e2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846", size = 1636060, upload-time = "2026-04-27T12:25:28.845Z" }, - { url = "https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5", size = 238740, upload-time = "2026-04-27T12:24:01.341Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b7/9c5c3d653bd4ff614277c049ac676422e2c557db47b4fe43e6313fc005dc/greenlet-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:47422135b1d308c14b2c6e758beedb1acd33bb91679f5670edf77bf46244722b", size = 235525, upload-time = "2026-04-27T12:23:12.308Z" }, - { url = "https://files.pythonhosted.org/packages/94/5e/a70f31e3e8d961c4ce589c15b28e4225d63704e431a23932a3808cbcc867/greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8", size = 285564, upload-time = "2026-04-27T12:23:08.555Z" }, - { url = "https://files.pythonhosted.org/packages/af/a6/046c0a28e21833e4086918218cfb3d8bed51c075a1b700f20b9d7861c0f4/greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1", size = 651166, upload-time = "2026-04-27T12:52:43.644Z" }, - { url = "https://files.pythonhosted.org/packages/47/f8/4af27f71c5ff32a7fbc516adb46370d9c4ae2bc7bd3dc7d066ac542b4b15/greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3", size = 663792, upload-time = "2026-04-27T12:59:44.93Z" }, - { url = "https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7", size = 660933, upload-time = "2026-04-27T12:25:33.276Z" }, - { url = "https://files.pythonhosted.org/packages/83/e4/b903e5a5fae1e8a28cdd32a0cfbfd560b668c25b692f67768822ddc5f40f/greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf", size = 1618401, upload-time = "2026-04-27T12:53:31.062Z" }, - { url = "https://files.pythonhosted.org/packages/0e/e3/5ec408a329acb854fb607a122e1ee5fb3ff649f9a97952948a90803c0d8e/greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16", size = 1682038, upload-time = "2026-04-27T12:25:31.838Z" }, - { url = "https://files.pythonhosted.org/packages/91/20/6b165108058767ee643c55c5c4904d591a830ee2b3c7dbd359828fbc829f/greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033", size = 239835, upload-time = "2026-04-27T12:24:54.136Z" }, - { url = "https://files.pythonhosted.org/packages/4e/62/1c498375cee177b55d980c1db319f26470e5309e54698c8f8fc06c0fd539/greenlet-3.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:a96fcee45e03fe30a62669fd16ab5c9d3c172660d3085605cb1e2d1280d3c988", size = 236862, upload-time = "2026-04-27T12:23:24.957Z" }, - { url = "https://files.pythonhosted.org/packages/78/a8/4522939255bb5409af4e87132f915446bf3622c2c292d14d3c38d128ae82/greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853", size = 293614, upload-time = "2026-04-27T12:24:12.874Z" }, - { url = "https://files.pythonhosted.org/packages/15/5e/8744c52e2c027b5a8772a01561934c8835f869733e101f62075c60430340/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f", size = 650723, upload-time = "2026-04-27T12:52:45.412Z" }, - { url = "https://files.pythonhosted.org/packages/00/ef/7b4c39c03cf46ceca512c5d3f914afd85aa30b2cc9a93015b0dd73e4be6c/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7", size = 656529, upload-time = "2026-04-27T12:59:46.295Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b5/c7768f352f5c010f92064d0063f987e7dc0cd290a6d92a34109015ce4aa1/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112", size = 654364, upload-time = "2026-04-27T12:25:35.64Z" }, - { url = "https://files.pythonhosted.org/packages/ef/d0/079ebe12e4b1fc758857ce5be1a5e73f06870f2101e52611d1e71925ce54/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2", size = 1614204, upload-time = "2026-04-27T12:53:32.618Z" }, - { url = "https://files.pythonhosted.org/packages/6d/89/6c2fb63df3596552d20e58fb4d96669243388cf680cff222758812c7bfaa/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2", size = 1675480, upload-time = "2026-04-27T12:25:34.168Z" }, - { url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324, upload-time = "2026-04-27T12:24:40.295Z" }, +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/6e/802acd792aebb2256fbbee8cacf2727faaeb6f240ac11008f09eae4414bc/greenlet-3.5.1.tar.gz", hash = "sha256:5a56aeb7d5d9cc4b3a735efb5095bd4b4f6f0e4f93e5ca876d0e2315137b7829", size = 197356, upload-time = "2026-05-20T15:05:03.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/21/117c8710abb7f146d804a124c07eb5964a60b90d02b72452885aecc18efa/greenlet-3.5.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7eacb17a9d41538a2bc4912eba5ef13823c83cb69e4d141d0813debe7163187f", size = 283510, upload-time = "2026-05-20T13:12:26.475Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f7/6762a56fa5f6c2295c449c6524e10ce481e381c994cc44d9d03aef0700fb/greenlet-3.5.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5cc9606aa5f4e0bde0d3bd502b44f743864c3ffa5cfa1011b1e30f5aa02366f", size = 599696, upload-time = "2026-05-20T14:00:02.906Z" }, + { url = "https://files.pythonhosted.org/packages/0f/05/85a511e68ee109aff0aa00b4b497806091dd2d82ce209e49c6e801bd5d92/greenlet-3.5.1-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c3d35f87c7253b715d13d679e0783d845910144f282cb939fe1ba4ac8616269c", size = 612618, upload-time = "2026-05-20T14:05:39.202Z" }, + { url = "https://files.pythonhosted.org/packages/89/b8/8b83d18ae07c46c019617f35afd7b47aab7f9b4fbb12fc637d681e10bdd8/greenlet-3.5.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:540dae7b956209af4d70a3be35927b4055f617763771e5e84a5255bea934d2f5", size = 612947, upload-time = "2026-05-20T13:14:23.469Z" }, + { url = "https://files.pythonhosted.org/packages/5d/14/ad1f9fc9b82384c010212464a3702bd911f95dab2f1180bc6fbcfb1f958c/greenlet-3.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed8cdb691169715a9a492844a83246f090182247d1a5031dc78a403f68ba1e97", size = 1571425, upload-time = "2026-05-20T14:02:22.671Z" }, + { url = "https://files.pythonhosted.org/packages/46/1c/43b8203cf10f4292c9e3d270e9e5f5ade79115a0a0ca5ea6f1be5f8915a7/greenlet-3.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d59e840387076a51016777a9328b3f2c427c6f9208a6e958bad251be50a648d", size = 1638688, upload-time = "2026-05-20T13:14:30.026Z" }, + { url = "https://files.pythonhosted.org/packages/ac/6e/0344b1e99f58f71715456e46492101fd2daa408957b8186ade0a4b515da7/greenlet-3.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:b9152fca4a6466e114aaec745ae61cba739903a109754a9d4e1262f01e9259b1", size = 237763, upload-time = "2026-05-20T13:11:35.659Z" }, + { url = "https://files.pythonhosted.org/packages/42/3c/ff890b466eaba2b0f5e6bdfff025f8c75f41b8ffdc3dbc3d24ad261e764a/greenlet-3.5.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:73f78f9b9f0a5c06e5c946ba1e8e36f5114923b6be109ee618c54f079c3ea14f", size = 284764, upload-time = "2026-05-20T13:09:10.204Z" }, + { url = "https://files.pythonhosted.org/packages/81/0e/5e5457be3d256918f6a4756f073548a3f0190836e2cc94aa6d0d617a940b/greenlet-3.5.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0cbed8bb44e23c5b199f888f4e4ce096b45ad9f25ff74a7ad0213875e936bb2", size = 603479, upload-time = "2026-05-20T14:00:04.757Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e1/f89a21d58d308298e6f275f13a1b472ed96c680b601a371b08be6a725989/greenlet-3.5.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a203a8bd0acb0701653d3bbb26e404854a68674139ed5cbb778830f42b09bb33", size = 615495, upload-time = "2026-05-20T14:05:40.87Z" }, + { url = "https://files.pythonhosted.org/packages/75/de/af6cef182862d2ccd6975440d21c9058a77c3f9b469abf94e322dfd2e0e3/greenlet-3.5.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a271fcd66c74615cda6a964fda3f304267a12e50a084472218a39bb0376f563", size = 614754, upload-time = "2026-05-20T13:14:24.947Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c6/50e520283a9f19388a7326b05f9e8637e566003475eacaadad04f558c68d/greenlet-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ded7b068c7c31c1a8657d4fd42d886b3e051ae29f88b80c5ff9d502257b0f071", size = 1574097, upload-time = "2026-05-20T14:02:24.003Z" }, + { url = "https://files.pythonhosted.org/packages/21/1c/13abd1f4860d987fa5e1170a01930d6e6cd40d328de487a3c9fdaff0ffd0/greenlet-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0932b81d72f552ded9d810d00021b64d89f2195a91ce115b893f943b7a4ab3c", size = 1641058, upload-time = "2026-05-20T13:14:31.83Z" }, + { url = "https://files.pythonhosted.org/packages/f5/56/5f332b7705545eac2dc01b4e9254d24a793f2656d55d5cc6b94ee59d22ae/greenlet-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:88e300d136eac057b2397aa1cfd7328b4c87c7eb66a09c7bc6a1292234db474e", size = 238089, upload-time = "2026-05-20T13:14:03.229Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a9/a3c2fa886c5b94863fb0e61b3bc14610b7aa94cf4f17f8741b11708305fc/greenlet-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:cc6ab7e555c8a112ad3a76e368e86e12a2754bcae1652a5602e133ec7b635523", size = 234989, upload-time = "2026-05-20T13:08:27.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/37/4549f149c9797c21b32c2683c33522af22522099de128b2406672526d005/greenlet-3.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fa4f98af3a528f0c3fd592a26df7f376f93329c8f4d987f6bb979057af8bf5e2", size = 286220, upload-time = "2026-05-20T13:07:28.463Z" }, + { url = "https://files.pythonhosted.org/packages/38/ff/a4f436709716965eaab9f36ea7b906c8a927fbe32fb1372a2071d964f6b1/greenlet-3.5.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffea73584b216150eab159b6d12348fb253e68757974de1e2c40d8a318ac89ed", size = 601585, upload-time = "2026-05-20T14:00:06.141Z" }, + { url = "https://files.pythonhosted.org/packages/65/ad/54bc3fcee3ad368a61b19b67d88117f7a8c29727bf71fffdeda81fbd946e/greenlet-3.5.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1072b4f9edcc1e192d9283a66a3e68d6b84c561de33a83d7858beb9ba1effe10", size = 614215, upload-time = "2026-05-20T14:05:42.675Z" }, + { url = "https://files.pythonhosted.org/packages/40/69/b91cda0647df839483201545913514c2827ebea5e5ccdf931842763bc127/greenlet-3.5.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:add5217d68b31130f0beca584d7fef4878327d2e31642b66618a14eef312b63b", size = 611358, upload-time = "2026-05-20T13:14:26.37Z" }, + { url = "https://files.pythonhosted.org/packages/59/90/3cf77e080350cd02fa307bb2abf05df48f4482c240275bbd2c203ba8bb1c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a5ea42a752d47a145eae922b605cd1634665ac3d5ec1e72402d5048e8d60d207", size = 1570475, upload-time = "2026-05-20T14:02:25.29Z" }, + { url = "https://files.pythonhosted.org/packages/65/2c/18cece62045e74598c3c393f70dce4a63f56222015ba29a5d4eeb04f764c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5551170cf4f5ff5623e9af81323751979fee2c731e2287b61f73cd27257b823", size = 1635625, upload-time = "2026-05-20T13:14:34.027Z" }, + { url = "https://files.pythonhosted.org/packages/30/f5/310d104ddf41eb5a70f4c268d22508dfb0c3c8e86fec152be34d0d2ed819/greenlet-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c8bb982ad117d29478ef8f5533e97df21f1e2befd17a299257b0c96d1371c0b", size = 238791, upload-time = "2026-05-20T13:10:39.018Z" }, + { url = "https://files.pythonhosted.org/packages/62/90/ceca11f504cd23a8047a3dea31919adc48df9b626dd0c13f0d858734fdfd/greenlet-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:80eb4b04dadc4e67df3fae179a32c4706a3f495bc7f22fc8a81115d5f5512188", size = 235580, upload-time = "2026-05-20T13:08:45.056Z" }, + { url = "https://files.pythonhosted.org/packages/27/69/7f7e5372d998b81001899b1c0823c957aa413ba0f2662e65821611cc31e4/greenlet-3.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:51518ff74664078fc51bffcc6fc529b0df5ae58da192691cee765d45ce944a2b", size = 285060, upload-time = "2026-05-20T13:08:51.899Z" }, + { url = "https://files.pythonhosted.org/packages/b1/bf/387f9b6b865fd2ae0d0be09e0004827295a01b71be76ed350dd1e28a91a4/greenlet-3.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ffdb3c0bb002c99cd8f298957e046c3dbf6006b5b7cdf11a4e19194624a0a0a", size = 604370, upload-time = "2026-05-20T14:00:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/32/f5/169ce3d4e4c67291bd18f8cbe0299c9f3e45102c7f1fb3c14780c93e4532/greenlet-3.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7715a5a2c3378ba602c3a440558261e13a820bb53a82693aacd7b7f6d964e283", size = 616987, upload-time = "2026-05-20T14:05:44.237Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e5/7f2e41d5273be07e77560d61ea4e56485b4d6c316d2a84518c62d1364061/greenlet-3.5.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc71ff466927a201b08305acac451ebe1aedfcea002f62f1f2f2ac2ac1e6a135", size = 613911, upload-time = "2026-05-20T13:14:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a4/fbdc67579b73615a1f91615e814303cc71e06128f7baaba87be79b8fb90c/greenlet-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cd443683db272ebaaca03af98c0b063ab30db70ea8a31a1559f35e3f7b744ccd", size = 1570689, upload-time = "2026-05-20T14:02:27.225Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b4/77abbe35078be39718a46cd49caf16bceb35662f97a34101dca28aa98e47/greenlet-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:089fff7a6ce8d9316d1f65ebc00273a56be258c1725b32b94de90a3a979557e1", size = 1635602, upload-time = "2026-05-20T13:14:36.344Z" }, + { url = "https://files.pythonhosted.org/packages/37/f7/129f27ca700845b8ee8ca88ce7f43435a1239c2eddb7677fc938822762cf/greenlet-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:110a1ca7b49b014b097f6078272c3f4ed31af45b254de5228b79adba879f6af9", size = 238683, upload-time = "2026-05-20T13:11:50.57Z" }, + { url = "https://files.pythonhosted.org/packages/6d/5c/a485a36e87df8d8fd0632ee01511244f5156a20ed3746cc6599340326395/greenlet-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f16ba1efc0715b680a18b8123d90dad887c6112ae3555b4b5c32c149540c6b4e", size = 235499, upload-time = "2026-05-20T13:12:42.028Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cb/c62454606daf5640369c94d8a9dd540599b1bfc090e2d2180cb77f4038d2/greenlet-3.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07", size = 285579, upload-time = "2026-05-20T13:08:56.396Z" }, + { url = "https://files.pythonhosted.org/packages/ec/71/c4270398c2eba968a6071af1dfbdcaeee6ec1c24bc8b435b8cc452700da6/greenlet-3.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea", size = 651106, upload-time = "2026-05-20T14:00:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ab/71e34b78a44ec271fb5f550c17bc46d301ddc5953890d935f270b0dcdb5a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2", size = 663478, upload-time = "2026-05-20T14:05:45.88Z" }, + { url = "https://files.pythonhosted.org/packages/77/96/4efd6fa5c62c85426a0c19077a586258ebc3a2a146ff2493e4312a697a22/greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c", size = 660800, upload-time = "2026-05-20T13:14:29.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e0/6c71401a25cac7000261304e866a2f2cc04dc74810d40e2f118aa4799495/greenlet-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0", size = 1617518, upload-time = "2026-05-20T14:02:28.662Z" }, + { url = "https://files.pythonhosted.org/packages/41/26/c5c06643e8c0af9e7bf18e16cb51d0ab7625155f0392e1c9015d66d556cd/greenlet-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc", size = 1681593, upload-time = "2026-05-20T13:14:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/8a/bd/e11a108317485075e68af9d23039619b86b28130c3b50d227d42edece64b/greenlet-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3", size = 239800, upload-time = "2026-05-20T13:09:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/47/f8/8e8e8417b7bf28639a5a56356ef934d0375e1d0c70a57e04d7701e870ffe/greenlet-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:7b5f5fae05b8ac6d176a61b60c394a8cbdc2b5b91b81793066e68745cf165e54", size = 236862, upload-time = "2026-05-20T13:09:10.498Z" }, + { url = "https://files.pythonhosted.org/packages/90/12/41bf27fde4d3605d3773ae57751eda182b8be2f5398011c041173b1d9534/greenlet-3.5.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad", size = 293637, upload-time = "2026-05-20T13:12:35.529Z" }, + { url = "https://files.pythonhosted.org/packages/44/44/ba14b23e9757707050c2f397d305bbcae62e5d7cad122f8b6baec5ae4a1f/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e", size = 650840, upload-time = "2026-05-20T14:00:11.079Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/5ddc2b686a6844f91abecef43411842426da2e1573f60b49ecf2547f4ae1/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986", size = 656416, upload-time = "2026-05-20T14:05:47.118Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f0/d17510297c35a2992712f0bf84de3779749999f7d3d63aa1f09db7c62dbe/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e", size = 654397, upload-time = "2026-05-20T13:14:30.696Z" }, + { url = "https://files.pythonhosted.org/packages/37/eb/147387705bb89092645b012586e7273cb5ed3c90ef7eaf3a69173eaf0209/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d", size = 1614469, upload-time = "2026-05-20T14:02:30.192Z" }, + { url = "https://files.pythonhosted.org/packages/a6/4e/37ee0da7732b7aa9896f17e15579a9df34b9fcb9dd494f0adfa749af6623/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78", size = 1675115, upload-time = "2026-05-20T13:14:40.972Z" }, + { url = "https://files.pythonhosted.org/packages/57/f3/97dfcf4a6eb5077f8a672234216fb5923eb89f2cab7081cb10b2cf75b605/greenlet-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2", size = 245246, upload-time = "2026-05-20T13:12:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/5d/73/d7f72e34b582f694f4a9b248162db7b09cc458a259ba8f0c0bfa1a34ea7d/greenlet-3.5.1-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541", size = 285575, upload-time = "2026-05-20T13:12:07.043Z" }, + { url = "https://files.pythonhosted.org/packages/df/59/fa9c6e87dc8ad27a95dabe2f29f372b733d05a8a67470f6c901ed9975655/greenlet-3.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de", size = 656428, upload-time = "2026-05-20T14:00:12.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f9/e753408871eaa61dfe35e619cfc67512b036fde99893685d50eea9e07146/greenlet-3.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64", size = 667064, upload-time = "2026-05-20T14:05:48.662Z" }, + { url = "https://files.pythonhosted.org/packages/96/27/5565b5b40389f1c7753003a07e21892fda8660926787036d5bc0308b8113/greenlet-3.5.1-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5", size = 665697, upload-time = "2026-05-20T13:14:32.943Z" }, + { url = "https://files.pythonhosted.org/packages/cf/82/e7de4178c0c2d1c9a5a3be3cc0b33e46a85b3ee4a77c071bf7ad8600e079/greenlet-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368", size = 1621256, upload-time = "2026-05-20T14:02:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/00/10/f2dddcf7dacac17dfc68691809589adad06135eb28930429cf58a6467a2f/greenlet-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26", size = 1685956, upload-time = "2026-05-20T13:14:42.55Z" }, + { url = "https://files.pythonhosted.org/packages/22/17/4a232b32133230ada52f70e9d7f5b65b0caef8772f01849bd8d149e7e4ca/greenlet-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab", size = 239802, upload-time = "2026-05-20T13:13:15.481Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ae/4e623a7e6d4d2a5f4cb8e4c82de4169fc637942caae68d6e676b8a128ac5/greenlet-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:92fd6d44ac5e5a887c8a5dc4a8ba0ba908527c31c12f78c6bc7dcfe8aab279f6", size = 236853, upload-time = "2026-05-20T13:15:37.301Z" }, + { url = "https://files.pythonhosted.org/packages/7a/57/816d9cff29119da3505b3d6a5e14a8af89006ac36f47f891ff293ee05af1/greenlet-3.5.1-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed", size = 293877, upload-time = "2026-05-20T13:10:19.078Z" }, + { url = "https://files.pythonhosted.org/packages/23/a1/59b0a7c7d140ff1a75626680b9a9899b79a9176cab298b394968fb023295/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244", size = 655333, upload-time = "2026-05-20T14:00:14.758Z" }, + { url = "https://files.pythonhosted.org/packages/72/1b/5efe127597625042218939d01855109f352779050768b670b52edcc16a6c/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c", size = 659443, upload-time = "2026-05-20T14:05:50.159Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6d/c404246ea4d22d097a7426d0efb5b781bd7eb67715f09e79001bd552ab18/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd", size = 658356, upload-time = "2026-05-20T13:14:35.091Z" }, + { url = "https://files.pythonhosted.org/packages/51/02/f8ee37fb6d2219329f350af241c27fcf12df57e723d11f6fc6d3bacdadaa/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e", size = 1619216, upload-time = "2026-05-20T14:02:33.403Z" }, + { url = "https://files.pythonhosted.org/packages/93/c5/3dc9475ace2c7a3680da12372cddd7f1ac874eb410a1ac48d3e9dab83782/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659", size = 1678427, upload-time = "2026-05-20T13:14:43.71Z" }, + { url = "https://files.pythonhosted.org/packages/df/4e/750c15c317a41ffb36f0bf40b933e3d744a7dede61889f74443ea69690cf/greenlet-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e", size = 245225, upload-time = "2026-05-20T13:13:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/4f/fd/d3baea2eeb7b617efd47e87ca06e2ec2c6118d303aa9e918e0ce16eadc10/greenlet-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:5028648bf2253ec4745add746129d3904121fa7fe871a76bed23c5720573ce0a", size = 239590, upload-time = "2026-05-20T13:13:37.382Z" }, ] [[package]] @@ -3083,7 +3122,7 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "1.15.0" +version = "1.16.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -3096,9 +3135,9 @@ dependencies = [ { name = "typer", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/b6/e22bd20a25299c34b8c5922c1545a6320825b13906eb0f7298edfd034a0b/huggingface_hub-1.15.0.tar.gz", hash = "sha256:28abfdddda3927fd4de6a63cf26ab012498a2c24dae52baf150c5c6edf98a1d5", size = 784100, upload-time = "2026-05-15T11:42:52.149Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/0f/ed994dbade67a54407c28cab96ef845e0e6d25500be56aca6394f8bfc9dd/huggingface_hub-1.16.1.tar.gz", hash = "sha256:7f1dc4c5ec21aed69be630ad0c3378616be16f3de1a47b141c0e812965d9c832", size = 792534, upload-time = "2026-05-21T18:40:00.908Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/11/0b64cc9024329b76d7547c19a67604a61d21d3ba678a69d1b220c29d5112/huggingface_hub-1.15.0-py3-none-any.whl", hash = "sha256:a4a59af04cbc41a3fe3fec429b171ef994ef8c971eda10136746f408dd4e3744", size = 663602, upload-time = "2026-05-15T11:42:50.487Z" }, + { url = "https://files.pythonhosted.org/packages/49/79/621a7dbb80c70974f73a597275351ebe03ce5bc65cb5f8f4acb5859252bc/huggingface_hub-1.16.1-py3-none-any.whl", hash = "sha256:64340de934b9ce37857ef85a82de72f5629e8a270f9119eabb12bf495eb53c22", size = 668176, upload-time = "2026-05-21T18:39:58.596Z" }, ] [[package]] @@ -3164,11 +3203,11 @@ wheels = [ [[package]] name = "idna" -version = "3.15" +version = "3.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/88/bcf9709822fe69d02c2a6a77956c98ce6ea8ca8767a9aadcedc7eb6a2390/idna-3.16.tar.gz", hash = "sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d", size = 203770, upload-time = "2026-05-22T00:16:18.781Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, + { url = "https://files.pythonhosted.org/packages/94/16/70255075a9859a0e3adb789b68ceb0e210dec03934245fd98d248226572f/idna-3.16-py3-none-any.whl", hash = "sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5", size = 74165, upload-time = "2026-05-22T00:16:16.698Z" }, ] [[package]] @@ -3618,7 +3657,7 @@ wheels = [ [[package]] name = "litellm" -version = "1.85.0" +version = "1.86.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -3634,9 +3673,9 @@ dependencies = [ { name = "tiktoken", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "tokenizers", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/d5/3c9b560db2ffa9e498655d0dfd74f408bc5b32ede858b5731c2a5fa4c752/litellm-1.85.0.tar.gz", hash = "sha256:babdd569809af913d08a08a7eb55df1ed3e6a3960ee365c6cef4ad031c9bc72a", size = 15344387, upload-time = "2026-05-17T01:59:15.97Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/a7/26b8b04e4fcff26b60200ffe7458a255552ae51014468188f5db45674eb2/litellm-1.86.0.tar.gz", hash = "sha256:eccab86e0820b60b3f9484b233fb8d818b97afb19d5b4fa08d0d045621350ba4", size = 15379195, upload-time = "2026-05-24T02:42:10.865Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/38/e6a4abb062e039d18d59538cc4e6fc370c2c10cd2bff4a2e546acb69dcb9/litellm-1.85.0-py3-none-any.whl", hash = "sha256:2bb449153610691faffd76f5b94a8c29e4b66fc5394156ebf54fd4fe92759b1a", size = 16978229, upload-time = "2026-05-17T01:59:11.902Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/9a044c061a69e801de042e962c34f5bc2e094810e28b49ce0b3bedee9327/litellm-1.86.0-py3-none-any.whl", hash = "sha256:9d8171ca1a17705b7c7a6fdce8cfc07bbf641284b46c1b6047f83a779159990c", size = 17011225, upload-time = "2026-05-24T02:42:00.629Z" }, ] [package.optional-dependencies] @@ -3655,6 +3694,7 @@ proxy = [ { name = "mcp", extra = ["ws"], marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "orjson", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "polars", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "pydantic-settings", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pyjwt", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pynacl", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pyroscope-io", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, @@ -3671,11 +3711,11 @@ proxy = [ [[package]] name = "litellm-enterprise" -version = "0.1.40" +version = "0.1.41" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/b5/c1ef8cbb4555564dc47489d65758d4132bb1a3a5c0991264c57e0069b059/litellm_enterprise-0.1.40.tar.gz", hash = "sha256:f2bc6d8ed3863f51d2aaafa99f2a5cda6ccf9fc64a9c646825ab8051a789bc62", size = 70107, upload-time = "2026-05-05T23:28:14.75Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/07/73412b99c6065ae49a5e87b5f5810b94c1743d7cd41d3a701ebf2c0a64d2/litellm_enterprise-0.1.41.tar.gz", hash = "sha256:3bbf37b6e997e28f9a39489ba532ac98f19b5176180ce091c04156ce3f048d54", size = 70437, upload-time = "2026-05-17T02:05:49.282Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/45/af3e0922805f81262bdfc6002567e004205b144fd2d011c2891da6f48265/litellm_enterprise-0.1.40-py3-none-any.whl", hash = "sha256:bf0eada2309053556aef09d9071297f3c5952e787df9c283de13a393b6b85251", size = 137250, upload-time = "2026-05-05T23:28:13.82Z" }, + { url = "https://files.pythonhosted.org/packages/65/16/284b7304dbf6eea7fe79352ca808f310b7e724648e5f3cf7c13a7a54d682/litellm_enterprise-0.1.41-py3-none-any.whl", hash = "sha256:7b31fd807dee8e1900fd15d8344e4509b6aaf05e10a211fc91c30a95e227685f", size = 137669, upload-time = "2026-05-17T02:05:48.24Z" }, ] [[package]] @@ -3969,6 +4009,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f0/1b/543ddaa2daf8593911a02a07a6a78366d4a6a0053ec86a557c19fa97b60e/microsoft_agents_hosting_core-0.3.1-py3-none-any.whl", hash = "sha256:a4b41556b15321b74f539c5a0a89f70955459b7ec57e9e4b24e61bba27f1cbbc", size = 94573, upload-time = "2025-09-09T23:19:53.855Z" }, ] +[[package]] +name = "microsoft-opentelemetry" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "azure-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "azure-core-tracing-opentelemetry", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "azure-monitor-opentelemetry-exporter", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-exporter-otlp-proto-http", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-django", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-fastapi", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-flask", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-logging", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-openai-agents-v2", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-openai-v2", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-psycopg2", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-urllib", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-urllib3", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-resource-detector-azure", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-sdk", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-util-genai", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "pyjwt", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "wrapt", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/44/4134d9ba56dffc71ab20019dd6084e12455bf327772f910a56938fdd68df/microsoft_opentelemetry-1.2.0.tar.gz", hash = "sha256:4a0f8c6a01b0ac2de3f32f76142aa912b9890bd8d093f1b8c907b8ee8b818975", size = 163091, upload-time = "2026-05-18T21:32:42.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/3c/3baa0bbb170377e318f4c22b258e1deb0d976fc3c10bf212982485618d76/microsoft_opentelemetry-1.2.0-py3-none-any.whl", hash = "sha256:17cd7d9f605e020f6c09461a3a80e36b7b39c4a9ee1568dd78e2c2c6a36c7e27", size = 181152, upload-time = "2026-05-18T21:32:43.536Z" }, +] + [[package]] name = "ml-dtypes" version = "0.5.4" @@ -4473,7 +4548,7 @@ wheels = [ [[package]] name = "openai" -version = "2.37.0" +version = "2.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -4485,9 +4560,9 @@ dependencies = [ { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/50/5901f01ef14e6c27788beb91e54fef5d6204fb5fb9e97402fc8a14de2e32/openai-2.37.0.tar.gz", hash = "sha256:f4bc562cc5f3a43d40d678105572d9d44765f6e0f50c125f63055419b72f4bd9", size = 754706, upload-time = "2026-05-15T22:30:35.428Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/12/cfa322c5f5dd8fa21aab9a7a8e979e7a11123800f86ca8d82eb68a83d213/openai-2.38.0.tar.gz", hash = "sha256:798694c6cf74145541fda94325b6f8f72d8e1fd0262cc137c8d728177a6a4ce3", size = 772764, upload-time = "2026-05-21T21:23:42.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/4c/bce61680d0699a78a405fd9a67989b175ba020590428831aab2ab1d2be7c/openai-2.37.0-py3-none-any.whl", hash = "sha256:814633888b8f3b1ffd6615697c6e4ef93632d08b7c2e28c8c5ef3556e5a10107", size = 1303238, upload-time = "2026-05-15T22:30:32.767Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/ccff9be562e24207716d04ef9dc931c76aff0c89a7265da43e2104d7fe06/openai-2.38.0-py3-none-any.whl", hash = "sha256:ec6661c57b2dcc47414a767e6e3335c7ed3d19c9696999283a3c82e95c756a3c", size = 1344910, upload-time = "2026-05-21T21:23:39.636Z" }, ] [[package]] @@ -4694,6 +4769,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/41/619f3530324a58491f2d20f216a10dd7393629b29db4610dda642a27f4ed/opentelemetry_instrumentation_flask-0.61b0-py3-none-any.whl", hash = "sha256:e8ce474d7ce543bfbbb3e93f8a6f8263348af9d7b45502f387420cf3afa71253", size = 15996, upload-time = "2026-03-04T14:19:31.304Z" }, ] +[[package]] +name = "opentelemetry-instrumentation-httpx" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-semantic-conventions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-util-http", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "wrapt", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/2a/e2becd55e33c29d1d9ef76e2579040ed1951cb33bacba259f6aff2fdd2a6/opentelemetry_instrumentation_httpx-0.61b0.tar.gz", hash = "sha256:6569ec097946c5551c2a4252f74c98666addd1bf047c1dde6b4ef426719ff8dd", size = 24104, upload-time = "2026-03-04T14:20:34.752Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/88/dde310dce56e2d85cf1a09507f5888544955309edc4b8d22971d6d3d1417/opentelemetry_instrumentation_httpx-0.61b0-py3-none-any.whl", hash = "sha256:dee05c93a6593a5dc3ae5d9d5c01df8b4e2c5d02e49275e5558534ee46343d5e", size = 17198, upload-time = "2026-03-04T14:19:33.585Z" }, +] + [[package]] name = "opentelemetry-instrumentation-logging" version = "0.61b0" @@ -4707,6 +4798,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/0e/2137db5239cc5e564495549a4d11488a7af9b48fc76520a0eea20e69ddae/opentelemetry_instrumentation_logging-0.61b0-py3-none-any.whl", hash = "sha256:6d87e5ded6a0128d775d41511f8380910a1b610671081d16efb05ac3711c0074", size = 17076, upload-time = "2026-03-04T14:19:36.765Z" }, ] +[[package]] +name = "opentelemetry-instrumentation-openai-agents-v2" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-semantic-conventions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-util-genai", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/15/b6a303454d2800d772cdebc490c1d598d06d0e541619db80195eb9ea85c6/opentelemetry_instrumentation_openai_agents_v2-0.1.0.tar.gz", hash = "sha256:1033f4b261ce07f65d197ac0e9c499302c805eae987a6cc4e7f99bb279363477", size = 22423, upload-time = "2025-10-15T19:04:59.912Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/0a/b6f47734e1d7f936cbc52ef8e673d3e08d9c3c8a13d9549c03f978758076/opentelemetry_instrumentation_openai_agents_v2-0.1.0-py3-none-any.whl", hash = "sha256:e4e3dfba32bd6eeee0624eca9be54341ab7cc4f7a3bb895354f2f9d6f7afe2f3", size = 25002, upload-time = "2025-10-15T19:04:58.562Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-openai-v2" +version = "2.3b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-semantic-conventions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/4e/21f8cd16ccb471dd217ed85eb817796a10c4f2718ae2c91e752a57180cf0/opentelemetry_instrumentation_openai_v2-2.3b0.tar.gz", hash = "sha256:5de9d70cc9536eea1fe48ea016e0c5f25735fa9a13709076a64b20657fadb6ba", size = 170838, upload-time = "2025-12-24T13:20:58.33Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/02/7ff0a9282520592772a356dd39d1559f3726610ccc3854a2f598b756c66f/opentelemetry_instrumentation_openai_v2-2.3b0-py3-none-any.whl", hash = "sha256:c6aca87be0da0289ea1d8167fea4b0f227ea5ef0e90496e2822121e47340d36a", size = 18053, upload-time = "2025-12-24T13:20:57.233Z" }, +] + [[package]] name = "opentelemetry-instrumentation-psycopg2" version = "0.61b0" @@ -4833,6 +4953,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, ] +[[package]] +name = "opentelemetry-util-genai" +version = "0.3b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-semantic-conventions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/d8/4dd2fb622d26ec45b10ef63eb87fd512f5d7467c7bd35ce390629bd6dff8/opentelemetry_util_genai-0.3b0.tar.gz", hash = "sha256:83e127789a9ad615b8ca65f05fc36955a67ce257b06142bfd46159a3b7ed73d3", size = 31800, upload-time = "2026-02-20T16:16:14.807Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/e5/fada54909e445d7b4007f8b96221d571999efeab9446f3127cc1cebe5e07/opentelemetry_util_genai-0.3b0-py3-none-any.whl", hash = "sha256:ebc2b01bcb891ddc7218452470d189d3321cd742653299ff8e7de45debcfb986", size = 28426, upload-time = "2026-02-20T16:16:12.027Z" }, +] + [[package]] name = "opentelemetry-util-http" version = "0.61b0" @@ -5305,7 +5439,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.15.0" +version = "7.15.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backoff", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -5313,9 +5447,9 @@ dependencies = [ { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/98/e4f414fd390bba6242be37f0a899325abba5ab22e3e83213cbb20253f807/posthog-7.15.0.tar.gz", hash = "sha256:22270215d7b062cd66badee3dfbe957e3f05e29b1b0e8c125a214ebc50bc77c8", size = 212310, upload-time = "2026-05-19T12:57:03.506Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/96/f6e63bdeb5a7809c25374872c6b59f03193c261eb029820e7d29c3aa8d07/posthog-7.15.4.tar.gz", hash = "sha256:9d7fe7bc0f03ed699e0608be0a6ef60267129a597651eb1c78c89c290c0a9b18", size = 218005, upload-time = "2026-05-25T06:33:09.727Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/4a/7b50a53e9a557c7e78deb36811eb6b0d05f40f20c3b633e379e206426e37/posthog-7.15.0-py3-none-any.whl", hash = "sha256:0ada8fe94c7fb9b7ad507180eed308fea3b45063ddb4088cc8eaddb9cdea15c4", size = 248461, upload-time = "2026-05-19T12:57:01.575Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b2/1108392d27c14139551cd2978bcc60c52c5fa0d52c8f639fb87b7b91d360/posthog-7.15.4-py3-none-any.whl", hash = "sha256:96f3a530a0dd709b91322212c61e161bf1e17a88d5202206870381222bc0fe2d", size = 255366, upload-time = "2026-05-25T06:33:07.427Z" }, ] [[package]] @@ -5323,8 +5457,8 @@ name = "powerfx" version = "0.0.34" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, - { name = "pythonnet", marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, + { name = "cffi", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "pythonnet", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/fb/6c4bf87e0c74ca1c563921ce89ca1c5785b7576bca932f7255cdf81082a7/powerfx-0.0.34.tar.gz", hash = "sha256:956992e7afd272657ed16d80f4cad24ec95d9e4a79fb9dfa4a068a09e136af32", size = 3237555, upload-time = "2025-12-22T15:50:59.682Z" } wheels = [ @@ -6080,7 +6214,7 @@ name = "pythonnet" version = "3.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "clr-loader", marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" }, + { name = "clr-loader", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9a/d6/1afd75edd932306ae9bd2c2d961d603dc2b52fcec51b04afea464f1f6646/pythonnet-3.0.5.tar.gz", hash = "sha256:48e43ca463941b3608b32b4e236db92d8d40db4c58a75ace902985f76dac21cf", size = 239212, upload-time = "2024-12-13T08:30:44.393Z" } wheels = [ @@ -7007,62 +7141,57 @@ wheels = [ [[package]] name = "sqlalchemy" -version = "2.0.49" +version = "2.0.50" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "(platform_machine == 'AMD64' and sys_platform == 'darwin') or (platform_machine == 'WIN32' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'darwin') or (platform_machine == 'amd64' and sys_platform == 'darwin') or (platform_machine == 'ppc64le' and sys_platform == 'darwin') or (platform_machine == 'win32' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'AMD64' and sys_platform == 'linux') or (platform_machine == 'WIN32' and sys_platform == 'linux') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'amd64' and sys_platform == 'linux') or (platform_machine == 'ppc64le' and sys_platform == 'linux') or (platform_machine == 'win32' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'WIN32' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'win32') or (platform_machine == 'amd64' and sys_platform == 'win32') or (platform_machine == 'ppc64le' and sys_platform == 'win32') or (platform_machine == 'win32' and sys_platform == 'win32') or (platform_machine == 'x86_64' and sys_platform == 'win32')" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/45/461788f35e0364a8da7bda51a1fe1b09762d0c32f12f63727998d85a873b/sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f", size = 9898221, upload-time = "2026-04-03T16:38:11.704Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/76/f908955139842c362aa877848f42f9249642d5b69e06cee9eae5111da1bd/sqlalchemy-2.0.49-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:42e8804962f9e6f4be2cbaedc0c3718f08f60a16910fa3d86da5a1e3b1bfe60f", size = 2159321, upload-time = "2026-04-03T16:50:11.8Z" }, - { url = "https://files.pythonhosted.org/packages/24/e2/17ba0b7bfbd8de67196889b6d951de269e8a46057d92baca162889beb16d/sqlalchemy-2.0.49-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc992c6ed024c8c3c592c5fc9846a03dd68a425674900c70122c77ea16c5fb0b", size = 3238937, upload-time = "2026-04-03T16:54:45.731Z" }, - { url = "https://files.pythonhosted.org/packages/90/1e/410dd499c039deacff395eec01a9da057125fcd0c97e3badc252c6a2d6a7/sqlalchemy-2.0.49-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eb188b84269f357669b62cb576b5b918de10fb7c728a005fa0ebb0b758adce1", size = 3237188, upload-time = "2026-04-03T16:56:53.217Z" }, - { url = "https://files.pythonhosted.org/packages/ab/06/e797a8b98a3993ac4bc785309b9b6d005457fc70238ee6cefa7c8867a92e/sqlalchemy-2.0.49-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:62557958002b69699bdb7f5137c6714ca1133f045f97b3903964f47db97ea339", size = 3190061, upload-time = "2026-04-03T16:54:47.489Z" }, - { url = "https://files.pythonhosted.org/packages/44/d3/5a9f7ef580af1031184b38235da6ac58c3b571df01c9ec061c44b2b0c5a6/sqlalchemy-2.0.49-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da9b91bca419dc9b9267ffadde24eae9b1a6bffcd09d0a207e5e3af99a03ce0d", size = 3211477, upload-time = "2026-04-03T16:56:55.056Z" }, - { url = "https://files.pythonhosted.org/packages/69/ec/7be8c8cb35f038e963a203e4fe5a028989167cc7299927b7cf297c271e37/sqlalchemy-2.0.49-cp310-cp310-win32.whl", hash = "sha256:5e61abbec255be7b122aa461021daa7c3f310f3e743411a67079f9b3cc91ece3", size = 2119965, upload-time = "2026-04-03T17:00:50.009Z" }, - { url = "https://files.pythonhosted.org/packages/b5/31/0defb93e3a10b0cf7d1271aedd87251a08c3a597ee4f353281769b547b5a/sqlalchemy-2.0.49-cp310-cp310-win_amd64.whl", hash = "sha256:0c98c59075b890df8abfcc6ad632879540f5791c68baebacb4f833713b510e75", size = 2142935, upload-time = "2026-04-03T17:00:51.675Z" }, - { url = "https://files.pythonhosted.org/packages/60/b5/e3617cc67420f8f403efebd7b043128f94775e57e5b84e7255203390ceae/sqlalchemy-2.0.49-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5070135e1b7409c4161133aa525419b0062088ed77c92b1da95366ec5cbebbe", size = 2159126, upload-time = "2026-04-03T16:50:13.242Z" }, - { url = "https://files.pythonhosted.org/packages/20/9b/91ca80403b17cd389622a642699e5f6564096b698e7cdcbcbb6409898bc4/sqlalchemy-2.0.49-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ac7a3e245fd0310fd31495eb61af772e637bdf7d88ee81e7f10a3f271bff014", size = 3315509, upload-time = "2026-04-03T16:54:49.332Z" }, - { url = "https://files.pythonhosted.org/packages/b1/61/0722511d98c54de95acb327824cb759e8653789af2b1944ab1cc69d32565/sqlalchemy-2.0.49-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d4e5a0ceba319942fa6b585cf82539288a61e314ef006c1209f734551ab9536", size = 3315014, upload-time = "2026-04-03T16:56:56.376Z" }, - { url = "https://files.pythonhosted.org/packages/46/55/d514a653ffeb4cebf4b54c47bec32ee28ad89d39fafba16eeed1d81dccd5/sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ddcb27fb39171de36e207600116ac9dfd4ae46f86c82a9bf3934043e80ebb88", size = 3267388, upload-time = "2026-04-03T16:54:51.272Z" }, - { url = "https://files.pythonhosted.org/packages/2f/16/0dcc56cb6d3335c1671a2258f5d2cb8267c9a2260e27fde53cbfb1b3540a/sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:32fe6a41ad97302db2931f05bb91abbcc65b5ce4c675cd44b972428dd2947700", size = 3289602, upload-time = "2026-04-03T16:56:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/51/6c/f8ab6fb04470a133cd80608db40aa292e6bae5f162c3a3d4ab19544a67af/sqlalchemy-2.0.49-cp311-cp311-win32.whl", hash = "sha256:46d51518d53edfbe0563662c96954dc8fcace9832332b914375f45a99b77cc9a", size = 2119044, upload-time = "2026-04-03T17:00:53.455Z" }, - { url = "https://files.pythonhosted.org/packages/c4/59/55a6d627d04b6ebb290693681d7683c7da001eddf90b60cfcc41ee907978/sqlalchemy-2.0.49-cp311-cp311-win_amd64.whl", hash = "sha256:951d4a210744813be63019f3df343bf233b7432aadf0db54c75802247330d3af", size = 2143642, upload-time = "2026-04-03T17:00:54.769Z" }, - { url = "https://files.pythonhosted.org/packages/49/b3/2de412451330756aaaa72d27131db6dde23995efe62c941184e15242a5fa/sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbccb45260e4ff1b7db0be80a9025bb1e6698bdb808b83fff0000f7a90b2c0b", size = 2157681, upload-time = "2026-04-03T16:53:07.132Z" }, - { url = "https://files.pythonhosted.org/packages/50/84/b2a56e2105bd11ebf9f0b93abddd748e1a78d592819099359aa98134a8bf/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb37f15714ec2652d574f021d479e78cd4eb9d04396dca36568fdfffb3487982", size = 3338976, upload-time = "2026-04-03T17:07:40Z" }, - { url = "https://files.pythonhosted.org/packages/2c/fa/65fcae2ed62f84ab72cf89536c7c3217a156e71a2c111b1305ab6f0690e2/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb9ec6436a820a4c006aad1ac351f12de2f2dbdaad171692ee457a02429b672", size = 3351937, upload-time = "2026-04-03T17:12:23.374Z" }, - { url = "https://files.pythonhosted.org/packages/f8/2f/6fd118563572a7fe475925742eb6b3443b2250e346a0cc27d8d408e73773/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d6efc136f44a7e8bc8088507eaabbb8c2b55b3dbb63fe102c690da0ddebe55e", size = 3281646, upload-time = "2026-04-03T17:07:41.949Z" }, - { url = "https://files.pythonhosted.org/packages/c5/d7/410f4a007c65275b9cf82354adb4bb8ba587b176d0a6ee99caa16fe638f8/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e06e617e3d4fd9e51d385dfe45b077a41e9d1b033a7702551e3278ac597dc750", size = 3316695, upload-time = "2026-04-03T17:12:25.642Z" }, - { url = "https://files.pythonhosted.org/packages/d9/95/81f594aa60ded13273a844539041ccf1e66c5a7bed0a8e27810a3b52d522/sqlalchemy-2.0.49-cp312-cp312-win32.whl", hash = "sha256:83101a6930332b87653886c01d1ee7e294b1fe46a07dd9a2d2b4f91bcc88eec0", size = 2117483, upload-time = "2026-04-03T17:05:40.896Z" }, - { url = "https://files.pythonhosted.org/packages/47/9e/fd90114059175cac64e4fafa9bf3ac20584384d66de40793ae2e2f26f3bb/sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl", hash = "sha256:618a308215b6cececb6240b9abde545e3acdabac7ae3e1d4e666896bf5ba44b4", size = 2144494, upload-time = "2026-04-03T17:05:42.282Z" }, - { url = "https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120", size = 2154547, upload-time = "2026-04-03T16:53:08.64Z" }, - { url = "https://files.pythonhosted.org/packages/a2/bc/3494270da80811d08bcfa247404292428c4fe16294932bce5593f215cad9/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e20e511dc15265fb433571391ba313e10dd8ea7e509d51686a51313b4ac01a2", size = 3280782, upload-time = "2026-04-03T17:07:43.508Z" }, - { url = "https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3", size = 3297156, upload-time = "2026-04-03T17:12:27.697Z" }, - { url = "https://files.pythonhosted.org/packages/88/50/a6af0ff9dc954b43a65ca9b5367334e45d99684c90a3d3413fc19a02d43c/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:22d8798819f86720bc646ab015baff5ea4c971d68121cb36e2ebc2ee43ead2b7", size = 3228832, upload-time = "2026-04-03T17:07:45.38Z" }, - { url = "https://files.pythonhosted.org/packages/bc/d1/5f6bdad8de0bf546fc74370939621396515e0cdb9067402d6ba1b8afbe9a/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b1c058c171b739e7c330760044803099c7fff11511e3ab3573e5327116a9c33", size = 3267000, upload-time = "2026-04-03T17:12:29.657Z" }, - { url = "https://files.pythonhosted.org/packages/f7/30/ad62227b4a9819a5e1c6abff77c0f614fa7c9326e5a3bdbee90f7139382b/sqlalchemy-2.0.49-cp313-cp313-win32.whl", hash = "sha256:a143af2ea6672f2af3f44ed8f9cd020e9cc34c56f0e8db12019d5d9ecf41cb3b", size = 2115641, upload-time = "2026-04-03T17:05:43.989Z" }, - { url = "https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl", hash = "sha256:12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148", size = 2141498, upload-time = "2026-04-03T17:05:45.7Z" }, - { url = "https://files.pythonhosted.org/packages/28/4b/52a0cb2687a9cd1648252bb257be5a1ba2c2ded20ba695c65756a55a15a4/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24bd94bb301ec672d8f0623eba9226cc90d775d25a0c92b5f8e4965d7f3a1518", size = 3560807, upload-time = "2026-04-03T16:58:31.666Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d8/fda95459204877eed0458550d6c7c64c98cc50c2d8d618026737de9ed41a/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51d3db74ba489266ef55c7a4534eb0b8db9a326553df481c11e5d7660c8364d", size = 3527481, upload-time = "2026-04-03T17:06:00.155Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0a/2aac8b78ac6487240cf7afef8f203ca783e8796002dc0cf65c4ee99ff8bb/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:55250fe61d6ebfd6934a272ee16ef1244e0f16b7af6cd18ab5b1fc9f08631db0", size = 3468565, upload-time = "2026-04-03T16:58:33.414Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3d/ce71cfa82c50a373fd2148b3c870be05027155ce791dc9a5dcf439790b8b/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:46796877b47034b559a593d7e4b549aba151dae73f9e78212a3478161c12ab08", size = 3477769, upload-time = "2026-04-03T17:06:02.787Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e8/0a9f5c1f7c6f9ca480319bf57c2d7423f08d31445974167a27d14483c948/sqlalchemy-2.0.49-cp313-cp313t-win32.whl", hash = "sha256:9c4969a86e41454f2858256c39bdfb966a20961e9b58bf8749b65abf447e9a8d", size = 2143319, upload-time = "2026-04-03T17:02:04.328Z" }, - { url = "https://files.pythonhosted.org/packages/0e/51/fb5240729fbec73006e137c4f7a7918ffd583ab08921e6ff81a999d6517a/sqlalchemy-2.0.49-cp313-cp313t-win_amd64.whl", hash = "sha256:b9870d15ef00e4d0559ae10ee5bc71b654d1f20076dbe8bc7ed19b4c0625ceba", size = 2175104, upload-time = "2026-04-03T17:02:05.989Z" }, - { url = "https://files.pythonhosted.org/packages/55/33/bf28f618c0a9597d14e0b9ee7d1e0622faff738d44fe986ee287cdf1b8d0/sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:233088b4b99ebcbc5258c755a097aa52fbf90727a03a5a80781c4b9c54347a2e", size = 2156356, upload-time = "2026-04-03T16:53:09.914Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a7/5f476227576cb8644650eff68cc35fa837d3802b997465c96b8340ced1e2/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57ca426a48eb2c682dae8204cd89ea8ab7031e2675120a47924fabc7caacbc2a", size = 3276486, upload-time = "2026-04-03T17:07:46.9Z" }, - { url = "https://files.pythonhosted.org/packages/2e/84/efc7c0bf3a1c5eef81d397f6fddac855becdbb11cb38ff957888603014a7/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685e93e9c8f399b0c96a624799820176312f5ceef958c0f88215af4013d29066", size = 3281479, upload-time = "2026-04-03T17:12:32.226Z" }, - { url = "https://files.pythonhosted.org/packages/91/68/bb406fa4257099c67bd75f3f2261b129c63204b9155de0d450b37f004698/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e0400fa22f79acc334d9a6b185dc00a44a8e6578aa7e12d0ddcd8434152b187", size = 3226269, upload-time = "2026-04-03T17:07:48.678Z" }, - { url = "https://files.pythonhosted.org/packages/67/84/acb56c00cca9f251f437cb49e718e14f7687505749ea9255d7bd8158a6df/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a05977bffe9bffd2229f477fa75eabe3192b1b05f408961d1bebff8d1cd4d401", size = 3248260, upload-time = "2026-04-03T17:12:34.381Z" }, - { url = "https://files.pythonhosted.org/packages/56/19/6a20ea25606d1efd7bd1862149bb2a22d1451c3f851d23d887969201633f/sqlalchemy-2.0.49-cp314-cp314-win32.whl", hash = "sha256:0f2fa354ba106eafff2c14b0cc51f22801d1e8b2e4149342023bd6f0955de5f5", size = 2118463, upload-time = "2026-04-03T17:05:47.093Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4f/8297e4ed88e80baa1f5aa3c484a0ee29ef3c69c7582f206c916973b75057/sqlalchemy-2.0.49-cp314-cp314-win_amd64.whl", hash = "sha256:77641d299179c37b89cf2343ca9972c88bb6eef0d5fc504a2f86afd15cd5adf5", size = 2144204, upload-time = "2026-04-03T17:05:48.694Z" }, - { url = "https://files.pythonhosted.org/packages/1f/33/95e7216df810c706e0cd3655a778604bbd319ed4f43333127d465a46862d/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dc3368794d522f43914e03312202523cc89692f5389c32bea0233924f8d977", size = 3565474, upload-time = "2026-04-03T16:58:35.128Z" }, - { url = "https://files.pythonhosted.org/packages/0c/a4/ed7b18d8ccf7f954a83af6bb73866f5bc6f5636f44c7731fbb741f72cc4f/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c821c47ecfe05cc32140dcf8dc6fd5d21971c86dbd56eabfe5ba07a64910c01", size = 3530567, upload-time = "2026-04-03T17:06:04.587Z" }, - { url = "https://files.pythonhosted.org/packages/73/a3/20faa869c7e21a827c4a2a42b41353a54b0f9f5e96df5087629c306df71e/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9c04bff9a5335eb95c6ecf1c117576a0aa560def274876fd156cfe5510fccc61", size = 3474282, upload-time = "2026-04-03T16:58:37.131Z" }, - { url = "https://files.pythonhosted.org/packages/b7/50/276b9a007aa0764304ad467eceb70b04822dc32092492ee5f322d559a4dc/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f605a456948c35260e7b2a39f8952a26f077fd25653c37740ed186b90aaa68a", size = 3480406, upload-time = "2026-04-03T17:06:07.176Z" }, - { url = "https://files.pythonhosted.org/packages/e5/c3/c80fcdb41905a2df650c2a3e0337198b6848876e63d66fe9188ef9003d24/sqlalchemy-2.0.49-cp314-cp314t-win32.whl", hash = "sha256:6270d717b11c5476b0cbb21eedc8d4dbb7d1a956fd6c15a23e96f197a6193158", size = 2149151, upload-time = "2026-04-03T17:02:07.281Z" }, - { url = "https://files.pythonhosted.org/packages/05/52/9f1a62feab6ed368aff068524ff414f26a6daebc7361861035ae00b05530/sqlalchemy-2.0.49-cp314-cp314t-win_amd64.whl", hash = "sha256:275424295f4256fd301744b8f335cff367825d270f155d522b30c7bf49903ee7", size = 2184178, upload-time = "2026-04-03T17:02:08.623Z" }, - { url = "https://files.pythonhosted.org/packages/e5/30/8519fdde58a7bdf155b714359791ad1dc018b47d60269d5d160d311fdc36/sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0", size = 1942158, upload-time = "2026-04-03T16:53:44.135Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/57/da/6fbf010c8ebb347679d0d100b22fe9ba5e13fd04046c5df7280d2f0bf706/sqlalchemy-2.0.50.tar.gz", hash = "sha256:af5607d11ef90fd6a5c0549fe0045dce1663d427426bcfb506dcb5346a85a3b9", size = 9907424, upload-time = "2026-05-24T19:20:04.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/a9/812a775bd8c1af0966d660238d005baf25e9bced1f038c8e71f00aa637a7/sqlalchemy-2.0.50-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7af6eeb84985bf840ba779018ff9424d61ff69b52e66b8789d3c8da7bf5341b2", size = 2161617, upload-time = "2026-05-24T20:00:00.761Z" }, + { url = "https://files.pythonhosted.org/packages/d5/74/5a6bc5496e9be8f740fbf80f9e6bd4ab965c8a80870eb07ab015e360957a/sqlalchemy-2.0.50-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fe7822866f3a9fc5f3db21a290ce8961a53050115f05edf9402b6a5feb92a9f", size = 3244104, upload-time = "2026-05-24T20:07:38.158Z" }, + { url = "https://files.pythonhosted.org/packages/81/55/b260d8df2adc9bb0bf294f67b5f802ff0d84d99442b536b9efd0ea72d447/sqlalchemy-2.0.50-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e1b0f6a4dcd9b4839e2320afb5df37a6981cbc20ff9c423ae11c5537bdbd21", size = 3243039, upload-time = "2026-05-24T20:14:23.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/6d/58714005cbf370f16c3f30d30324a43be10069efcfe764f7236a2e851947/sqlalchemy-2.0.50-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e195687f1af431c9515416288373b323b6eb599f774409814e89e9d603a56e39", size = 3195017, upload-time = "2026-05-24T20:07:40.086Z" }, + { url = "https://files.pythonhosted.org/packages/30/e8/67527fee039bd3e1a6ce3f03d2b62fd87ab9099c17052810d79496727b66/sqlalchemy-2.0.50-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ea1a8a2db4b2217d456c8d7a873bfc605f06fe3584d315264ea18c2a17585d0b", size = 3215308, upload-time = "2026-05-24T20:14:26.034Z" }, + { url = "https://files.pythonhosted.org/packages/94/b2/dd3155a6a6706cb89adecf5ee6e0512f7b0ee5cf3e6f4cde67d3c20ebfda/sqlalchemy-2.0.50-cp310-cp310-win32.whl", hash = "sha256:68b154b08088b4ec32bb4d2958bfbb50e57549f91a4cd3e7f928e3553ed69031", size = 2121637, upload-time = "2026-05-24T20:08:06.401Z" }, + { url = "https://files.pythonhosted.org/packages/93/a1/a09c463ee3e7764b5ce5bd19a7f0b6eefbde62e637439ab58498cdbd6b47/sqlalchemy-2.0.50-cp310-cp310-win_amd64.whl", hash = "sha256:66e374271ecb7101273f57af1a62446a953d327eec4f8089147de57c591bbacc", size = 2144673, upload-time = "2026-05-24T20:08:07.936Z" }, + { url = "https://files.pythonhosted.org/packages/b6/5d/3172686af1770e4de2805f919a51441085f589ddadf3dd76ec582f84f497/sqlalchemy-2.0.50-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa6e403663a9c43c8fef7ce4bdb4cf48bcd8d352e91deda2a99f963270bd508", size = 2161366, upload-time = "2026-05-24T20:00:02.061Z" }, + { url = "https://files.pythonhosted.org/packages/0f/90/e98dedea3c3e663a17afcd003a34ba45efdac2cea3b6f2e4585e2b1e2537/sqlalchemy-2.0.50-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51b637a84f9fa35ae1f9017e786cb142974a25305085e1b378b3647a67f65ad3", size = 3318926, upload-time = "2026-05-24T20:07:42.369Z" }, + { url = "https://files.pythonhosted.org/packages/3b/4f/501308c2babb62c11753ecb4ee88ba9eef019419a4d6cbf7cb13e2bad353/sqlalchemy-2.0.50-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2dab927761d9108550f0cf8e66ff21af56f907a0ce0a689793db615e2b55f62c", size = 3319199, upload-time = "2026-05-24T20:14:28.551Z" }, + { url = "https://files.pythonhosted.org/packages/ac/39/d88996c5e03ed6248c3a788d20f0b8d8b376b9f8a495e4bab9df7c72d2f8/sqlalchemy-2.0.50-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:545eae198d37bcf837a10ede3684e2af32458d6f35c597c35c2de7502dc38fc4", size = 3270301, upload-time = "2026-05-24T20:07:44.917Z" }, + { url = "https://files.pythonhosted.org/packages/42/1b/1ae0e65161b51cc43e5ca75430ef79d80e23b5042d645586c2c342c3b92e/sqlalchemy-2.0.50-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fec460e18cdbb4c7773531122ce9a27e96c6ca17af3933941d94da475ad2c86", size = 3293465, upload-time = "2026-05-24T20:14:30.501Z" }, + { url = "https://files.pythonhosted.org/packages/83/29/17c0003f2c0dfa6d1b97672475707e3ec5980db09defd7fa20beb6833bbd/sqlalchemy-2.0.50-cp311-cp311-win32.whl", hash = "sha256:e6e814658818fd165e749e3d8490ef16cc7f379a118c37ada8b0589ffbaaac22", size = 2120694, upload-time = "2026-05-24T20:08:09.237Z" }, + { url = "https://files.pythonhosted.org/packages/c9/18/280d00654cc19d1fccf236fa5070f6dd04b84dde6f1b2e637bde0ff340a7/sqlalchemy-2.0.50-cp311-cp311-win_amd64.whl", hash = "sha256:1c5f858fe79c9f5d8fda065c06186356acb7f8df3cd52dbd5ee3f200e4b144f5", size = 2145315, upload-time = "2026-05-24T20:08:10.952Z" }, + { url = "https://files.pythonhosted.org/packages/be/b0/a9d19b43f38f878b1278bca5b00b909f7540d41494396dd2561f9ad0956d/sqlalchemy-2.0.50-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae23d8b9d344d30d0a92f06d45825024a5790f1c1dd4cf452636a50d3e58cb", size = 2159807, upload-time = "2026-05-24T19:27:53.086Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2c/191dd58a248fd2cfd4780fa82c375c505e4ad98c8b522fa69ec492130d77/sqlalchemy-2.0.50-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47b71b933e7b4ebad407c8fdfd70d2c4f08b78b3238bb30eebdd6eb32ca51b89", size = 3343358, upload-time = "2026-05-24T20:09:29.279Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2b/514fce8a7df81cf5bad7ff7865de7ac0c5776a38cc043475c4703eb7fe8b/sqlalchemy-2.0.50-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:110fdac56ace278949f00de805edacbd6141e382d992f9ba28238b3a0827a600", size = 3357994, upload-time = "2026-05-24T20:17:13.495Z" }, + { url = "https://files.pythonhosted.org/packages/35/a6/a0e283f5494f92b0d77e319ff77e437b1ffe4a051ba67c81d53234825475/sqlalchemy-2.0.50-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5e4ac70e9e757f6b3e87c0491ff034442ecd8dfd36d041a50564c322dafc0e", size = 3289399, upload-time = "2026-05-24T20:09:32.239Z" }, + { url = "https://files.pythonhosted.org/packages/b7/96/1b07325ba71752d6a028b77d07bed1483ad545f794e8b1dc89b3ba3b3c68/sqlalchemy-2.0.50-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:724f3dcbe53dd0151e3cb5e7ec4ba4c620bede579caacd16275dc35ce06e8615", size = 3321216, upload-time = "2026-05-24T20:17:15.581Z" }, + { url = "https://files.pythonhosted.org/packages/ed/8e/bad6ed253e8a99edfc99af02f7173ec48a1d3ed1b9b35a1b8bc1700900cc/sqlalchemy-2.0.50-cp312-cp312-win32.whl", hash = "sha256:1208050441471d003b7c8cb4054fb084f185cf35ac3f0ea270803865bca9939a", size = 2119194, upload-time = "2026-05-24T19:50:04.943Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2d/314a6690dda4b9cfc571eab1a63cf6fe6e1470aa3759ccda6aa016ee0f5a/sqlalchemy-2.0.50-cp312-cp312-win_amd64.whl", hash = "sha256:9d1af51558029a156a70986b7df88f042b3d158d7c8d8fb5072912d4b32d89c7", size = 2146186, upload-time = "2026-05-24T19:50:06.74Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c4/c42356b527296e9862f67990efce31ef78b4cf69cd3f80873a528a060320/sqlalchemy-2.0.50-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:06a9210bdc5f4298cff0781087e2ff45683922252dacc452846373a58761f093", size = 2156697, upload-time = "2026-05-24T19:27:54.764Z" }, + { url = "https://files.pythonhosted.org/packages/60/a1/b1a70e3c4365ac7fe9e347f3710f19b562c866fb96d45e3c891588789a7b/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b53784972ade4f8174b9aa661f31a06f8a936d2cfdd602913ff3c6dd40ae873", size = 3284260, upload-time = "2026-05-24T20:09:34.195Z" }, + { url = "https://files.pythonhosted.org/packages/3f/4a/f3ac3caa19f263d57b0a47f8c91bbf56583dc2d3fc63acfbf644abb24fe0/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31648fa14460537e768a7303b078e4344d208e0d23e06867c1f376a227ed82db", size = 3302280, upload-time = "2026-05-24T20:17:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/66/55/ccada3e3d62254587819749a0bc69f41173eb48a6e385d10e66d32a9c88e/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:03f4323c980ad0e918cc9e5369b015f759f4e534db5bbaf4dc36832c10d05064", size = 3231580, upload-time = "2026-05-24T20:09:36.406Z" }, + { url = "https://files.pythonhosted.org/packages/05/f6/6809349130a2de0e109e7f00fd7d431da9565b9b2868b32ee684754f672b/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b9dcc43afef8ac157cd92fce96985d6b8b0cfbd3df4d666f66b4d55a75d202f", size = 3269375, upload-time = "2026-05-24T20:17:20.34Z" }, + { url = "https://files.pythonhosted.org/packages/48/84/278a811ef4e07be9c89dc5cdd7be833268509a66a68c4897cf585e67428f/sqlalchemy-2.0.50-cp313-cp313-win32.whl", hash = "sha256:60922d6599065ddca2c6f376b9aa2f41a6b85a271725e0909490bbc50b1998a5", size = 2117229, upload-time = "2026-05-24T19:50:08.215Z" }, + { url = "https://files.pythonhosted.org/packages/f6/1c/067cc6187ed32d2ec222fe6d2643acc1659a6d0659f8a7cbc5ad3ae83280/sqlalchemy-2.0.50-cp313-cp313-win_amd64.whl", hash = "sha256:287086e67275a212c4582d166a6fb03a65ccc5551d80866270ce0dd9f34eccd3", size = 2143126, upload-time = "2026-05-24T19:50:09.691Z" }, + { url = "https://files.pythonhosted.org/packages/df/32/10ac51b4be7cdecd7e93d069251c86dfbf70b7adbd7c67b48ccea6c49e1c/sqlalchemy-2.0.50-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c966932507a4d7d0a37314927dbfcd89720e3f37d2a1e3352e7ae7939fa8e8a0", size = 2158519, upload-time = "2026-05-24T19:27:56.472Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/e703d2f7681d7d66c4c891af3f07c7ccf4c76ad7f18351de035b5eda007a/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:faffef4bcc20a1892e65e155293d99d60855bbbc79250ab712819cfd56a8e6bb", size = 3282063, upload-time = "2026-05-24T20:09:38.57Z" }, + { url = "https://files.pythonhosted.org/packages/31/26/ef168b184a25701f9995e8fb7e503fafd7a99c1c77cda1bc1a26ea2ed486/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c206aec519a2e7bd08abbfb33436e325fd22c632d9c21a9047e376ce241646e", size = 3287069, upload-time = "2026-05-24T20:17:21.942Z" }, + { url = "https://files.pythonhosted.org/packages/c2/15/765acc2bc693bccc43ca4a95d5b69750da8aaf6db1b5c616536e087f8920/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bef4ac756363227ef6402a75fee025a4bc690f92328e825868939b3b3a446a6d", size = 3230453, upload-time = "2026-05-24T20:09:40.398Z" }, + { url = "https://files.pythonhosted.org/packages/63/61/08e03c3adbf5db0087a0b6816746fec8f3032fb2f7fc899a9bb9b2a48ce4/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96fbee6b19c19cd1556c8bf9419447cf2ec149ffcab7ab64348c23e54ef8547f", size = 3252413, upload-time = "2026-05-24T20:17:24.067Z" }, + { url = "https://files.pythonhosted.org/packages/03/0c/370a1f2db38436c615e10134c8a37de3688e74084792380695f3f5083860/sqlalchemy-2.0.50-cp314-cp314-win32.whl", hash = "sha256:8f00e3eb43ba30eb1b238ee03a8a62309486d1321eda3328bb611e0340033ad8", size = 2120063, upload-time = "2026-05-24T19:50:11.08Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a0/fe92bb9817863bc13ba093bda931979a26cc2ca69f8e8f26d07add3d7c6f/sqlalchemy-2.0.50-cp314-cp314-win_amd64.whl", hash = "sha256:15708c613cd5005b7dffe1f66ee6a63ee8f5e46799f71c70ebad74178c676a39", size = 2145830, upload-time = "2026-05-24T19:50:12.452Z" }, + { url = "https://files.pythonhosted.org/packages/cc/ff/e5640a98a0b2f491eb8fde10fb6c773621a2e44340de231fafcc9370f4a9/sqlalchemy-2.0.50-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3699dac4be410e97049a1658e9480da9cde956594aa0f3aebc60b88f21c5ba70", size = 2178435, upload-time = "2026-05-24T19:42:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/b7/85/337116e186f1236375b5fb70c21cfac98e8e8ab0d3a47be838dc47a59e08/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96233858e3df43932ac11589e22520da6e8aeb624b03fedfeebb0e8ea213086", size = 3566059, upload-time = "2026-05-24T20:01:20.848Z" }, + { url = "https://files.pythonhosted.org/packages/96/34/bb0e190e161c3c2c24314a65add57218be14a4a9486886b7f5047c1ff7c8/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4e70c46fad30c3bcc6a4708bc0130a3173e11a5b25f0ea4a9d8911b450f1f52", size = 3535366, upload-time = "2026-05-24T20:03:56.768Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/a7f759f97e4fd499c5d4e4488c760d5a7fbecf3028b465a04274fcd52384/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1918a3cf564d16d95bca7301005f41ab2ad50b07cd3b9da50d3ed986db148d6a", size = 3474879, upload-time = "2026-05-24T20:01:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d9/2907ea38eb60687d297bf9c39e5ee58053c87b57fe8a9cae97090cecbf10/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b00098cdbdbd38c7be3d568b0c9c3122b8c0ec62b911b57cd5e6e0254d60a76d", size = 3486117, upload-time = "2026-05-24T20:03:59.052Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e3/5aa06f167559f8c0bdae487e297d23ba548150ab016a3418265d617a4985/sqlalchemy-2.0.50-cp314-cp314t-win32.whl", hash = "sha256:1fbd55a969d7ac44a98e3dec75016074f809fa08f871585ace58dde110d1bf3e", size = 2150823, upload-time = "2026-05-24T20:08:58.644Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/112fb8f977582d7489d036e409e3723948bcf5320b3ac465f3c481bbe8f9/sqlalchemy-2.0.50-cp314-cp314t-win_amd64.whl", hash = "sha256:c5c3cdb753a9004183e1ccb634b41611654c989e61bc68617ce878e46d6f1e51", size = 2185794, upload-time = "2026-05-24T20:09:00.319Z" }, + { url = "https://files.pythonhosted.org/packages/d0/10/f7220e9b784d295d241c86ed99aeb537f92afcd469a64861f2717e9bb077/sqlalchemy-2.0.50-py3-none-any.whl", hash = "sha256:92064363517a3ff8212b5a93b8c62876579d8dfd1ca5b561335f30152d884fa9", size = 1943861, upload-time = "2026-05-24T19:59:01.119Z" }, ] [[package]] @@ -7482,16 +7611,16 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.47.0" +version = "0.48.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "h11", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform == 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f6/b1/8e7077a8641086aea449e1b5752a570f1b5906c64e0a33cd6d93b63a066b/uvicorn-0.47.0.tar.gz", hash = "sha256:7c9a0ea1a9414106bbab7324609c162d8fa0cdcdcb703060987269d77c7bb533", size = 90582, upload-time = "2026-05-14T18:16:54.455Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/bf/f6544ba992ddb9a6077343a576f9844f7f8f06ab819aefd00206e9255f18/uvicorn-0.48.0.tar.gz", hash = "sha256:a5504207195d08c2511bf9125ede5ac4a4b71725d519e758d01dcf0bc2d31c37", size = 91074, upload-time = "2026-05-24T12:08:41.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/41/ac2dfdbc1f60c7af4f994c7a335cfa7040c01642b605d65f611cecc2a1e4/uvicorn-0.47.0-py3-none-any.whl", hash = "sha256:2c5715bc12d1892d84752049f400cd1c3cb018514967fdfeb97640443a6a9432", size = 71301, upload-time = "2026-05-14T18:16:51.762Z" }, + { url = "https://files.pythonhosted.org/packages/01/be/72532be3da7acc5fdfbccdb95215cd04f995a0886532a5b423f929cda4cc/uvicorn-0.48.0-py3-none-any.whl", hash = "sha256:48097851328b87ec36117d3d575234519eb58c2b22d79666e9bbc6c49a761dad", size = 71410, upload-time = "2026-05-24T12:08:40.258Z" }, ] [package.optional-dependencies] @@ -7537,6 +7666,48 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, ] +[[package]] +name = "valkey-glide" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "sniffio", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/83/82429b227936b50be8f38a2a8201738f9d15824352576d4b51422a32bf7f/valkey_glide-2.4.0.tar.gz", hash = "sha256:d0473026f9effa65465fa9888251600d4ec1e759d3a4dea0360eb1709a673ba7", size = 890870, upload-time = "2026-05-20T16:10:55.755Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/1e/6bbf16b86c16f81810b154b3248707465c4c3347556e3c7477d1ec4487d0/valkey_glide-2.4.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:dc41f01cca074b25816f0eba807c090aee62defb8a0da6fafb1522a5789cee9d", size = 7475407, upload-time = "2026-05-20T21:57:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/ea/4c/c31a82810dd5c312d06b4192d544b36308238bae08dc3c97c623cf698d44/valkey_glide-2.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:85eea5be57014c6c8f6bba420b5f05fd005794c015dd7e3b772038732ff2bf7d", size = 6950286, upload-time = "2026-05-20T21:57:30.124Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/58823ab052c704e7022ce8b050d357daea75aaa17d541b5590d9d74df875/valkey_glide-2.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:546138d0f924575afadb0cdab917cfa7bd2c3d7ecda4ec3b33a3e7a361eafa62", size = 7271359, upload-time = "2026-05-20T21:57:31.689Z" }, + { url = "https://files.pythonhosted.org/packages/bd/59/8f7062fd7632006612697c034b113d4602ea8825e7e7acca5af28352a08a/valkey_glide-2.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab0d44a083e433dc91b23289a9548d51d0b3201bb783e87ac70d319e838e07b4", size = 7698040, upload-time = "2026-05-20T21:57:33.686Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f3/c95f4eaed85187e5f6916f5f2ce4b6787e3dd46987cc48ab73a8131a1469/valkey_glide-2.4.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:f021cf7683f113e58142866b0afa3bdc7832c1919e1c1eb94c1a076fbdaae656", size = 7475842, upload-time = "2026-05-20T21:57:35.404Z" }, + { url = "https://files.pythonhosted.org/packages/83/b0/4f421941bbd7bd199078c4da5c796a9b25a4ca97f25bd4f7ee28e2ae59d5/valkey_glide-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:06645f137386bfbc72ec1c1691f29711fcb620cb9a0d1eaeca12585974bd10b9", size = 6950846, upload-time = "2026-05-20T21:57:37.081Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e5/af12bc364293f78c1aea2994e8b1dae426cc9454014c6bb39f00d748f62a/valkey_glide-2.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a73d4a41eba2dc7c01194d76909e34fc68dd1fb2149cc4f4e281bac75383085c", size = 7271553, upload-time = "2026-05-20T21:57:38.986Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/1eae338fe98a9a99675fd64cbd38d6a4143d4dc763f471d2e32359ec7473/valkey_glide-2.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af50749a61897b2c2da805e4bf56e3ecf44c13393d04883834d12a8f2661bd46", size = 7697931, upload-time = "2026-05-20T21:57:40.753Z" }, + { url = "https://files.pythonhosted.org/packages/0e/50/53505aa4101cd95c7aa963e959efe74bfea5fdfdcc254954803b9428ea5c/valkey_glide-2.4.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:ed3afadd6329f70642c855365de6f359010db05e07917da3bbf7166578895669", size = 7474129, upload-time = "2026-05-20T21:57:42.857Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d9/6046a2fd28a91562c2439480886083523d413b6e93c9024185969170ce24/valkey_glide-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:63426bef320c2b3849add7c4eebc2205f2aead29e5ad32b19d19ae73b903df79", size = 6947614, upload-time = "2026-05-20T21:57:44.787Z" }, + { url = "https://files.pythonhosted.org/packages/c9/19/d8d8db130b4f865b313555a5dbde18606a03618dadb7efed881be27613fb/valkey_glide-2.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a769b85ed07eca851c5cdda769808f3e4133348ca3d76a5e1db3f83fc5790699", size = 7275078, upload-time = "2026-05-20T21:57:46.508Z" }, + { url = "https://files.pythonhosted.org/packages/57/31/521f604a780888c5050c2da6ca98bfd90f161a7aededa48b4b13ef1a9db5/valkey_glide-2.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7117519770868c5f1db31cc3f568b1441f4f1e6ee07f8c4f4b7ad6361b11ea", size = 7696980, upload-time = "2026-05-20T21:57:48.255Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d1/33332fbc4a4f97d606434bc8b3b37507c780876973a220e21c0cfd895f8a/valkey_glide-2.4.0-cp313-cp313-macosx_10_7_x86_64.whl", hash = "sha256:c4b1f8ff977ff5ff0879584d7e676715f15595b10f8f6d9b9b465cc3c5251c4d", size = 7474447, upload-time = "2026-05-20T21:57:50.018Z" }, + { url = "https://files.pythonhosted.org/packages/85/ee/09f438faab5d7d46d720374e42a3fb30446974b01e650bc21a03346e423e/valkey_glide-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0d466a5fc23c588a612473c2a96effb7589293751a5dec08bd8a8f18c0c0400e", size = 6947944, upload-time = "2026-05-20T21:57:52.139Z" }, + { url = "https://files.pythonhosted.org/packages/16/ee/b2840085ee3fdc09e5f17123a59a80f278e2afe4caf2bd3923959540d334/valkey_glide-2.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47780fd93b30991f6d9780b501ec9109ec4e2b4238dfb575efc2ac191c8dc69d", size = 7274725, upload-time = "2026-05-20T21:57:53.61Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e3/622cce53ab71a9390de4ee45d91ace3dfd19e0db99b04141156d9c19f2f0/valkey_glide-2.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb5255b3b54e79d01bab6b535ec6ccfafb7ea0601f8c6890a07316643f9983f7", size = 7696630, upload-time = "2026-05-20T21:57:55.496Z" }, + { url = "https://files.pythonhosted.org/packages/97/3f/091cd9d5ab541c21ffcc0078340a43fb7084cd717934910778f60a6a7de7/valkey_glide-2.4.0-cp314-cp314-macosx_10_7_x86_64.whl", hash = "sha256:7f9ee25fa5db27cfb6eb98ff17e24baac051ac34d005ebd428386fefc73a395d", size = 7473470, upload-time = "2026-05-20T21:57:57.524Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5b/4979833063515919c5d0d97505b882c1fad9040728608a7fdde83dfbae53/valkey_glide-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0df5d69d7d6629297e8742bad33ab5f378fc21e8ae8491eeb276964962955ebe", size = 6950770, upload-time = "2026-05-20T21:57:59.157Z" }, + { url = "https://files.pythonhosted.org/packages/f8/de/fdc6dace2786e5af9d02763b09fe719d589fde89472fdf068fe12216b04a/valkey_glide-2.4.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd0617276dd47c8650af2d670efd2251c0489cc593e5c3d73ec6d4084a49485d", size = 7276193, upload-time = "2026-05-20T21:58:01.188Z" }, + { url = "https://files.pythonhosted.org/packages/8b/28/e668a344d97e562ac0b22852e076a8e602bebb4699390380f3684178affa/valkey_glide-2.4.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6e1171e38ac4759ec01208f18e7fc9127a5d0306f733f4e24e86407a335fe42", size = 7695602, upload-time = "2026-05-20T21:58:02.988Z" }, + { url = "https://files.pythonhosted.org/packages/ae/57/0534c311c710d73f6f3cea04096f159711b3128e3f38097df4df764fb7de/valkey_glide-2.4.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:a717945ccc79f9df8aa68d8e4409953286c937c9edfa0cc96883989e4ec138e1", size = 7478516, upload-time = "2026-05-20T21:58:12.151Z" }, + { url = "https://files.pythonhosted.org/packages/d2/7d/9e7b8fa87c3c505f4ec1e88bc460da7ce87545d20c636b7306a77e7e774b/valkey_glide-2.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a5d430eb55dcc4d2d7207d87edf5773419a7cf5696ad24ec1d9ad80bf78bbc7a", size = 6949095, upload-time = "2026-05-20T21:58:14.106Z" }, + { url = "https://files.pythonhosted.org/packages/fb/59/ffc1ad5b7727f9cbb829fac6e6383b74e00670dfe5240e24eb905abda6c6/valkey_glide-2.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd4daf1d869c1c96c100eb84c53df9ddc7e915bd82d8f56dbfc5199be32ff07f", size = 7273139, upload-time = "2026-05-20T21:58:15.82Z" }, + { url = "https://files.pythonhosted.org/packages/a6/81/66d88d54e7eecbdefae911f918f8e785bdee2950524c81615d17680ce2b6/valkey_glide-2.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44edecc1977a42baa8c585722937b8e67568e3e0a05640d925df42cb99dcb8f2", size = 7698234, upload-time = "2026-05-20T21:58:17.504Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b4/81d9dcafced5dcdb159fc9bb372a32405deb041506fa39acdd438bfbf1aa/valkey_glide-2.4.0-pp311-pypy311_pp73-macosx_10_7_x86_64.whl", hash = "sha256:8dc07919ec72d4c6e5fff0ea3d309b359fe6705eeaeed8f3bd266aface17ce13", size = 7478618, upload-time = "2026-05-20T21:58:19.101Z" }, + { url = "https://files.pythonhosted.org/packages/29/ab/5ca8d31515601e0cff846a4e62af6fbc95a70d274ca1db4cf6a49c6ee297/valkey_glide-2.4.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b23c5d858cae517b1643c4cbefa35745afd5d9251f11fd290a6ae285b22748ed", size = 6949012, upload-time = "2026-05-20T21:58:21.095Z" }, + { url = "https://files.pythonhosted.org/packages/94/e6/b04246bca25e32fed0d4b4f6156bd61c735d7f9d84cb5adb7430bc73c595/valkey_glide-2.4.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c878f097b99e4034b32db92a390ea25a765adf7ea9c6cd3a5949010c892f462a", size = 7273352, upload-time = "2026-05-20T21:58:22.852Z" }, + { url = "https://files.pythonhosted.org/packages/46/0c/9a20f8becba1cb721311da3c8dabfdab6ad6afd3e9041503288227081083/valkey_glide-2.4.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:565a49e57492ce5ade793ecfdf0a8b4444f1d222c65036623a488ce1c80b98bf", size = 7698497, upload-time = "2026-05-20T21:58:24.763Z" }, +] + [[package]] name = "watchdog" version = "6.0.0"