Skip to content

Commit c959e6a

Browse files
authored
Merge pull request #2478 from jakkdl/drop_async_generator_dependency
Fixes #2458
2 parents 4ac9c22 + a58a58c commit c959e6a

10 files changed

Lines changed: 56 additions & 46 deletions

File tree

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@
8181
install_requires=[
8282
"attrs >= 19.2.0", # for eq
8383
"sortedcontainers",
84-
"async_generator >= 1.9",
8584
"idna",
8685
"outcome",
8786
"sniffio",

test-requirements.in

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# For tests
22
pytest >= 5.0 # for faulthandler in core
33
pytest-cov >= 2.6.0
4+
async_generator >= 1.9
45
# ipython 7.x is the last major version supporting Python 3.7
56
ipython < 7.32 # for the IPython traceback integration tests
67
pyOpenSSL >= 22.0.0 # for the ssl + DTLS tests
@@ -26,9 +27,7 @@ typing-extensions; implementation_name == "cpython"
2627
cffi; os_name == "nt"
2728
attrs >= 19.2.0
2829
sortedcontainers
29-
async_generator >= 1.9
3030
idna
3131
outcome
3232
sniffio
3333
exceptiongroup >= 1.0.0rc9; python_version < "3.11"
34-

trio/_core/_ki.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
import signal
33
import sys
44
from functools import wraps
5-
import attr
65

7-
import async_generator
6+
import attr
87

98
from .._util import is_main_thread
109

@@ -109,6 +108,14 @@ def currently_ki_protected():
109108
return ki_protection_enabled(sys._getframe())
110109

111110

111+
# This is to support the async_generator package necessary for aclosing on <3.10
112+
# functions decorated @async_generator are given this magic property that's a
113+
# reference to the object itself
114+
# see python-trio/async_generator/async_generator/_impl.py
115+
def legacy_isasyncgenfunction(obj):
116+
return getattr(obj, "_async_gen_function", None) == id(obj)
117+
118+
112119
def _ki_protection_decorator(enabled):
113120
def decorator(fn):
114121
# In some version of Python, isgeneratorfunction returns true for
@@ -141,7 +148,7 @@ def wrapper(*args, **kwargs):
141148
return gen
142149

143150
return wrapper
144-
elif async_generator.isasyncgenfunction(fn):
151+
elif inspect.isasyncgenfunction(fn) or legacy_isasyncgenfunction(fn):
145152

146153
@wraps(fn)
147154
def wrapper(*args, **kwargs):

trio/_core/tests/test_asyncgen.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import sys
22
import weakref
33
import pytest
4+
import contextlib
45
from math import inf
56
from functools import partial
6-
from async_generator import aclosing
7+
78
from ... import _core
89
from .tutil import gc_collect_harder, buggy_pypy_asyncgens, restore_unraisablehook
910

1011

12+
@pytest.mark.skipif(sys.version_info < (3, 10), reason="no aclosing() in stdlib<3.10")
1113
def test_asyncgen_basics():
1214
collected = []
1315

@@ -46,7 +48,7 @@ async def async_main():
4648
assert collected.pop() == "abandoned"
4749

4850
# aclosing() ensures it's cleaned up at point of use
49-
async with aclosing(example("exhausted 1")) as aiter:
51+
async with contextlib.aclosing(example("exhausted 1")) as aiter:
5052
assert 42 == await aiter.asend(None)
5153
assert collected.pop() == "exhausted 1"
5254

@@ -58,7 +60,7 @@ async def async_main():
5860
gc_collect_harder()
5961

6062
# No problems saving the geniter when using either of these patterns
61-
async with aclosing(example("exhausted 3")) as aiter:
63+
async with contextlib.aclosing(example("exhausted 3")) as aiter:
6264
saved.append(aiter)
6365
assert 42 == await aiter.asend(None)
6466
assert collected.pop() == "exhausted 3"

trio/_core/tests/test_ki.py

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@
66
import threading
77
import contextlib
88
import time
9+
import inspect
910

10-
from async_generator import (
11-
async_generator,
12-
yield_,
13-
isasyncgenfunction,
14-
asynccontextmanager,
15-
)
11+
try:
12+
from async_generator import yield_, async_generator
13+
except ImportError: # pragma: no cover
14+
async_generator = yield_ = None
1615

1716
from ... import _core
1817
from ...testing import wait_all_tasks_blocked
@@ -142,7 +141,8 @@ def protected_manager():
142141
raise KeyError
143142

144143

145-
async def test_agen_protection():
144+
@pytest.mark.skipif(async_generator is None, reason="async_generator not installed")
145+
async def test_async_generator_agen_protection():
146146
@_core.enable_ki_protection
147147
@async_generator
148148
async def agen_protected1():
@@ -180,45 +180,49 @@ async def agen_unprotected2():
180180
finally:
181181
assert not _core.currently_ki_protected()
182182

183+
await _check_agen(agen_protected1)
184+
await _check_agen(agen_protected2)
185+
await _check_agen(agen_unprotected1)
186+
await _check_agen(agen_unprotected2)
187+
188+
189+
async def test_native_agen_protection():
183190
# Native async generators
184191
@_core.enable_ki_protection
185-
async def agen_protected3():
192+
async def agen_protected():
186193
assert _core.currently_ki_protected()
187194
try:
188195
yield
189196
finally:
190197
assert _core.currently_ki_protected()
191198

192199
@_core.disable_ki_protection
193-
async def agen_unprotected3():
200+
async def agen_unprotected():
194201
assert not _core.currently_ki_protected()
195202
try:
196203
yield
197204
finally:
198205
assert not _core.currently_ki_protected()
199206

200-
for agen_fn in [
201-
agen_protected1,
202-
agen_protected2,
203-
agen_protected3,
204-
agen_unprotected1,
205-
agen_unprotected2,
206-
agen_unprotected3,
207-
]:
208-
async for _ in agen_fn(): # noqa
207+
await _check_agen(agen_protected)
208+
await _check_agen(agen_unprotected)
209+
210+
211+
async def _check_agen(agen_fn):
212+
async for _ in agen_fn(): # noqa
213+
assert not _core.currently_ki_protected()
214+
215+
# asynccontextmanager insists that the function passed must itself be an
216+
# async gen function, not a wrapper around one
217+
if inspect.isasyncgenfunction(agen_fn):
218+
async with contextlib.asynccontextmanager(agen_fn)():
209219
assert not _core.currently_ki_protected()
210220

211-
# asynccontextmanager insists that the function passed must itself be an
212-
# async gen function, not a wrapper around one
213-
if isasyncgenfunction(agen_fn):
214-
async with asynccontextmanager(agen_fn)():
215-
assert not _core.currently_ki_protected()
216-
217-
# Another case that's tricky due to:
218-
# https://bugs.python.org/issue29590
219-
with pytest.raises(KeyError):
220-
async with asynccontextmanager(agen_fn)():
221-
raise KeyError
221+
# Another case that's tricky due to:
222+
# https://bugs.python.org/issue29590
223+
with pytest.raises(KeyError):
224+
async with contextlib.asynccontextmanager(agen_fn)():
225+
raise KeyError
222226

223227

224228
# Test the case where there's no magic local anywhere in the call stack

trio/_util.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
import typing as t
1212
import threading
1313
import collections
14-
15-
from async_generator import isasyncgen
14+
import inspect
1615

1716
import trio
1817

@@ -143,6 +142,7 @@ def _return_value_looks_like_wrong_library(value):
143142
# for things like functools.partial objects wrapping an async
144143
# function. So we have to just call it and then check whether the
145144
# return value is a coroutine object.
145+
# Note: will not be necessary on python>=3.8, see https://bugs.python.org/issue34890
146146
if not isinstance(coro, collections.abc.Coroutine):
147147
# Give good error for: nursery.start_soon(func_returning_future)
148148
if _return_value_looks_like_wrong_library(coro):
@@ -152,7 +152,7 @@ def _return_value_looks_like_wrong_library(value):
152152
"That won't work without some sort of compatibility shim.".format(coro)
153153
)
154154

