From 88b972731a671a3a0d46f92fe24f2260c14651c3 Mon Sep 17 00:00:00 2001 From: Mason Daugherty Date: Mon, 28 Sep 2026 16:06:40 -0400 Subject: [PATCH] feat(fireworks): add prompt caching middleware (#38823) Added `FireworksPromptCachingMiddleware` to improve prompt-cache reuse across calls in the same agent thread. Explicit affinity settings take precedence, and no affinity is generated without a thread ID. --- Fireworks agents need consistent routing to reuse a replica's prompt cache across turns. `FireworksPromptCachingMiddleware` supplies session affinity from a SHA-256 hash of `config.configurable.thread_id`, while respecting explicit `user`, `prompt_cache_key`, and `x-session-affinity` settings on the selected model or request. Affinity is scoped to the call and applied by `ChatFireworks` when invoking the API. Generated affinity stays out of shared request settings, and model-local headers remain scoped to their owning model, including during fallback. This works with either ordering of the caching and fallback middleware for a Fireworks primary model. Related fallback cleanup for explicitly supplied cache settings is in #40886, stacked on this PR. This PR works independently of that change. --------- Co-authored-by: open-swe[bot] --- .../langchain_fireworks/chat_models.py | 64 +- .../middleware/__init__.py | 7 + .../middleware/prompt_caching.py | 184 ++++++ libs/partners/fireworks/pyproject.toml | 2 + .../tests/unit_tests/middleware/__init__.py | 0 .../middleware/test_prompt_caching.py | 586 ++++++++++++++++++ .../tests/unit_tests/test_chat_models.py | 46 +- libs/partners/fireworks/uv.lock | 303 +++++++-- 8 files changed, 1119 insertions(+), 73 deletions(-) create mode 100644 libs/partners/fireworks/langchain_fireworks/middleware/__init__.py create mode 100644 libs/partners/fireworks/langchain_fireworks/middleware/prompt_caching.py create mode 100644 libs/partners/fireworks/tests/unit_tests/middleware/__init__.py create mode 100644 libs/partners/fireworks/tests/unit_tests/middleware/test_prompt_caching.py diff --git a/libs/partners/fireworks/langchain_fireworks/chat_models.py b/libs/partners/fireworks/langchain_fireworks/chat_models.py index ff38f7e3a7..6d58e2034e 100644 --- a/libs/partners/fireworks/langchain_fireworks/chat_models.py +++ b/libs/partners/fireworks/langchain_fireworks/chat_models.py @@ -6,6 +6,7 @@ import contextlib import json import logging from collections.abc import AsyncIterator, Callable, Iterator, Mapping, Sequence +from contextvars import ContextVar from operator import itemgetter from typing import ( Any, @@ -123,6 +124,10 @@ from langchain_fireworks.data._profiles import _PROFILES logger = logging.getLogger(__name__) +_PROMPT_CACHE_AFFINITY: ContextVar[str | None] = ContextVar( + "fireworks_prompt_cache_affinity", default=None +) + _MODEL_PROFILES = cast("ModelProfileRegistry", _PROFILES) @@ -759,6 +764,57 @@ def _prepare_sdk_kwargs(kwargs: dict[str, Any]) -> dict[str, Any]: return kwargs +def _merge_model_headers(llm: ChatFireworks, kwargs: dict[str, Any]) -> dict[str, Any]: + """Merge model-local headers only when invoking their owning model.""" + model_headers = llm.model_kwargs.get("extra_headers") + request_headers = kwargs.get("extra_headers") + if isinstance(model_headers, Mapping) and isinstance(request_headers, Mapping): + # HTTP header names are case-insensitive; retain the request's spelling. + headers = { + key.lower(): (key, value) + for source in (model_headers, request_headers) + for key, value in source.items() + } + return { + **kwargs, + "extra_headers": dict(headers.values()), + } + return kwargs + + +def _apply_prompt_cache_affinity(kwargs: dict[str, Any]) -> dict[str, Any]: + """Apply the middleware default after the selected model's settings are merged.""" + affinity = _PROMPT_CACHE_AFFINITY.get() + if affinity is None: + return kwargs + + # The SDK merges extra_body over top-level fields before sending the request. + extra_body = kwargs.get("extra_body") + body = {**kwargs, **extra_body} if isinstance(extra_body, Mapping) else kwargs + if any(body.get(key) for key in ("user", "prompt_cache_key")): + return kwargs + + headers = kwargs.get("extra_headers") + if isinstance(headers, Mapping): + if any( + isinstance(key, str) and key.lower() == "x-session-affinity" + for key in headers + ): + return kwargs + elif headers is not None: + logger.warning( + "Cannot set Fireworks session affinity because extra_headers is %s", + type(headers).__name__, + ) + return kwargs + + return { + **kwargs, + "prompt_cache_key": affinity, + "extra_headers": {**(headers or {}), "x-session-affinity": affinity}, + } + + def _completion_with_retry( llm: ChatFireworks, run_manager: CallbackManagerForLLMRun | None = None, @@ -766,7 +822,9 @@ def _completion_with_retry( ) -> Any: """Retry the sync completion call, including stream setup.""" retry_decorator = _create_retry_decorator(llm, run_manager=run_manager) - kwargs = _prepare_sdk_kwargs(kwargs) + kwargs = _prepare_sdk_kwargs( + _apply_prompt_cache_affinity(_merge_model_headers(llm, kwargs)) + ) @retry_decorator def _call() -> Any: @@ -799,7 +857,9 @@ async def _acompletion_with_retry( ) -> Any: """Retry the async completion call, including stream setup.""" retry_decorator = _create_retry_decorator(llm, run_manager=run_manager) - kwargs = _prepare_sdk_kwargs(kwargs) + kwargs = _prepare_sdk_kwargs( + _apply_prompt_cache_affinity(_merge_model_headers(llm, kwargs)) + ) @retry_decorator async def _call() -> Any: diff --git a/libs/partners/fireworks/langchain_fireworks/middleware/__init__.py b/libs/partners/fireworks/langchain_fireworks/middleware/__init__.py new file mode 100644 index 0000000000..0bdbe4a617 --- /dev/null +++ b/libs/partners/fireworks/langchain_fireworks/middleware/__init__.py @@ -0,0 +1,7 @@ +"""Middleware for Fireworks models.""" + +from langchain_fireworks.middleware.prompt_caching import ( + FireworksPromptCachingMiddleware, +) + +__all__ = ["FireworksPromptCachingMiddleware"] diff --git a/libs/partners/fireworks/langchain_fireworks/middleware/prompt_caching.py b/libs/partners/fireworks/langchain_fireworks/middleware/prompt_caching.py new file mode 100644 index 0000000000..029e9f67eb --- /dev/null +++ b/libs/partners/fireworks/langchain_fireworks/middleware/prompt_caching.py @@ -0,0 +1,184 @@ +"""Middleware for Fireworks prompt-cache session affinity. + +Requires `langchain` for the agent middleware framework. It is imported lazily, +and an `ImportError` with install guidance is raised if it is missing. +`ChatFireworks` is provided by this package (`langchain-fireworks`). +""" + +from __future__ import annotations + +import hashlib +import logging +from collections.abc import Awaitable, Callable, Iterator +from contextlib import contextmanager +from typing import Literal +from warnings import warn + +from langchain_fireworks.chat_models import _PROMPT_CACHE_AFFINITY, ChatFireworks + +try: + from langchain.agents.middleware.types import ( + AgentMiddleware, + ModelCallResult, + ModelRequest, + ModelResponse, + ) + from langgraph.config import get_config +except ImportError as e: + msg = ( + "FireworksPromptCachingMiddleware requires 'langchain' to be installed " + f"(missing module: {e.name}). This middleware is designed for use with " + "LangChain agents. Install it with: pip install langchain" + ) + raise ImportError(msg) from e + +logger = logging.getLogger(__name__) + +_UNSUPPORTED_MODEL_BEHAVIORS = ("ignore", "warn", "raise") + + +def _get_thread_id(request: ModelRequest) -> str | None: + """Return the thread ID from request runtime metadata or runnable config.""" + # Runtime metadata is available even without async config propagation on + # Python 3.10. Older runtimes may not expose execution_info. + execution_info = getattr(request.runtime, "execution_info", None) + thread_id = getattr(execution_info, "thread_id", None) + if isinstance(thread_id, str) and thread_id: + return thread_id + + try: + config = get_config() + except RuntimeError: + return None + thread_id = (config.get("configurable") or {}).get("thread_id") + if isinstance(thread_id, str) and thread_id: + return thread_id + return None + + +@contextmanager +def _session_affinity(request: ModelRequest) -> Iterator[None]: + """Scope an affinity default to this call, including any fallback attempts.""" + thread_id = _get_thread_id(request) + affinity = ( + hashlib.sha256(thread_id.encode("utf-8")).hexdigest() + if thread_id is not None + else None + ) + if affinity is None: + logger.debug("Fireworks session affinity not applied: no thread_id in config") + token = _PROMPT_CACHE_AFFINITY.set(affinity) + try: + yield + finally: + _PROMPT_CACHE_AFFINITY.reset(token) + + +class FireworksPromptCachingMiddleware(AgentMiddleware): + """Set Fireworks prompt-cache session affinity from the active thread ID. + + Fireworks prompt caching is enabled by default. This middleware improves + cache hit rate by pinning session affinity to a SHA-256 hash of + `config.configurable.thread_id`, so related requests route to the same + replica and reuse its warm cache. The hexadecimal hash keeps affinity values + safe for HTTP headers, including when thread IDs contain Unicode. + + The middleware supplies a scoped default that `ChatFireworks` applies to + `prompt_cache_key` and `extra_headers["x-session-affinity"]` when invoking + the API. Explicit `user` or `prompt_cache_key` body fields (including + `extra_body` overrides), or `x-session-affinity` headers on the selected + model or request take precedence, including on fallback models. No affinity + is added when no thread ID is configured. + + Generated affinity stays out of shared request settings, so it is never + forwarded to another provider. This works with either ordering of this + middleware and `ModelFallbackMiddleware` for a Fireworks primary model. + """ + + def __init__( + self, + *, + unsupported_model_behavior: Literal["ignore", "warn", "raise"] = "warn", + ) -> None: + """Initialize the middleware. + + Args: + unsupported_model_behavior: Behavior when the request model is not + `ChatFireworks`. `"ignore"` continues without affinity, + `"warn"` emits a warning, and `"raise"` raises `ValueError`. + + Raises: + ValueError: If `unsupported_model_behavior` is not one of + `"ignore"`, `"warn"`, or `"raise"`. + """ + if unsupported_model_behavior not in _UNSUPPORTED_MODEL_BEHAVIORS: + msg = ( + "unsupported_model_behavior must be one of " + f"{_UNSUPPORTED_MODEL_BEHAVIORS}, got {unsupported_model_behavior!r}." + ) + raise ValueError(msg) + self.unsupported_model_behavior = unsupported_model_behavior + + def _should_apply_caching(self, request: ModelRequest) -> bool: + """Return whether session affinity should be applied to the request. + + Args: + request: The model request to check. + + Returns: + `True` if the request model is `ChatFireworks`, else `False`. + + Raises: + ValueError: If the model is unsupported and + `unsupported_model_behavior` is `"raise"`. + """ + if isinstance(request.model, ChatFireworks): + return True + + msg = ( + "FireworksPromptCachingMiddleware only supports ChatFireworks, " + f"not {type(request.model).__name__}." + ) + if self.unsupported_model_behavior == "raise": + raise ValueError(msg) + if self.unsupported_model_behavior == "warn": + warn(msg, stacklevel=3) + return False + + def wrap_model_call( + self, + request: ModelRequest, + handler: Callable[[ModelRequest], ModelResponse], + ) -> ModelCallResult: + """Supply default Fireworks affinity while executing the handler. + + Args: + request: The outgoing model request. + handler: Callable that executes the request. + + Returns: + The result produced by `handler`. + """ + if not self._should_apply_caching(request): + return handler(request) + with _session_affinity(request): + return handler(request) + + async def awrap_model_call( + self, + request: ModelRequest, + handler: Callable[[ModelRequest], Awaitable[ModelResponse]], + ) -> ModelCallResult: + """Supply default Fireworks affinity while awaiting the handler. + + Args: + request: The outgoing model request. + handler: Async callable that executes the request. + + Returns: + The result produced by `handler`. + """ + if not self._should_apply_caching(request): + return await handler(request) + with _session_affinity(request): + return await handler(request) diff --git a/libs/partners/fireworks/pyproject.toml b/libs/partners/fireworks/pyproject.toml index 14f6b33290..4c1a2522ce 100644 --- a/libs/partners/fireworks/pyproject.toml +++ b/libs/partners/fireworks/pyproject.toml @@ -52,6 +52,7 @@ test = [ "pytest-socket>=0.7.0,<1.0.0", "pytest-xdist>=3.8.0,<4.0.0", "langchain-tests>=1.1.9,<2.0.0", + "langchain>=1.0.0,<2.0.0", ] test_integration = [] lint = ["ruff>=0.13.1,<0.14.0"] @@ -72,6 +73,7 @@ constraint-dependencies = [ prerelease = "allow" [tool.uv.sources] +langchain = { path = "../../langchain_v1", editable = true } langchain-core = { path = "../../core", editable = true } langchain-tests = { path = "../../standard-tests", editable = true } diff --git a/libs/partners/fireworks/tests/unit_tests/middleware/__init__.py b/libs/partners/fireworks/tests/unit_tests/middleware/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/partners/fireworks/tests/unit_tests/middleware/test_prompt_caching.py b/libs/partners/fireworks/tests/unit_tests/middleware/test_prompt_caching.py new file mode 100644 index 0000000000..470c7ff272 --- /dev/null +++ b/libs/partners/fireworks/tests/unit_tests/middleware/test_prompt_caching.py @@ -0,0 +1,586 @@ +"""Unit tests for `FireworksPromptCachingMiddleware`.""" + +from __future__ import annotations + +import asyncio +import json +import logging +from collections.abc import Awaitable, Callable +from typing import Any +from unittest.mock import AsyncMock, MagicMock, patch + +import httpx +import pytest +from fireworks import AsyncFireworks, Fireworks +from langchain.agents import create_agent +from langchain.agents.middleware import ModelFallbackMiddleware, wrap_model_call +from langchain.agents.middleware.types import ModelRequest, ModelResponse +from langchain_core.language_models.fake_chat_models import GenericFakeChatModel +from langchain_core.messages import AIMessage, HumanMessage +from langchain_core.runnables import RunnableConfig + +from langchain_fireworks import ChatFireworks +from langchain_fireworks.middleware import FireworksPromptCachingMiddleware + +_THREAD_ID = "thread-abc-123" +_AFFINITY = "ef8dbcc47038744819e7c6386305c0acf1961597773391096c267358ec6496eb" +_MODEL_NAME = "accounts/fireworks/models/test-model" +_SESSION_AFFINITY_HEADER = "x-session-affinity" + + +def _make_model(**kwargs: Any) -> ChatFireworks: + settings: dict[str, Any] = { + "model": _MODEL_NAME, + "api_key": "fake-key", + **kwargs, + } + model = ChatFireworks(**settings) + response = {"choices": [{"message": {"role": "assistant", "content": "ok"}}]} + model.client = MagicMock() + model.client.create.return_value = response + model.async_client = MagicMock() + model.async_client.create = AsyncMock(return_value=response) + return model + + +def _call_kwargs(request: ModelRequest) -> dict[str, Any]: + assert isinstance(request.model, ChatFireworks) + return dict(request.model.client.create.call_args.kwargs) + + +def _make_request( + model: ChatFireworks | GenericFakeChatModel, + model_settings: dict[str, Any] | None = None, +) -> ModelRequest: + return ModelRequest( + model=model, + messages=[], + model_settings=model_settings if model_settings is not None else {}, + ) + + +def _run( + request: ModelRequest, + *, + middleware: FireworksPromptCachingMiddleware | None = None, + thread_id: str | None = _THREAD_ID, + config: dict[str, Any] | None = None, +) -> ModelRequest: + middleware = middleware or FireworksPromptCachingMiddleware() + captured: dict[str, ModelRequest] = {} + + def handler(req: ModelRequest) -> ModelResponse: + captured["request"] = req + return ModelResponse(result=[req.model.invoke("Hello", **req.model_settings)]) + + if config is None: + config = ( + {"configurable": {"thread_id": thread_id}} if thread_id is not None else {} + ) + with patch( + "langchain_fireworks.middleware.prompt_caching.get_config", + return_value=config, + ): + middleware.wrap_model_call(request, handler) + return captured["request"] + + +async def _arun( + request: ModelRequest, + *, + middleware: FireworksPromptCachingMiddleware | None = None, +) -> ModelRequest: + middleware = middleware or FireworksPromptCachingMiddleware() + captured: dict[str, ModelRequest] = {} + + async def handler(req: ModelRequest) -> ModelResponse: + captured["request"] = req + message = await req.model.ainvoke("Hello", **req.model_settings) + return ModelResponse(result=[message]) + + with patch( + "langchain_fireworks.middleware.prompt_caching.get_config", + return_value={"configurable": {"thread_id": _THREAD_ID}}, + ): + await middleware.awrap_model_call(request, handler) + return captured["request"] + + +@pytest.mark.parametrize( + "thread_id", + [ + "会話-café-😀", + "thread\r\nInjected: value\x00", + "x" * 1024, + ], + ids=["unicode", "control-characters", "long"], +) +def test_thread_id_produces_stable_header_safe_affinity(thread_id: str) -> None: + request = _make_request(_make_model()) + + def run(value: str) -> dict[str, Any]: + _run(request, thread_id=value) + return _call_kwargs(request) + + result = run(thread_id) + affinity = httpx.Headers(result["extra_headers"])[_SESSION_AFFINITY_HEADER] + assert result["prompt_cache_key"] == affinity + assert len(affinity) == 64 + assert set(affinity) <= set("0123456789abcdef") + assert run(thread_id)["prompt_cache_key"] == affinity + assert run(thread_id + "-other")["prompt_cache_key"] != affinity + assert request.model_settings == {} + + +def test_unsupported_model_behavior() -> None: + model = GenericFakeChatModel(messages=iter([AIMessage(content="ok")] * 2)) + request = _make_request(model) + + ignored = _run( + request, + middleware=FireworksPromptCachingMiddleware( + unsupported_model_behavior="ignore" + ), + ) + assert ignored is request + + with pytest.warns(UserWarning, match="only supports ChatFireworks"): + warned = _run(request) + assert warned is request + + with pytest.raises(ValueError, match="only supports ChatFireworks"): + _run( + request, + middleware=FireworksPromptCachingMiddleware( + unsupported_model_behavior="raise" + ), + ) + + +def test_invalid_unsupported_model_behavior_raises() -> None: + with pytest.raises(ValueError, match="unsupported_model_behavior must be one of"): + FireworksPromptCachingMiddleware( + unsupported_model_behavior="warning", # type: ignore[arg-type] + ) + + +@pytest.mark.parametrize( + ("config", "thread_id"), + [ + ({}, None), + ({"configurable": None}, _THREAD_ID), + (None, ""), + ({"configurable": {"thread_id": 123}}, None), + ], +) +def test_missing_thread_id_is_unchanged( + config: dict[str, Any] | None, + thread_id: str | None, +) -> None: + request = _make_request(_make_model()) + result = _run(request, config=config, thread_id=thread_id) + + assert result is request + assert result.model_settings == {} + assert "prompt_cache_key" not in _call_kwargs(request) + assert "extra_headers" not in _call_kwargs(request) + + +def test_no_runnable_context_is_unchanged() -> None: + request = _make_request(_make_model()) + middleware = FireworksPromptCachingMiddleware() + captured: dict[str, ModelRequest] = {} + + def handler(req: ModelRequest) -> ModelResponse: + captured["request"] = req + return ModelResponse(result=[req.model.invoke("Hello", **req.model_settings)]) + + with patch( + "langchain_fireworks.middleware.prompt_caching.get_config", + side_effect=RuntimeError, + ): + middleware.wrap_model_call(request, handler) + + assert captured["request"] is request + assert "prompt_cache_key" not in _call_kwargs(request) + assert "extra_headers" not in _call_kwargs(request) + + +@pytest.mark.parametrize("setting", ["user", "prompt_cache_key"]) +def test_existing_affinity_setting_causes_no_injection(setting: str) -> None: + request = _make_request(_make_model(), {setting: "caller"}) + result = _run(request) + + assert result is request + assert result.model_settings == {setting: "caller"} + assert _call_kwargs(request)[setting] == "caller" + assert "extra_headers" not in _call_kwargs(request) + + +@pytest.mark.parametrize("use_async", [False, True]) +@pytest.mark.parametrize("on_model", [False, True]) +@pytest.mark.parametrize("setting", ["user", "prompt_cache_key"]) +async def test_extra_body_affinity_takes_precedence_on_the_wire( + setting: str, *, use_async: bool, on_model: bool +) -> None: + requests: list[httpx.Request] = [] + extra_body = {setting: "explicit-affinity"} + + def respond(request: httpx.Request) -> httpx.Response: + requests.append(request) + return httpx.Response( + 200, + json={"choices": [{"message": {"role": "assistant", "content": "ok"}}]}, + ) + + with Fireworks( + api_key="fake-key", + http_client=httpx.Client(transport=httpx.MockTransport(respond)), + ) as sdk: + async with AsyncFireworks( + api_key="fake-key", + http_client=httpx.AsyncClient(transport=httpx.MockTransport(respond)), + ) as async_sdk: + model = ChatFireworks( + model=_MODEL_NAME, + api_key="fake-key", # type: ignore[arg-type] + client=sdk.chat.completions, + async_client=async_sdk.chat.completions, + model_kwargs={"extra_body": extra_body} if on_model else {}, + ) + request = _make_request( + model, {} if on_model else {"extra_body": extra_body} + ) + if use_async: + await _arun(request) + else: + _run(request) + + assert len(requests) == 1 + body = json.loads(requests[0].content) + assert body[setting] == "explicit-affinity" + assert body.get("prompt_cache_key") == extra_body.get("prompt_cache_key") + assert _SESSION_AFFINITY_HEADER not in requests[0].headers + assert extra_body == {setting: "explicit-affinity"} + assert model.model_kwargs == ({"extra_body": extra_body} if on_model else {}) + assert request.model_settings == ({} if on_model else {"extra_body": extra_body}) + + +@pytest.mark.parametrize("use_async", [False, True]) +@pytest.mark.parametrize("setting", ["user", "prompt_cache_key"]) +async def test_extra_body_null_overrides_top_level_affinity( + setting: str, *, use_async: bool +) -> None: + settings = {setting: "overridden", "extra_body": {setting: None}} + model = _make_model() + request = _make_request(model, settings) + if use_async: + await _arun(request) + else: + _run(request) + + client = model.async_client if use_async else model.client + kwargs = client.create.call_args.kwargs + assert kwargs["extra_headers"][_SESSION_AFFINITY_HEADER] == _AFFINITY + assert kwargs["prompt_cache_key"] == _AFFINITY + assert kwargs["extra_body"] == {setting: None} + assert settings == {setting: "overridden", "extra_body": {setting: None}} + + +@pytest.mark.parametrize("setting", ["user", "prompt_cache_key"]) +def test_null_affinity_setting_injects_session_affinity(setting: str) -> None: + request = _make_request(_make_model(), {setting: None}) + result = _run(request) + + assert result is request + kwargs = _call_kwargs(request) + if setting == "user": + assert kwargs[setting] is None + assert kwargs["prompt_cache_key"] == _AFFINITY + assert kwargs["extra_headers"][_SESSION_AFFINITY_HEADER] == _AFFINITY + + +def test_existing_session_affinity_header_causes_no_injection() -> None: + request = _make_request( + _make_model(), + {"extra_headers": {"X-Session-Affinity": "existing"}}, + ) + result = _run(request) + + assert result is request + assert result.model_settings["extra_headers"] == {"X-Session-Affinity": "existing"} + assert _call_kwargs(request)["extra_headers"] == {"X-Session-Affinity": "existing"} + assert "prompt_cache_key" not in _call_kwargs(request) + + +@pytest.mark.parametrize("use_async", [False, True]) +@pytest.mark.parametrize("fireworks_fallback", [False, True]) +async def test_model_headers_do_not_leak_to_fallback( + *, use_async: bool, fireworks_fallback: bool +) -> None: + primary_headers = {"Authorization": "primary-placeholder"} + primary = _make_model(model_kwargs={"extra_headers": primary_headers}) + primary.client = MagicMock() + primary.client.create.side_effect = ValueError("primary failed") + primary.async_client = MagicMock() + primary.async_client.create = AsyncMock(side_effect=ValueError("primary failed")) + fallback: ChatFireworks | GenericFakeChatModel + if fireworks_fallback: + fallback = _make_model( + model_kwargs={"extra_headers": {"Authorization": "fallback-placeholder"}} + ) + else: + fallback = GenericFakeChatModel(messages=iter([AIMessage(content="ok")])) + + request_headers = {"X-Request-ID": "request-1"} + request = _make_request(primary, {"extra_headers": request_headers}) + caching = FireworksPromptCachingMiddleware() + fallbacks = ModelFallbackMiddleware(fallback) + attempts: list[ModelRequest] = [] + + def handler(req: ModelRequest) -> ModelResponse: + attempts.append(req) + return ModelResponse(result=[req.model.invoke("Hello", **req.model_settings)]) + + async def ahandler(req: ModelRequest) -> ModelResponse: + attempts.append(req) + message = await req.model.ainvoke("Hello", **req.model_settings) + return ModelResponse(result=[message]) + + with patch( + "langchain_fireworks.middleware.prompt_caching.get_config", + return_value={"configurable": {"thread_id": _THREAD_ID}}, + ): + if use_async: + + async def afallback(req: ModelRequest) -> ModelResponse: + result = await fallbacks.awrap_model_call(req, ahandler) + assert isinstance(result, ModelResponse) + return result + + await caching.awrap_model_call(request, afallback) + else: + + def fallback_handler(req: ModelRequest) -> ModelResponse: + result = fallbacks.wrap_model_call(req, handler) + assert isinstance(result, ModelResponse) + return result + + caching.wrap_model_call(request, fallback_handler) + + assert len(attempts) == 2 + for attempt in attempts: + assert attempt.model_settings == {"extra_headers": request_headers} + assert "Authorization" not in attempt.model_settings["extra_headers"] + assert attempt.model_settings["extra_headers"]["X-Request-ID"] == "request-1" + client = primary.async_client if use_async else primary.client + assert client.create.call_args.kwargs["extra_headers"] == { + **primary_headers, + **request_headers, + _SESSION_AFFINITY_HEADER: _AFFINITY, + } + if isinstance(fallback, ChatFireworks): + client = fallback.async_client if use_async else fallback.client + assert client.create.call_args.kwargs["extra_headers"] == { + "Authorization": "fallback-placeholder", + **request_headers, + _SESSION_AFFINITY_HEADER: _AFFINITY, + } + assert primary.model_kwargs["extra_headers"] == primary_headers + assert request.model_settings == {"extra_headers": request_headers} + + +def test_non_mapping_extra_headers_is_unchanged_and_warns( + caplog: pytest.LogCaptureFixture, +) -> None: + request = _make_request( + _make_model(), + {"extra_headers": ["not", "a", "mapping"]}, + ) + + with caplog.at_level( + logging.WARNING, + logger="langchain_fireworks.chat_models", + ): + result = _run(request) + + assert result is request + assert any("extra_headers" in record.message for record in caplog.records) + + +async def test_async_unsupported_model_passes_original_request() -> None: + model = GenericFakeChatModel(messages=iter([AIMessage(content="ok")])) + request = _make_request(model) + result = await _arun( + request, + middleware=FireworksPromptCachingMiddleware( + unsupported_model_behavior="ignore" + ), + ) + + assert result is request + + +@pytest.mark.parametrize("use_async", [False, True]) +async def test_agent_thread_id_without_runnable_context(*, use_async: bool) -> None: + """Runtime metadata supplies affinity without ambient config on Python 3.10.""" + model = _make_model() + agent = create_agent(model, middleware=[FireworksPromptCachingMiddleware()]) + config: RunnableConfig = {"configurable": {"thread_id": _THREAD_ID}} + + with patch( + "langchain_fireworks.middleware.prompt_caching.get_config", + side_effect=RuntimeError("No runnable context"), + ): + if use_async: + await agent.ainvoke({"messages": [HumanMessage("Hello")]}, config) + else: + agent.invoke({"messages": [HumanMessage("Hello")]}, config) + + client = model.async_client if use_async else model.client + kwargs = client.create.call_args.kwargs + assert kwargs["prompt_cache_key"] == _AFFINITY + assert kwargs["extra_headers"][_SESSION_AFFINITY_HEADER] == _AFFINITY + + +@pytest.mark.parametrize("use_async", [False, True]) +@pytest.mark.parametrize( + ("primary_settings", "fallback_settings", "caching_first"), + [ + pytest.param({}, {}, False, id="automatic-fallback-first"), + pytest.param({}, {}, True, id="automatic-caching-first"), + pytest.param({}, {"user": "fallback-user"}, True, id="fallback-user"), + pytest.param( + {}, {"prompt_cache_key": "fallback-cache"}, True, id="fallback-key" + ), + pytest.param( + {}, + {"extra_headers": {"X-Session-Affinity": "fallback-affinity"}}, + True, + id="fallback-header", + ), + pytest.param( + {"prompt_cache_key": "primary-cache"}, + {}, + False, + id="primary-key-fallback-first", + ), + pytest.param( + {"prompt_cache_key": "primary-cache"}, + {}, + True, + id="primary-key-caching-first", + ), + ], +) +async def test_fallback_respects_explicit_affinity( + primary_settings: dict[str, Any], + fallback_settings: dict[str, Any], + *, + use_async: bool, + caching_first: bool, +) -> None: + primary = _make_model(model_kwargs=primary_settings) + primary.client = MagicMock() + primary.client.create.side_effect = ValueError("primary failed") + primary.async_client = MagicMock() + primary.async_client.create = AsyncMock(side_effect=ValueError("primary failed")) + fallback = _make_model(model_kwargs=fallback_settings) + caching = FireworksPromptCachingMiddleware() + fallbacks = ModelFallbackMiddleware(fallback) + agent = create_agent( + primary, + middleware=[caching, fallbacks] if caching_first else [fallbacks, caching], + ) + config: RunnableConfig = {"configurable": {"thread_id": _THREAD_ID}} + if use_async: + await agent.ainvoke({"messages": [HumanMessage("Hello")]}, config) + else: + agent.invoke({"messages": [HumanMessage("Hello")]}, config) + + client = fallback.async_client if use_async else fallback.client + kwargs = client.create.call_args.kwargs + expected = fallback_settings or { + "prompt_cache_key": _AFFINITY, + "extra_headers": {_SESSION_AFFINITY_HEADER: _AFFINITY}, + } + for setting in ("user", "prompt_cache_key", "extra_headers"): + assert kwargs.get(setting) == expected.get(setting) + assert fallback.model_kwargs == fallback_settings + client = primary.async_client if use_async else primary.client + assert client.create.call_args.kwargs["prompt_cache_key"] == primary_settings.get( + "prompt_cache_key", _AFFINITY + ) + assert primary.model_kwargs == primary_settings + + +@pytest.mark.parametrize("use_async", [False, True]) +@pytest.mark.parametrize("fail", [False, True]) +async def test_affinity_is_cleared_after_handler( + *, use_async: bool, fail: bool +) -> None: + model = _make_model() + client = model.async_client if use_async else model.client + request = _make_request(model) + if fail: + client.create.side_effect = ValueError("model failed") + with pytest.raises(ValueError, match="model failed"): + if use_async: + await _arun(request) + else: + _run(request) + client.create.side_effect = None + elif use_async: + await _arun(request) + else: + _run(request) + + assert client.create.call_args.kwargs["prompt_cache_key"] == _AFFINITY + if use_async: + await model.ainvoke("Outside middleware") + else: + model.invoke("Outside middleware") + assert "prompt_cache_key" not in client.create.call_args.kwargs + assert "extra_headers" not in client.create.call_args.kwargs + + +async def test_concurrent_agents_keep_affinity_separate() -> None: + model = _make_model() + ready = asyncio.Event() + arrivals = 0 + + @wrap_model_call + async def wait_for_both_calls( + request: ModelRequest, + handler: Callable[[ModelRequest], Awaitable[ModelResponse]], + ) -> ModelResponse: + nonlocal arrivals + arrivals += 1 + if arrivals == 2: + ready.set() + await ready.wait() + return await handler(request) + + agent = create_agent( + model, middleware=[FireworksPromptCachingMiddleware(), wait_for_both_calls] + ) + await asyncio.wait_for( + asyncio.gather( + *( + agent.ainvoke( + {"messages": [HumanMessage(thread_id)]}, + {"configurable": {"thread_id": thread_id}}, + ) + for thread_id in (_THREAD_ID, "other-thread") + ) + ), + timeout=10, + ) + keys = { + call.kwargs["messages"][0]["content"]: call.kwargs["prompt_cache_key"] + for call in model.async_client.create.call_args_list + } + assert keys[_THREAD_ID] == _AFFINITY + assert keys["other-thread"] != _AFFINITY + await model.ainvoke("Outside middleware") + assert "prompt_cache_key" not in model.async_client.create.call_args.kwargs diff --git a/libs/partners/fireworks/tests/unit_tests/test_chat_models.py b/libs/partners/fireworks/tests/unit_tests/test_chat_models.py index 5ac6fefc26..d2330955e7 100644 --- a/libs/partners/fireworks/tests/unit_tests/test_chat_models.py +++ b/libs/partners/fireworks/tests/unit_tests/test_chat_models.py @@ -16,6 +16,7 @@ from fireworks import ( APITimeoutError, AuthenticationError, BadRequestError, + Fireworks, FireworksError, InternalServerError, NotFoundError, @@ -1497,14 +1498,37 @@ class TestCreateChatResult: class TestExtraHeaders: """Tests for request-specific HTTP header plumbing.""" + def test_mixed_case_headers_are_not_duplicated_on_the_wire(self) -> None: + requests: list[httpx.Request] = [] + + def respond(request: httpx.Request) -> httpx.Response: + requests.append(request) + return httpx.Response(200, json=_success_response()) + + with Fireworks( + api_key="fake-key", + http_client=httpx.Client(transport=httpx.MockTransport(respond)), + ) as sdk: + model = _make_model( + client=sdk.chat.completions, + async_client=MagicMock(), + model_kwargs={"extra_headers": {"X-Session-Affinity": "model"}}, + ) + model.invoke("Hello", extra_headers={"x-session-affinity": "request"}) + + assert len(requests) == 1 + assert requests[0].headers.get_list("x-session-affinity") == ["request"] + def test_extra_headers_forwarded_to_sync_create(self) -> None: - model = _make_model() + model_headers = {"X-Model": "model", "x-shared": "model"} + model = _make_model(model_kwargs={"extra_headers": model_headers}) model.client = MagicMock() model.client.create.return_value = { "choices": [{"message": {"role": "assistant", "content": "ok"}}], "usage": {}, } headers = { + "X-Shared": "request", "x-session-affinity": "thread-123", "x-multi-turn-session-id": "thread-123", } @@ -1512,15 +1536,19 @@ class TestExtraHeaders: model.invoke("Hello", extra_headers=headers) call_kwargs = model.client.create.call_args[1] - assert call_kwargs["extra_headers"] == headers + assert call_kwargs["extra_headers"] == {"X-Model": "model", **headers} + assert model.model_kwargs["extra_headers"] == model_headers + assert "X-Model" not in headers # `extra_headers` must reach the SDK at the top level, not be folded # into `extra_body` by `_prepare_sdk_kwargs`. assert "extra_headers" not in call_kwargs.get("extra_body", {}) async def test_extra_headers_forwarded_to_async_create(self) -> None: - model = _make_model() + model_headers = {"X-Model": "model", "x-shared": "model"} + model = _make_model(model_kwargs={"extra_headers": model_headers}) model.async_client = MagicMock() headers = { + "X-Shared": "request", "x-session-affinity": "thread-123", "x-multi-turn-session-id": "thread-123", } @@ -1536,14 +1564,18 @@ class TestExtraHeaders: await model.ainvoke("Hello", extra_headers=headers) call_kwargs = model.async_client.create.call_args[1] - assert call_kwargs["extra_headers"] == headers + assert call_kwargs["extra_headers"] == {"X-Model": "model", **headers} + assert model.model_kwargs["extra_headers"] == model_headers + assert "X-Model" not in headers def test_extra_headers_forwarded_when_streaming(self) -> None: """`extra_headers` must also survive the separate streaming param path.""" - model = _make_model() + model_headers = {"X-Model": "model", "x-shared": "model"} + model = _make_model(model_kwargs={"extra_headers": model_headers}) model.client = MagicMock() model.client.create.return_value = iter(list(_STREAM_CHUNKS)) headers = { + "X-Shared": "request", "x-session-affinity": "thread-123", "x-multi-turn-session-id": "thread-123", } @@ -1551,7 +1583,9 @@ class TestExtraHeaders: list(model.stream("Hello", extra_headers=headers)) call_kwargs = model.client.create.call_args[1] - assert call_kwargs["extra_headers"] == headers + assert call_kwargs["extra_headers"] == {"X-Model": "model", **headers} + assert model.model_kwargs["extra_headers"] == model_headers + assert "X-Model" not in headers assert "extra_headers" not in call_kwargs.get("extra_body", {}) diff --git a/libs/partners/fireworks/uv.lock b/libs/partners/fireworks/uv.lock index de63060bf5..3c85150c07 100644 --- a/libs/partners/fireworks/uv.lock +++ b/libs/partners/fireworks/uv.lock @@ -447,7 +447,7 @@ name = "exceptiongroup" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } wheels = [ @@ -615,8 +615,8 @@ name = "httpcore2" version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "h11", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten'" }, - { name = "truststore", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten'" }, + { name = "h11" }, + { name = "truststore" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648", size = 67548, upload-time = "2026-08-18T13:22:08.211Z" } wheels = [ @@ -815,6 +815,71 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, ] +[[package]] +name = "langchain" +version = "1.3.18" +source = { editable = "../../langchain_v1" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph" }, + { name = "pydantic" }, +] + +[package.metadata] +requires-dist = [ + { name = "langchain-anthropic", marker = "extra == 'anthropic'", editable = "../anthropic" }, + { name = "langchain-aws", marker = "extra == 'aws'" }, + { name = "langchain-azure-ai", marker = "extra == 'azure-ai'" }, + { name = "langchain-baseten", marker = "extra == 'baseten'", specifier = ">=0.2.0" }, + { name = "langchain-community", marker = "extra == 'community'" }, + { name = "langchain-core", editable = "../../core" }, + { name = "langchain-deepseek", marker = "extra == 'deepseek'" }, + { name = "langchain-fireworks", marker = "extra == 'fireworks'" }, + { name = "langchain-google-genai", marker = "extra == 'google-genai'" }, + { name = "langchain-google-vertexai", marker = "extra == 'google-vertexai'" }, + { name = "langchain-groq", marker = "extra == 'groq'" }, + { name = "langchain-huggingface", marker = "extra == 'huggingface'" }, + { name = "langchain-meta", marker = "extra == 'meta'" }, + { name = "langchain-mistralai", marker = "extra == 'mistralai'" }, + { name = "langchain-ollama", marker = "extra == 'ollama'" }, + { name = "langchain-openai", marker = "extra == 'openai'", editable = "../openai" }, + { name = "langchain-perplexity", marker = "extra == 'perplexity'" }, + { name = "langchain-together", marker = "extra == 'together'" }, + { name = "langchain-xai", marker = "extra == 'xai'" }, + { name = "langgraph", specifier = ">=1.2.11,<1.3.0" }, + { name = "pydantic", specifier = ">=2.7.4,<3.0.0" }, +] +provides-extras = ["community", "anthropic", "openai", "azure-ai", "google-vertexai", "google-genai", "fireworks", "ollama", "together", "mistralai", "huggingface", "groq", "aws", "baseten", "deepseek", "xai", "perplexity", "meta"] + +[package.metadata.requires-dev] +lint = [{ name = "ruff", specifier = ">=0.15.0,<0.17.0" }] +test = [ + { name = "blockbuster", specifier = ">=1.5.26,<1.6.0" }, + { name = "langchain-openai", editable = "../openai" }, + { name = "langchain-tests", editable = "../../standard-tests" }, + { name = "pytest", specifier = ">=9.0.3,<10.0.0" }, + { name = "pytest-asyncio", specifier = ">=1.3.0,<2.0.0" }, + { name = "pytest-benchmark", specifier = ">=5.1.0,<6.0.0" }, + { name = "pytest-cov", specifier = ">=4.0.0,<8.0.0" }, + { name = "pytest-mock" }, + { name = "pytest-socket", specifier = ">=0.6.0,<1.0.0" }, + { name = "pytest-watcher", specifier = ">=0.2.6,<1.0.0" }, + { name = "pytest-xdist", specifier = ">=3.6.1,<4.0.0" }, + { name = "syrupy", specifier = ">=5.0.0,<6.0.0" }, + { name = "toml", specifier = ">=0.10.2,<1.0.0" }, +] +test-integration = [ + { name = "langchain-text-splitters", editable = "../../text-splitters" }, + { name = "langchainhub", specifier = ">=0.1.16,<1.0.0" }, + { name = "python-dotenv", specifier = ">=1.0.0,<2.0.0" }, + { name = "vcrpy", specifier = ">=8.2.0,<9.0.0" }, + { name = "wrapt", specifier = ">=1.15.0,<3.0.0" }, +] +typing = [ + { name = "mypy", specifier = ">=2.1.0,<2.2.0" }, + { name = "types-toml", specifier = ">=0.10.8.20240310,<1.0.0.0" }, +] + [[package]] name = "langchain-core" version = "1.6.5" @@ -897,6 +962,7 @@ lint = [ ] test = [ { name = "freezegun" }, + { name = "langchain" }, { name = "langchain-tests" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -926,6 +992,7 @@ dev = [] lint = [{ name = "ruff", specifier = ">=0.13.1,<0.14.0" }] test = [ { name = "freezegun", specifier = ">=1.2.2,<2.0.0" }, + { name = "langchain", editable = "../../langchain_v1" }, { name = "langchain-tests", editable = "../../standard-tests" }, { name = "pytest", specifier = ">=9.0.3,<10.0.0" }, { name = "pytest-asyncio", specifier = ">=1.3.0,<2.0.0" }, @@ -998,6 +1065,65 @@ typing = [ { name = "types-pyyaml", specifier = ">=6.0.12.2,<7.0.0.0" }, ] +[[package]] +name = "langgraph" +version = "1.2.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, + { name = "langgraph-prebuilt" }, + { name = "langgraph-sdk" }, + { name = "pydantic" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/0d/c8e7ee98896659e1b6555db0ab115a9ca899844744645d5d894032bab1d7/langgraph-1.2.11.tar.gz", hash = "sha256:9ecfe11e50d338b34b15cf4d8a442642de103e8ae6971320efba84e4542eb363", size = 725753, upload-time = "2026-08-11T14:00:36.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7f/c5c30e4be99ff821029c7ac872a480676bb179c9f3df85ea3f38d13f86d4/langgraph-1.2.11-py3-none-any.whl", hash = "sha256:8bab70de7b2d00b5300fb289bcf38d8b241400f3184c1e95e8ce706fb0e8686b", size = 248854, upload-time = "2026-08-11T14:00:35.494Z" }, +] + +[[package]] +name = "langgraph-checkpoint" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "ormsgpack" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/47/886af6f886f0bff2273164a45f008694e48a96ff3cd25ff0228f2aa9480e/langgraph_checkpoint-4.1.1.tar.gz", hash = "sha256:6c2bdb530c91f91d7d9c1bd100925d0fc4f498d418c17f3587d1526279482a25", size = 184020, upload-time = "2026-05-22T16:57:38.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/b4/71425e3e38be92611300b9cc5e46a5bf98ab23f5ea8a75b73d02a2f1413c/langgraph_checkpoint-4.1.1-py3-none-any.whl", hash = "sha256:25d29144b082827218e7bc3f1e9b0566a4bb007895cd6cc26f66a8428739f56e", size = 56212, upload-time = "2026-05-22T16:57:37.203Z" }, +] + +[[package]] +name = "langgraph-prebuilt" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/66/ed9b93f56bc17ef22d551892f0ac2b225a97fe0fcf23a511b857f70d590b/langgraph_prebuilt-1.1.0.tar.gz", hash = "sha256:3c579cf6eed2d17f9c157c2d0fcaddcd8688524e7022d3b22b37a3bf4589d528", size = 178833, upload-time = "2026-05-12T03:37:49.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/43/3fe1a700b8490ed02679cdbbc8c915eb23a092faf496c9c1118abcd10be3/langgraph_prebuilt-1.1.0-py3-none-any.whl", hash = "sha256:51e311747d755b751d5c6b39b0c1446124d3a7643d2515017e6714b323508fc9", size = 41043, upload-time = "2026-05-12T03:37:48.007Z" }, +] + +[[package]] +name = "langgraph-sdk" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "langchain-core" }, + { name = "langchain-protocol" }, + { name = "orjson" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/2b/bd8ac26d4e97f6df88ef05ce5b6a38945a3903e1025d926f4752aa88aa97/langgraph_sdk-0.4.2.tar.gz", hash = "sha256:b88f0f5f6328ac0680d6790614a905b2bcfa257f2276dba4e38f0e86db0aa738", size = 348327, upload-time = "2026-06-01T17:51:19.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/05/aac507337cceae773c2cc9ab91eb6301963af7aeeb55b4217a00e15aff17/langgraph_sdk-0.4.2-py3-none-any.whl", hash = "sha256:75fa5096c1177ce39c847096a8fe3745ffd480ddb412995f836e9f5f884c43dd", size = 160521, upload-time = "2026-06-01T17:51:18.849Z" }, +] + [[package]] name = "langsmith" version = "0.12.6" @@ -1545,6 +1671,62 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/82/56/630c9113ec8996778f1f0304b364b091b9a9db5fef5fdc17cca622f5ea24/orjson-3.12.0-cp315-cp315-win_arm64.whl", hash = "sha256:859fc4196855890150bb08e649b30d2c93b249b3e3edd0d3bb2231abf8aa8adc", size = 126754, upload-time = "2026-08-14T16:13:28.962Z" }, ] +[[package]] +name = "ormsgpack" +version = "1.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/0c/f1761e21486942ab9bb6feaebc610fa074f7c5e496e6962dea5873348077/ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33", size = 39031, upload-time = "2026-01-18T20:55:28.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/fa/a91f70829ebccf6387c4946e0a1a109f6ba0d6a28d65f628bedfad94b890/ormsgpack-1.12.2-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c1429217f8f4d7fcb053523bbbac6bed5e981af0b85ba616e6df7cce53c19657", size = 378262, upload-time = "2026-01-18T20:55:22.284Z" }, + { url = "https://files.pythonhosted.org/packages/5f/62/3698a9a0c487252b5c6a91926e5654e79e665708ea61f67a8bdeceb022bf/ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f13034dc6c84a6280c6c33db7ac420253852ea233fc3ee27c8875f8dd651163", size = 203034, upload-time = "2026-01-18T20:55:53.324Z" }, + { url = "https://files.pythonhosted.org/packages/66/3a/f716f64edc4aec2744e817660b317e2f9bb8de372338a95a96198efa1ac1/ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:59f5da97000c12bc2d50e988bdc8576b21f6ab4e608489879d35b2c07a8ab51a", size = 210538, upload-time = "2026-01-18T20:55:20.097Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/a436be9ce27d693d4e19fa94900028067133779f09fc45776db3f689c822/ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e4459c3f27066beadb2b81ea48a076a417aafffff7df1d3c11c519190ed44f2", size = 212401, upload-time = "2026-01-18T20:55:46.447Z" }, + { url = "https://files.pythonhosted.org/packages/10/c5/cde98300fd33fee84ca71de4751b19aeeca675f0cf3c0ec4b043f40f3b76/ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a1c460655d7288407ffa09065e322a7231997c0d62ce914bf3a96ad2dc6dedd", size = 387080, upload-time = "2026-01-18T20:56:00.884Z" }, + { url = "https://files.pythonhosted.org/packages/6a/31/30bf445ef827546747c10889dd254b3d84f92b591300efe4979d792f4c41/ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:458e4568be13d311ef7d8877275e7ccbe06c0e01b39baaac874caaa0f46d826c", size = 482346, upload-time = "2026-01-18T20:55:39.831Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f5/e1745ddf4fa246c921b5ca253636c4c700ff768d78032f79171289159f6e/ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8cde5eaa6c6cbc8622db71e4a23de56828e3d876aeb6460ffbcb5b8aff91093b", size = 425178, upload-time = "2026-01-18T20:55:27.106Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a2/e6532ed7716aed03dede8df2d0d0d4150710c2122647d94b474147ccd891/ormsgpack-1.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:dc7a33be14c347893edbb1ceda89afbf14c467d593a5ee92c11de4f1666b4d4f", size = 117183, upload-time = "2026-01-18T20:55:55.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/08/8b68f24b18e69d92238aa8f258218e6dfeacf4381d9d07ab8df303f524a9/ormsgpack-1.12.2-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bd5f4bf04c37888e864f08e740c5a573c4017f6fd6e99fa944c5c935fabf2dd9", size = 378266, upload-time = "2026-01-18T20:55:59.876Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/29fc13044ecb7c153523ae0a1972269fcd613650d1fa1a9cec1044c6b666/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d5b28b3570e9fed9a5a76528fc7230c3c76333bc214798958e58e9b79cc18a", size = 203035, upload-time = "2026-01-18T20:55:30.59Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c2/00169fb25dd8f9213f5e8a549dfb73e4d592009ebc85fbbcd3e1dcac575b/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3708693412c28f3538fb5a65da93787b6bbab3484f6bc6e935bfb77a62400ae5", size = 210539, upload-time = "2026-01-18T20:55:48.569Z" }, + { url = "https://files.pythonhosted.org/packages/1b/33/543627f323ff3c73091f51d6a20db28a1a33531af30873ea90c5ac95a9b5/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43013a3f3e2e902e1d05e72c0f1aeb5bedbb8e09240b51e26792a3c89267e181", size = 212401, upload-time = "2026-01-18T20:56:10.101Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5d/f70e2c3da414f46186659d24745483757bcc9adccb481a6eb93e2b729301/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7c8b1667a72cbba74f0ae7ecf3105a5e01304620ed14528b2cb4320679d2869b", size = 387082, upload-time = "2026-01-18T20:56:12.047Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d6/06e8dc920c7903e051f30934d874d4afccc9bb1c09dcaf0bc03a7de4b343/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:df6961442140193e517303d0b5d7bc2e20e69a879c2d774316125350c4a76b92", size = 482346, upload-time = "2026-01-18T20:56:05.152Z" }, + { url = "https://files.pythonhosted.org/packages/66/c4/f337ac0905eed9c393ef990c54565cd33644918e0a8031fe48c098c71dbf/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6a4c34ddef109647c769d69be65fa1de7a6022b02ad45546a69b3216573eb4a", size = 425181, upload-time = "2026-01-18T20:55:37.83Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/6d5758fabef3babdf4bbbc453738cc7de9cd3334e4c38dd5737e27b85653/ormsgpack-1.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:73670ed0375ecc303858e3613f407628dd1fca18fe6ac57b7b7ce66cc7bb006c", size = 117182, upload-time = "2026-01-18T20:55:31.472Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/17a15549233c37e7fd054c48fe9207492e06b026dbd872b826a0b5f833b6/ormsgpack-1.12.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2be829954434e33601ae5da328cccce3266b098927ca7a30246a0baec2ce7bd", size = 111464, upload-time = "2026-01-18T20:55:38.811Z" }, + { url = "https://files.pythonhosted.org/packages/4c/36/16c4b1921c308a92cef3bf6663226ae283395aa0ff6e154f925c32e91ff5/ormsgpack-1.12.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7", size = 378618, upload-time = "2026-01-18T20:55:50.835Z" }, + { url = "https://files.pythonhosted.org/packages/c0/68/468de634079615abf66ed13bb5c34ff71da237213f29294363beeeca5306/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d", size = 203186, upload-time = "2026-01-18T20:56:11.163Z" }, + { url = "https://files.pythonhosted.org/packages/73/a9/d756e01961442688b7939bacd87ce13bfad7d26ce24f910f6028178b2cc8/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e", size = 210738, upload-time = "2026-01-18T20:56:09.181Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ba/795b1036888542c9113269a3f5690ab53dd2258c6fb17676ac4bd44fcf94/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc", size = 212569, upload-time = "2026-01-18T20:56:06.135Z" }, + { url = "https://files.pythonhosted.org/packages/6c/aa/bff73c57497b9e0cba8837c7e4bcab584b1a6dbc91a5dd5526784a5030c8/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e", size = 387166, upload-time = "2026-01-18T20:55:36.738Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cf/f8283cba44bcb7b14f97b6274d449db276b3a86589bdb363169b51bc12de/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6", size = 482498, upload-time = "2026-01-18T20:55:29.626Z" }, + { url = "https://files.pythonhosted.org/packages/05/be/71e37b852d723dfcbe952ad04178c030df60d6b78eba26bfd14c9a40575e/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd", size = 425518, upload-time = "2026-01-18T20:55:49.556Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0c/9803aa883d18c7ef197213cd2cbf73ba76472a11fe100fb7dab2884edf48/ormsgpack-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4", size = 117462, upload-time = "2026-01-18T20:55:47.726Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9e/029e898298b2cc662f10d7a15652a53e3b525b1e7f07e21fef8536a09bb8/ormsgpack-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6", size = 111559, upload-time = "2026-01-18T20:55:54.273Z" }, + { url = "https://files.pythonhosted.org/packages/eb/29/bb0eba3288c0449efbb013e9c6f58aea79cf5cb9ee1921f8865f04c1a9d7/ormsgpack-1.12.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355", size = 378661, upload-time = "2026-01-18T20:55:57.765Z" }, + { url = "https://files.pythonhosted.org/packages/6e/31/5efa31346affdac489acade2926989e019e8ca98129658a183e3add7af5e/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1", size = 203194, upload-time = "2026-01-18T20:56:08.252Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/d0087278beef833187e0167f8527235ebe6f6ffc2a143e9de12a98b1ce87/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172", size = 210778, upload-time = "2026-01-18T20:55:17.694Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a2/072343e1413d9443e5a252a8eb591c2d5b1bffbe5e7bfc78c069361b92eb/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d", size = 212592, upload-time = "2026-01-18T20:55:32.747Z" }, + { url = "https://files.pythonhosted.org/packages/a2/8b/a0da3b98a91d41187a63b02dda14267eefc2a74fcb43cc2701066cf1510e/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7", size = 387164, upload-time = "2026-01-18T20:55:40.853Z" }, + { url = "https://files.pythonhosted.org/packages/19/bb/6d226bc4cf9fc20d8eb1d976d027a3f7c3491e8f08289a2e76abe96a65f3/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685", size = 482516, upload-time = "2026-01-18T20:55:42.033Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f1/bb2c7223398543dedb3dbf8bb93aaa737b387de61c5feaad6f908841b782/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258", size = 425539, upload-time = "2026-01-18T20:55:24.727Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e8/0fb45f57a2ada1fed374f7494c8cd55e2f88ccd0ab0a669aa3468716bf5f/ormsgpack-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9", size = 117459, upload-time = "2026-01-18T20:55:56.876Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d4/0cfeea1e960d550a131001a7f38a5132c7ae3ebde4c82af1f364ccc5d904/ormsgpack-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709", size = 111577, upload-time = "2026-01-18T20:55:43.605Z" }, + { url = "https://files.pythonhosted.org/packages/94/16/24d18851334be09c25e87f74307c84950f18c324a4d3c0b41dabdbf19c29/ormsgpack-1.12.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bc68dd5915f4acf66ff2010ee47c8906dc1cf07399b16f4089f8c71733f6e36c", size = 378717, upload-time = "2026-01-18T20:55:26.164Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a2/88b9b56f83adae8032ac6a6fa7f080c65b3baf9b6b64fd3d37bd202991d4/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46d084427b4132553940070ad95107266656cb646ea9da4975f85cb1a6676553", size = 203183, upload-time = "2026-01-18T20:55:18.815Z" }, + { url = "https://files.pythonhosted.org/packages/a9/80/43e4555963bf602e5bdc79cbc8debd8b6d5456c00d2504df9775e74b450b/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c010da16235806cf1d7bc4c96bf286bfa91c686853395a299b3ddb49499a3e13", size = 210814, upload-time = "2026-01-18T20:55:33.973Z" }, + { url = "https://files.pythonhosted.org/packages/78/e1/7cfbf28de8bca6efe7e525b329c31277d1b64ce08dcba723971c241a9d60/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18867233df592c997154ff942a6503df274b5ac1765215bceba7a231bea2745d", size = 212634, upload-time = "2026-01-18T20:55:28.634Z" }, + { url = "https://files.pythonhosted.org/packages/95/f8/30ae5716e88d792a4e879debee195653c26ddd3964c968594ddef0a3cc7e/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b009049086ddc6b8f80c76b3955df1aa22a5fbd7673c525cd63bf91f23122ede", size = 387139, upload-time = "2026-01-18T20:56:02.013Z" }, + { url = "https://files.pythonhosted.org/packages/dc/81/aee5b18a3e3a0e52f718b37ab4b8af6fae0d9d6a65103036a90c2a8ffb5d/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1dcc17d92b6390d4f18f937cf0b99054824a7815818012ddca925d6e01c2e49e", size = 482578, upload-time = "2026-01-18T20:55:35.117Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/71c9ba472d5d45f7546317f467a5fc941929cd68fb32796ca3d13dcbaec2/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f04b5e896d510b07c0ad733d7fce2d44b260c5e6c402d272128f8941984e4285", size = 425539, upload-time = "2026-01-18T20:56:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a6/ac99cd7fe77e822fed5250ff4b86fa66dd4238937dd178d2299f10b69816/ormsgpack-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:ae3aba7eed4ca7cb79fd3436eddd29140f17ea254b91604aa1eb19bfcedb990f", size = 117493, upload-time = "2026-01-18T20:56:07.343Z" }, + { url = "https://files.pythonhosted.org/packages/3a/67/339872846a1ae4592535385a1c1f93614138566d7af094200c9c3b45d1e5/ormsgpack-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:118576ea6006893aea811b17429bfc561b4778fad393f5f538c84af70b01260c", size = 111579, upload-time = "2026-01-18T20:55:21.161Z" }, + { url = "https://files.pythonhosted.org/packages/49/c2/6feb972dc87285ad381749d3882d8aecbde9f6ecf908dd717d33d66df095/ormsgpack-1.12.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7121b3d355d3858781dc40dafe25a32ff8a8242b9d80c692fd548a4b1f7fd3c8", size = 378721, upload-time = "2026-01-18T20:55:52.12Z" }, + { url = "https://files.pythonhosted.org/packages/a3/9a/900a6b9b413e0f8a471cf07830f9cf65939af039a362204b36bd5b581d8b/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee766d2e78251b7a63daf1cddfac36a73562d3ddef68cacfb41b2af64698033", size = 203170, upload-time = "2026-01-18T20:55:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/87/4c/27a95466354606b256f24fad464d7c97ab62bce6cc529dd4673e1179b8fb/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292410a7d23de9b40444636b9b8f1e4e4b814af7f1ef476e44887e52a123f09d", size = 212816, upload-time = "2026-01-18T20:55:23.501Z" }, + { url = "https://files.pythonhosted.org/packages/73/cd/29cee6007bddf7a834e6cd6f536754c0535fcb939d384f0f37a38b1cddb8/ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2", size = 117232, upload-time = "2026-01-18T20:55:45.448Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -2315,70 +2497,61 @@ wheels = [ [[package]] name = "websockets" -version = "16.0" +version = "15.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/74/221f58decd852f4b59cc3354cccaf87e8ef695fede361d03dc9a7396573b/websockets-16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04cdd5d2d1dacbad0a7bf36ccbcd3ccd5a30ee188f2560b7a62a30d14107b31a", size = 177343, upload-time = "2026-01-10T09:22:21.28Z" }, - { url = "https://files.pythonhosted.org/packages/19/0f/22ef6107ee52ab7f0b710d55d36f5a5d3ef19e8a205541a6d7ffa7994e5a/websockets-16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ff32bb86522a9e5e31439a58addbb0166f0204d64066fb955265c4e214160f0", size = 175021, upload-time = "2026-01-10T09:22:22.696Z" }, - { url = "https://files.pythonhosted.org/packages/10/40/904a4cb30d9b61c0e278899bf36342e9b0208eb3c470324a9ecbaac2a30f/websockets-16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:583b7c42688636f930688d712885cf1531326ee05effd982028212ccc13e5957", size = 175320, upload-time = "2026-01-10T09:22:23.94Z" }, - { url = "https://files.pythonhosted.org/packages/9d/2f/4b3ca7e106bc608744b1cdae041e005e446124bebb037b18799c2d356864/websockets-16.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7d837379b647c0c4c2355c2499723f82f1635fd2c26510e1f587d89bc2199e72", size = 183815, upload-time = "2026-01-10T09:22:25.469Z" }, - { url = "https://files.pythonhosted.org/packages/86/26/d40eaa2a46d4302becec8d15b0fc5e45bdde05191e7628405a19cf491ccd/websockets-16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df57afc692e517a85e65b72e165356ed1df12386ecb879ad5693be08fac65dde", size = 185054, upload-time = "2026-01-10T09:22:27.101Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ba/6500a0efc94f7373ee8fefa8c271acdfd4dca8bd49a90d4be7ccabfc397e/websockets-16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2b9f1e0d69bc60a4a87349d50c09a037a2607918746f07de04df9e43252c77a3", size = 184565, upload-time = "2026-01-10T09:22:28.293Z" }, - { url = "https://files.pythonhosted.org/packages/04/b4/96bf2cee7c8d8102389374a2616200574f5f01128d1082f44102140344cc/websockets-16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:335c23addf3d5e6a8633f9f8eda77efad001671e80b95c491dd0924587ece0b3", size = 183848, upload-time = "2026-01-10T09:22:30.394Z" }, - { url = "https://files.pythonhosted.org/packages/02/8e/81f40fb00fd125357814e8c3025738fc4ffc3da4b6b4a4472a82ba304b41/websockets-16.0-cp310-cp310-win32.whl", hash = "sha256:37b31c1623c6605e4c00d466c9d633f9b812ea430c11c8a278774a1fde1acfa9", size = 178249, upload-time = "2026-01-10T09:22:32.083Z" }, - { url = "https://files.pythonhosted.org/packages/b4/5f/7e40efe8df57db9b91c88a43690ac66f7b7aa73a11aa6a66b927e44f26fa/websockets-16.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e1dab317b6e77424356e11e99a432b7cb2f3ec8c5ab4dabbcee6add48f72b35", size = 178685, upload-time = "2026-01-10T09:22:33.345Z" }, - { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, - { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, - { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, - { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, - { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, - { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, - { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, - { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, - { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, - { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, - { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, - { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, - { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, - { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, - { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, - { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, - { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, - { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, - { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, - { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, - { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, - { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, - { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, - { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, - { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, - { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, - { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, - { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, - { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, - { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, - { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, - { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, - { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, - { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, - { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, - { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, - { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, - { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, + { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, + { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, + { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, + { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] [[package]]