Skip to content

Commit d193bab

Browse files
authored
fix: detect uvloop and skip nest_asyncio to prevent patching errors (#2369)
## Issue Link / Problem Description <!-- Link to related issue or describe the problem this PR solves --> - Fixes #2357
1 parent dd1b2ac commit d193bab

File tree

2 files changed

+136
-12
lines changed

2 files changed

+136
-12
lines changed

src/ragas/async_utils.py

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,40 @@ def is_event_loop_running() -> bool:
1919
return loop.is_running()
2020

2121

22-
def apply_nest_asyncio():
23-
"""Apply nest_asyncio if an event loop is running."""
24-
if is_event_loop_running():
25-
# an event loop is running so call nested_asyncio to fix this
26-
try:
27-
import nest_asyncio
28-
except ImportError:
29-
raise ImportError(
30-
"It seems like your running this in a jupyter-like environment. Please install nest_asyncio with `pip install nest_asyncio` to make it work."
22+
def apply_nest_asyncio() -> bool:
23+
"""
24+
Apply nest_asyncio if an event loop is running and compatible.
25+
26+
Returns:
27+
bool: True if nest_asyncio was applied, False if skipped
28+
"""
29+
if not is_event_loop_running():
30+
return False
31+
32+
try:
33+
import nest_asyncio
34+
except ImportError:
35+
raise ImportError(
36+
"It seems like your running this in a jupyter-like environment. Please install nest_asyncio with `pip install nest_asyncio` to make it work."
37+
)
38+
39+
try:
40+
loop = asyncio.get_running_loop()
41+
loop_type = type(loop).__name__
42+
43+
if "uvloop" in loop_type.lower() or "uvloop" in str(type(loop)):
44+
logger.debug(
45+
f"Skipping nest_asyncio.apply() for incompatible loop type: {loop_type}"
3146
)
47+
return False
3248

3349
nest_asyncio.apply()
50+
return True
51+
except ValueError as e:
52+
if "Can't patch loop of type" in str(e):
53+
logger.debug(f"Skipping nest_asyncio.apply(): {e}")
54+
return False
55+
raise
3456

3557

3658
def as_completed(
@@ -116,12 +138,22 @@ def run(
116138
Whether to apply nest_asyncio for Jupyter compatibility. Default is True.
117139
Set to False in production environments to avoid event loop patching.
118140
"""
119-
# Only apply nest_asyncio if explicitly allowed
141+
nest_asyncio_applied = False
120142
if allow_nest_asyncio:
121-
apply_nest_asyncio()
143+
nest_asyncio_applied = apply_nest_asyncio()
122144

123-
# Create the coroutine if it's a callable, otherwise use directly
124145
coro = async_func() if callable(async_func) else async_func
146+
147+
if is_event_loop_running() and not nest_asyncio_applied:
148+
loop = asyncio.get_running_loop()
149+
loop_type = type(loop).__name__
150+
raise RuntimeError(
151+
f"Cannot execute nested async code with {loop_type}. "
152+
f"uvloop does not support nested event loop execution. "
153+
f"Please use asyncio's standard event loop in Jupyter environments, "
154+
f"or refactor your code to avoid nested async calls."
155+
)
156+
125157
return asyncio.run(coro)
126158

127159

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""Test uvloop compatibility with nest_asyncio."""
2+
3+
import asyncio
4+
import sys
5+
6+
import pytest
7+
8+
9+
class TestUvloopCompatibility:
10+
"""Test that ragas works with uvloop event loops."""
11+
12+
@pytest.mark.skipif(sys.version_info < (3, 8), reason="uvloop requires Python 3.8+")
13+
def test_apply_nest_asyncio_with_uvloop_returns_false(self):
14+
"""Test that apply_nest_asyncio returns False with uvloop."""
15+
uvloop = pytest.importorskip("uvloop")
16+
17+
from ragas.async_utils import apply_nest_asyncio
18+
19+
async def test_func():
20+
result = apply_nest_asyncio()
21+
return result
22+
23+
uvloop.install()
24+
try:
25+
result = asyncio.run(test_func())
26+
assert result is False
27+
finally:
28+
asyncio.set_event_loop_policy(None)
29+
30+
@pytest.mark.skipif(sys.version_info < (3, 8), reason="uvloop requires Python 3.8+")
31+
def test_run_with_uvloop_and_running_loop(self):
32+
"""Test that run() raises clear error with uvloop in running event loop (Jupyter scenario)."""
33+
uvloop = pytest.importorskip("uvloop")
34+
35+
from ragas.async_utils import run
36+
37+
async def inner_task():
38+
return "success"
39+
40+
async def outer_task():
41+
with pytest.raises(RuntimeError, match="Cannot execute nested async code"):
42+
run(inner_task)
43+
44+
uvloop.install()
45+
try:
46+
asyncio.run(outer_task())
47+
finally:
48+
asyncio.set_event_loop_policy(None)
49+
50+
@pytest.mark.skipif(sys.version_info < (3, 8), reason="uvloop requires Python 3.8+")
51+
def test_run_async_tasks_with_uvloop(self):
52+
"""Test that run_async_tasks works with uvloop."""
53+
uvloop = pytest.importorskip("uvloop")
54+
55+
from ragas.async_utils import run_async_tasks
56+
57+
async def task(n):
58+
return n * 2
59+
60+
tasks = [task(i) for i in range(5)]
61+
62+
uvloop.install()
63+
try:
64+
results = run_async_tasks(tasks, show_progress=False)
65+
assert sorted(results) == [0, 2, 4, 6, 8]
66+
finally:
67+
asyncio.set_event_loop_policy(None)
68+
69+
def test_apply_nest_asyncio_without_uvloop_returns_true(self):
70+
"""Test that apply_nest_asyncio returns True with standard asyncio."""
71+
from ragas.async_utils import apply_nest_asyncio
72+
73+
async def test_func():
74+
result = apply_nest_asyncio()
75+
return result
76+
77+
result = asyncio.run(test_func())
78+
assert result is True
79+
80+
def test_run_with_standard_asyncio_and_running_loop(self):
81+
"""Test that run() works with standard asyncio in a running loop."""
82+
from ragas.async_utils import run
83+
84+
async def inner_task():
85+
return "nested_success"
86+
87+
async def outer_task():
88+
result = run(inner_task)
89+
return result
90+
91+
result = asyncio.run(outer_task())
92+
assert result == "nested_success"

0 commit comments

Comments
 (0)