155-
if isasyncgen(coro):
155+
if inspect.isasyncgen(coro):
156156
raise TypeError(
157157
"start_soon expected an async function but got an async "
158158
"generator {!r}".format(coro)

trio/testing/_sequencer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from collections import defaultdict
2+
from contextlib import asynccontextmanager
23

34
import attr
4-
from async_generator import asynccontextmanager
55

66
from .. import _core
77
from .. import _util

trio/tests/test_dtls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from trio import DTLSEndpoint
55
import random
66
import attr
7-
from async_generator import asynccontextmanager
7+
from contextlib import asynccontextmanager
88
from itertools import count
99

1010
import trustme

trio/tests/test_ssl.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@
77
import threading
88
import socket as stdlib_socket
99
import ssl
10-
from contextlib import contextmanager
10+
from contextlib import asynccontextmanager, contextmanager
1111
from functools import partial
1212

1313
from OpenSSL import SSL
1414
import trustme
15-
from async_generator import asynccontextmanager
1615

1716
import trio
1817
from .. import _core

trio/tests/test_subprocess.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
import signal
44
import subprocess
55
import sys
6+
from contextlib import asynccontextmanager
67
from functools import partial
78
from pathlib import Path as SyncPath
89

910
import pytest
10-
from async_generator import asynccontextmanager
1111

1212
from .. import (
1313
ClosedResourceError,

0 commit comments

Comments
 (0)