Skip to content

Commit b05403c

Browse files
committed
Re-write trio-to-asyncio case to use aio_as_trio.
1 parent 06e856b commit b05403c

9 files changed

Lines changed: 272 additions & 107 deletions

File tree

tests/aiotest/test_callback.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from tests import aiotest
22
import signal
33
import pytest
4+
from .. import utils as test_utils
45

56

67
class TestCallback(aiotest.TestCase):
@@ -70,7 +71,10 @@ def test():
7071
with pytest.raises(RuntimeError, match='Event loop is closed'):
7172
loop.run_in_executor(None, func)
7273
with pytest.raises(RuntimeError, match='Event loop is closed'):
73-
await loop.run_coroutine(coro)
74+
await loop.run_aio_coroutine(coro)
75+
with test_utils.deprecate(self):
76+
with pytest.raises(RuntimeError, match='Event loop is closed'):
77+
await loop.run_coroutine(coro)
7478
with pytest.raises(RuntimeError, match='Event loop is closed'):
7579
loop.add_signal_handler(signal.SIGTERM, func)
7680
finally:

tests/aiotest/test_coroutine.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from tests import aiotest
22
import pytest
3+
from .. import utils as test_utils
34

45

56
async def hello_world(asyncio, result, delay, loop):
@@ -15,7 +16,15 @@ class TestCoroutine(aiotest.TestCase):
1516
async def test_hello_world(self, loop, config):
1617
result = []
1718
coro = hello_world(config.asyncio, result, 0.001, loop)
18-
await loop.run_coroutine(config.asyncio.ensure_future(coro, loop=loop))
19+
await loop.run_aio_coroutine(config.asyncio.ensure_future(coro, loop=loop))
20+
assert result == ['Hello', 'World']
21+
22+
@pytest.mark.trio
23+
async def test_hello_world_depr(self, loop, config):
24+
result = []
25+
coro = hello_world(config.asyncio, result, 0.001, loop)
26+
with test_utils.deprecate(self):
27+
await loop.run_coroutine(config.asyncio.ensure_future(coro, loop=loop))
1928
assert result == ['Hello', 'World']
2029

2130
@pytest.mark.trio

tests/aiotest/test_thread.py

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pytest
33
import trio
44
import trio_asyncio
5+
from .. import utils as test_utils
56

67

78
class TestThread(aiotest.TestCase):
@@ -19,7 +20,29 @@ def work():
1920
result['ident'] = get_ident()
2021

2122
fut = loop.run_in_executor(None, work)
22-
await loop.run_coroutine(fut)
23+
await loop.run_aio_coroutine(fut)
24+
25+
# ensure that work() was executed in a different thread
26+
work_ident = result['ident']
27+
assert work_ident is not None
28+
assert work_ident != get_ident()
29+
30+
@pytest.mark.trio
31+
async def test_ident_depr(self, loop, config):
32+
threading = config.threading
33+
try:
34+
get_ident = threading.get_ident # Python 3
35+
except AttributeError:
36+
get_ident = threading._get_ident # Python 2
37+
38+
result = {'ident': None}
39+
40+
def work():
41+
result['ident'] = get_ident()
42+
43+
fut = loop.run_in_executor(None, work)
44+
with test_utils.deprecate(self):
45+
await loop.run_coroutine(fut)
2346

2447
# ensure that work() was executed in a different thread
2548
work_ident = result['ident']
@@ -34,12 +57,30 @@ def work():
3457
result.append("run")
3558

3659
fut = loop.run_in_executor(None, work)
37-
await loop.run_future(fut)
60+
await loop.run_aio_future(fut)
61+
assert result == ["run"]
62+
63+
# ensure that run_in_executor() can be called twice
64+
fut = loop.run_in_executor(None, work)
65+
await loop.run_aio_future(fut)
66+
assert result == ["run", "run"]
67+
68+
@pytest.mark.trio
69+
async def test_run_twice_depr(self, loop):
70+
result = []
71+
72+
def work():
73+
result.append("run")
74+
75+
fut = loop.run_in_executor(None, work)
76+
with test_utils.deprecate(self):
77+
await loop.run_future(fut)
3878
assert result == ["run"]
3979

4080
# ensure that run_in_executor() can be called twice
4181
fut = loop.run_in_executor(None, work)
42-
await loop.run_future(fut)
82+
with test_utils.deprecate(self):
83+
await loop.run_future(fut)
4384
assert result == ["run", "run"]
4485

