From 6d43ff4b7ebf5735f7c9558bd92b0490272587ca Mon Sep 17 00:00:00 2001 From: grandcamel Date: Wed, 12 Aug 2026 14:58:57 -0500 Subject: [PATCH] fix(tests): unbreak suite collection and restore the ruff gates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the pyupgrade sweep (94e0386), test_search_service.py's module import crashes during collection: search_service.py has no future annotations import, so `credentials: HTTPBasicCredentials | None` evaluates eagerly, and under the test's sys.modules stubs that is None | None -> TypeError. The crash lands before the test file's restore block, so the fastapi/numpy/nltk stubs stay installed for the whole pytest session and every later FastAPI-touching test fails with FastAPI=None (95 failed + 9 errors on this checkout; 10 environmental failures after the fix). from __future__ import annotations makes the module's annotations lazy again — no runtime change; the existing test file is itself the regression test. Also restores the LINT/FMT gates, which currently fail on main: - ai_chat/__init__.py: sort __all__ (RUF022) - search/document_processor.py: fold nested if into elif (SIM102) - ruff format over ai_chat/gateway.py and core/function_result.py Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MgA3KeCEPMKMJVvroZY1wV --- signalwire/signalwire/ai_chat/__init__.py | 4 ++-- signalwire/signalwire/ai_chat/gateway.py | 19 ++++++++++++------- signalwire/signalwire/core/function_result.py | 4 +--- .../signalwire/search/document_processor.py | 11 +++++++---- .../signalwire/search/search_service.py | 2 ++ 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/signalwire/signalwire/ai_chat/__init__.py b/signalwire/signalwire/ai_chat/__init__.py index 73542767..ad0671a1 100644 --- a/signalwire/signalwire/ai_chat/__init__.py +++ b/signalwire/signalwire/ai_chat/__init__.py @@ -26,15 +26,15 @@ __all__ = [ "AIChatClient", - "ChatGateway", - "GatewayRejection", "AIChatError", "AuthenticationError", + "ChatGateway", "ChatInProgressError", "ChatLog", "ChatResponse", "ConversationInfo", "ConversationNotFoundError", + "GatewayRejection", "RateLimitError", "SummaryError", ] diff --git a/signalwire/signalwire/ai_chat/gateway.py b/signalwire/signalwire/ai_chat/gateway.py index e2f4f5f4..79128112 100644 --- a/signalwire/signalwire/ai_chat/gateway.py +++ b/signalwire/signalwire/ai_chat/gateway.py @@ -73,8 +73,8 @@ SERVICE_DEFAULT_CONVERSATION_TIMEOUT = 3600 # Caps chosen to be invisible to a real conversation and ruinous to a script. -DEFAULT_MAX_NEW_CONVERSATIONS = 60 # per window, per gateway -DEFAULT_MAX_TURNS = 200 # per conversation, ever +DEFAULT_MAX_NEW_CONVERSATIONS = 60 # per window, per gateway +DEFAULT_MAX_TURNS = 200 # per conversation, ever DEFAULT_WINDOW_SECONDS = 60 # Hosts that never need listing, so `pip install` → run → it works. @@ -160,8 +160,10 @@ def __init__( raise ValueError("config_url is required — it is what a key is scoped to.") self.config_url = config_url - self.key = key or os.environ.get("SIGNALWIRE_CHAT_GATEWAY_KEY") or ( - "pk_" + secrets.token_urlsafe(24) + self.key = ( + key + or os.environ.get("SIGNALWIRE_CHAT_GATEWAY_KEY") + or ("pk_" + secrets.token_urlsafe(24)) ) self.allowed_origins = {o.rstrip("/") for o in allowed_origins} self.handle_ttl = handle_ttl @@ -174,7 +176,9 @@ def __init__( self._owns_client = client is None if secret is None: - secret = os.environ.get("SIGNALWIRE_CHAT_GATEWAY_SECRET") or secrets.token_bytes(32) + secret = os.environ.get( + "SIGNALWIRE_CHAT_GATEWAY_SECRET" + ) or secrets.token_bytes(32) self._secret = secret.encode() if isinstance(secret, str) else secret self._mints: list[float] = [] @@ -341,8 +345,9 @@ def _charge_turn(self, conversation_id: str) -> None: # ── The proxied call ───────────────────────────────────────────── - def prepare(self, body: dict[str, Any], *, origin: str | None, - key: str | None) -> tuple[str, dict[str, Any], str | None]: + def prepare( + self, body: dict[str, Any], *, origin: str | None, key: str | None + ) -> tuple[str, dict[str, Any], str | None]: """Validate a browser request and build the upstream JSON-RPC call. Returns ``(method, params, minted_handle)`` — ``minted_handle`` is set diff --git a/signalwire/signalwire/core/function_result.py b/signalwire/signalwire/core/function_result.py index 23f3d286..72480d90 100644 --- a/signalwire/signalwire/core/function_result.py +++ b/signalwire/signalwire/core/function_result.py @@ -567,9 +567,7 @@ def hold( timeout, prompt = prompt, None if prompt is not None: - self.set_tool_response( - tool_result="status: on hold", tool_prompt=prompt - ) + self.set_tool_response(tool_result="status: on hold", tool_prompt=prompt) self.post_process = True # Clamp timeout to valid range diff --git a/signalwire/signalwire/search/document_processor.py b/signalwire/signalwire/search/document_processor.py index f8f9387b..2e95958f 100644 --- a/signalwire/signalwire/search/document_processor.py +++ b/signalwire/signalwire/search/document_processor.py @@ -1745,11 +1745,14 @@ def _chunk_from_json( # A new topic: remember its heading, and leave it alone - # it already names itself. current_heading = stripped.split("\n", 1)[0].strip() - elif current_heading and stripped.startswith("#"): + elif ( + current_heading + and stripped.startswith("#") + and current_heading.lower() not in chunk_text.lower() + ): # A subsection of that topic: give it the subject back. - if current_heading.lower() not in chunk_text.lower(): - chunk_text = f"{current_heading}\n\n{chunk_text}" - metadata["heading_context"] = current_heading + chunk_text = f"{current_heading}\n\n{chunk_text}" + metadata["heading_context"] = current_heading chunk = self._create_chunk( content=chunk_text, diff --git a/signalwire/signalwire/search/search_service.py b/signalwire/signalwire/search/search_service.py index d7193718..c54a320e 100644 --- a/signalwire/signalwire/search/search_service.py +++ b/signalwire/signalwire/search/search_service.py @@ -7,6 +7,8 @@ See LICENSE file in the project root for full license information. """ +from __future__ import annotations + import hashlib import json from collections.abc import Awaitable, Callable