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.
This commit is contained in:
Cintu07 2026-09-11 08:42:35 +00:00
parent 958ae8b619
commit 8f8e76d488
2 changed files with 42 additions and 2 deletions

View file

@ -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",

View file

@ -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"]