4586
@pytest.mark.trio
@@ -55,7 +96,24 @@ def work():
5596

5697
# get_event_loop() must return None in a different thread
5798
fut = loop.run_in_executor(None, work)
58-
await loop.run_future(fut)
99+
await loop.run_aio_future(fut)
100+
assert isinstance(result['loop'], (AssertionError, RuntimeError))
101+
102+
@pytest.mark.trio
103+
async def test_policy_depr(self, loop, config):
104+
asyncio = config.asyncio
105+
result = {'loop': 'not set'} # sentinel, different than None
106+
107+
def work():
108+
try:
109+
result['loop'] = asyncio.get_event_loop()
110+
except Exception as exc:
111+
result['loop'] = exc
112+
113+
# get_event_loop() must return None in a different thread
114+
fut = loop.run_in_executor(None, work)
115+
with test_utils.deprecate(self):
116+
await loop.run_future(fut)
59117
assert isinstance(result['loop'], (AssertionError, RuntimeError))
60118

61119
@pytest.mark.trio

tests/interop/test_adapter.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
from trio_asyncio import aio_as_trio
23
from trio_asyncio import aio2trio, trio2aio
34
import asyncio
45
import trio
@@ -22,6 +23,14 @@ async def dly_trio(self):
2223
return 8
2324

2425
@trio2aio
26+
async def dly_asyncio_depr(self):
27+
if sys.version_info >= (3, 7):
28+
assert sniffio.current_async_library() == "asyncio"
29+
await asyncio.sleep(0.01, loop=self.loop)
30+
self.flag |= 1
31+
return 4
32+
33+
@aio_as_trio
2534
async def dly_asyncio(self):
2635
if sys.version_info >= (3, 7):
2736
assert sniffio.current_async_library() == "asyncio"
@@ -46,3 +55,11 @@ async def test_trio_asyncio(self, loop):
4655
res = await sth.dly_asyncio()
4756
assert res == 4
4857
assert sth.flag == 1
58+
59+
@pytest.mark.trio
60+
async def test_trio_asyncio_depr(self, loop):
61+
sth = SomeThing(loop)
62+
res = await sth.dly_asyncio_depr()
63+
assert res == 4
64+
assert sth.flag == 1
65+

tests/interop/test_calls.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
import asyncio
33
import trio
44
import sniffio
5+
from trio_asyncio import aio_as_trio
56
from tests import aiotest
67
from functools import partial
78
import sys
8-
from .. import utils
9+
from .. import utils as test_utils
910

1011

1112
class Seen:
@@ -307,7 +308,7 @@ async def dly_asyncio():
307308
await asyncio.sleep(0.01, loop=loop)
308309
yield 2
309310

310-
with utils.deprecate(self):
311+
with test_utils.deprecate(self):
311312
res = await async_gen_to_list(loop.wrap_generator(dly_asyncio))
312313
assert res == [1, 2]
313314

@@ -318,7 +319,7 @@ async def dly_asyncio():
318319
raise RuntimeError("I has an owie")
319320
yield 2
320321

321-
with utils.deprecate(self):
322+
with test_utils.deprecate(self):
322323
with pytest.raises(RuntimeError) as err:
323324
await async_gen_to_list(loop.wrap_generator(dly_asyncio))
324325
assert err.value.args[0] == "I has an owie"
@@ -337,7 +338,7 @@ async def cancel_soon(nursery):
337338
hold = asyncio.Event(loop=loop)
338339
seen = Seen()
339340

340-
with utils.deprecate(self):
341+
with test_utils.deprecate(self):
341342
async with trio.open_nursery() as nursery:
342343
nursery.start_soon(async_gen_to_list, loop.wrap_generator(dly_asyncio, hold, seen))
343344
nursery.start_soon(cancel_soon, nursery)
@@ -352,6 +353,19 @@ async def slow_nums():
352353
yield n
353354

354355
sum = 0
356+
async for n in aio_as_trio(slow_nums()):
357+
sum += n
358+
assert sum == 15
359+
360+
@pytest.mark.trio
361+
async def test_trio_asyncio_iterator_depr(self, loop):
362+
async def slow_nums():
363+
for n in range(1, 6):
364+
asyncio.sleep(0.01, loop=loop)
365+
yield n
366+
367+
sum = 0
368+
# with test_utils.deprecate(self): ## not yet
355369
async for n in loop.run_asyncio(slow_nums()):
356370
sum += n
357371
assert sum == 15

