From 8f8e76d488f36a2db6bb43a0a68d91c31458bba5 Mon Sep 17 00:00:00 2001 From: Cintu07 Date: Fri, 11 Sep 2026 08:42:35 +0000 Subject: [PATCH] fix(openai-sdk-python): run the requests fallback off the event loop supermemory_profile_search is async, but without the optional aiohttp extra it falls back to requests.post, which blocks whatever event loop awaited it for the whole request. aiohttp is not installed by default, so on a plain pip install every memory search on the async path stalls the caller's loop. The fallback now runs requests.post through asyncio.to_thread. The new test forces the fallback and fails if the request holds the loop. --- .../src/supermemory_openai/middleware.py | 6 ++- .../tests/test_middleware.py | 38 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/openai-sdk-python/src/supermemory_openai/middleware.py b/packages/openai-sdk-python/src/supermemory_openai/middleware.py index 676aad35..bd0a784b 100644 --- a/packages/openai-sdk-python/src/supermemory_openai/middleware.py +++ b/packages/openai-sdk-python/src/supermemory_openai/middleware.py @@ -227,10 +227,12 @@ async def supermemory_profile_search( return SupermemoryProfileSearch(data) except ImportError: - # Fallback to requests if aiohttp not available + # Fallback to requests if aiohttp not available. requests blocks, so it + # runs on a worker thread to keep the caller's event loop free. import requests - response = requests.post( + response = await asyncio.to_thread( + requests.post, profile_url, headers={ "Content-Type": "application/json", diff --git a/packages/openai-sdk-python/tests/test_middleware.py b/packages/openai-sdk-python/tests/test_middleware.py index cec2cb6e..9045fe0a 100644 --- a/packages/openai-sdk-python/tests/test_middleware.py +++ b/packages/openai-sdk-python/tests/test_middleware.py @@ -1,8 +1,11 @@ """Tests for middleware module.""" import os +import sys import pytest +import requests import asyncio +import threading from unittest.mock import AsyncMock, Mock, patch, MagicMock from typing import Dict, Any @@ -26,6 +29,7 @@ except ImportError: SupermemoryOpenAIWrapper, ) +from supermemory_openai.middleware import supermemory_profile_search from openai import OpenAI, AsyncOpenAI from openai.types.chat import ChatCompletion, ChatCompletionMessage from openai.types import CompletionUsage @@ -812,3 +816,37 @@ class TestBackgroundTaskManagement: ) # Should complete without error + + +class TestProfileSearchFallback: + """Test the requests fallback used when aiohttp is not installed.""" + + @pytest.mark.asyncio + async def test_requests_fallback_does_not_block_event_loop(self): + """requests.post must run off the event loop so other tasks keep running.""" + released = threading.Event() + + def slow_post(*args, **kwargs): + # The test sets the event from the event loop, which it can only do + # while this call is running somewhere else. + if not released.wait(timeout=2): + raise AssertionError("requests.post blocked the event loop") + response = Mock(status_code=200) + response.json.return_value = { + "profile": {"static": ["Prefers vegetarian food"], "dynamic": []}, + "searchResults": {"results": []}, + } + return response + + with patch.dict(sys.modules, {"aiohttp": None}): + with patch.object(requests, "post", side_effect=slow_post): + search = asyncio.create_task( + supermemory_profile_search( + "user-123", "", "test-key", "https://api.supermemory.ai" + ) + ) + await asyncio.sleep(0) + released.set() + result = await search + + assert result.profile["static"] == ["Prefers vegetarian food"]