trio_asyncio/adapter.py

Lines changed: 82 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,104 @@
22
# Trio.
33

44
import types
5+
import warnings
6+
57
from async_generator import isasyncgenfunction
68
import asyncio
79
import trio_asyncio
8-
from contextvars import copy_context
10+
from contextvars import ContextVar
11+
12+
from .util import run_aio_generator, run_aio_future
13+
14+
current_loop = ContextVar('trio_aio_loop', default=None)
15+
current_policy = ContextVar('trio_aio_policy', default=None)
916

1017
# import logging
1118
# logger = logging.getLogger(__name__)
1219

1320
from functools import wraps, partial
1421

15-
__all__ = ['trio2aio', 'aio2trio', 'allow_asyncio']
22+
__all__ = ['trio2aio', 'aio2trio', 'aio_as_trio', 'allow_asyncio',
23+
'current_loop', 'current_policy']
1624

1725

1826
def trio2aio(proc):
19-
if isasyncgenfunction(proc):
27+
"""Call asyncio code from Trio.
2028
21-
@wraps(proc)
22-
def call(*args, **kwargs):
23-
proc_ = proc
24-
if kwargs:
25-
proc_ = partial(proc_, **kwargs)
26-
return trio_asyncio.wrap_generator(proc_, *args)
29+
Deprecated: Use aio_as_trio() instead.
2730
28-
else:
31+
await loop.run_iterator(iter)
2932
30-
@wraps(proc)
31-
async def call(*args, **kwargs):
32-
proc_ = proc
33-
if kwargs:
34-
proc_ = partial(proc_, **kwargs)
35-
return await trio_asyncio.run_asyncio(proc_, *args)
33+
simply call
3634
37-
return call
35+
await loop.run_asyncio(iter)
36+
"""
37+
warnings.warn("Use 'aio_as_trio(proc)' instead'", DeprecationWarning)
38+
39+
return aio_as_trio(proc)
40+
41+
class Asyncio_Trio_Wrapper:
42+
"""
43+
This wrapper object encapsulates an asyncio-style coroutine,
44+
generator, or iterator, to be called seamlessly from Trio.
45+
"""
46+
def __init__(self, proc, args=[], loop=None):
47+
self.proc = proc
48+
self.args = args
49+
self._loop = loop
50+
51+
@property
52+
def loop(self):
53+
"""The loop argument needs to be lazily evaluated."""
54+
loop = self._loop
55+
if loop is None:
56+
loop = current_loop.get()
57+
return loop
58+
59+
def __get__(self, obj, cls):
60+
"""If this is used to decorate an instance,
61+
we need to forward the original ``self`` to the wrapped method.
62+
"""
63+
if obj is None:
64+
return self.__call__
65+
return partial(self.__call__, obj)
66+
67+
async def __call__(self, *args, **kwargs):
68+
if self.args:
69+
"Call 'aio_as_trio(oroc)(*args)', not 'aio_as_trio(proc, *args)'"
70+
71+
f = self.proc(*args, **kwargs)
72+
return await self.loop.run_aio_coroutine(f)
73+
74+
def __await__(self):
75+
"""Compatbility code for loop.run_asyncio"""
76+
f = self.proc(*self.args)
77+
return self.loop.run_aio_coroutine(f).__await__()
78+
79+
def __aenter__(self):
80+
proc_enter = getattr(self.proc, "__aenter__", None)
81+
if proc_enter is None or self.args:
82+
raise RuntimeError(
83+
"Call 'aio_as_trio(ctxfactory(*args))', not 'aio_as_trio(ctxfactory, *args)'"
84+
)
85+
f = proc_enter()
86+
return self.loop.run_aio_coroutine(f)
87+
88+
def __aexit__(self, *tb):
89+
f = self.proc.__aexit__(*tb)
90+
return self.loop.run_aio_coroutine(f)
91+
92+
def __aiter__(self):
93+
proc_iter = getattr(self.proc, "__anext__", None)
94+
if proc_iter is None or self.args:
95+
raise RuntimeError(
96+
"Call 'run_asyncio(gen(*args))', not 'run_asyncio(gen, *args)'"
97+
)
98+
return run_aio_generator(self.loop, self.proc)
99+
100+
101+
def aio_as_trio(proc, loop=None):
102+
return Asyncio_Trio_Wrapper(proc, loop=loop)
38103

39104

40105
def aio2trio(proc):

0 commit comments

Comments
 (0)