Skip to content

Commit f951d28

Browse files
committed
asyncio: sync with Tulip, add a new asyncio.coroutines module
1 parent 61f32cb commit f951d28

File tree

12 files changed

+221
-199
lines changed

12 files changed

+221
-199
lines changed

‎Lib/asyncio/__init__.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import _overlapped # Will also be exported.
1919

2020
# This relies on each of the submodules having an __all__ variable.
21+
from .coroutines import *
2122
from .events import *
2223
from .futures import *
2324
from .locks import *
@@ -34,7 +35,8 @@
3435
from .unix_events import * # pragma: no cover
3536

3637

37-
__all__ = (events.__all__ +
38+
__all__ = (coroutines.__all__ +
39+
events.__all__ +
3840
futures.__all__ +
3941
locks.__all__ +
4042
protocols.__all__ +

‎Lib/asyncio/base_events.py‎

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
import os
2727
import sys
2828

29+
from . import coroutines
2930
from . import events
3031
from . import futures
3132
from . import tasks
33+
from .coroutines import coroutine
3234
from .log import logger
3335

3436

@@ -118,7 +120,7 @@ def _wakeup(self):
118120
if not waiter.done():
119121
waiter.set_result(waiter)
120122

121-
@tasks.coroutine
123+
@coroutine
122124
def wait_closed(self):
123125
if self.sockets is None or self.waiters is None:
124126
return
@@ -175,7 +177,7 @@ def _make_write_pipe_transport(self, pipe, protocol, waiter=None,
175177
"""Create write pipe transport."""
176178
raise NotImplementedError
177179

178-
@tasks.coroutine
180+
@coroutine
179181
def _make_subprocess_transport(self, protocol, args, shell,
180182
stdin, stdout, stderr, bufsize,
181183
extra=None, **kwargs):
@@ -298,7 +300,7 @@ def call_later(self, delay, callback, *args):
298300

299301
def call_at(self, when, callback, *args):
300302
"""Like call_later(), but uses an absolute time."""
301-
if tasks.iscoroutinefunction(callback):
303+
if coroutines.iscoroutinefunction(callback):
302304
raise TypeError("coroutines cannot be used with call_at()")
303305
if self._debug:
304306
self._assert_is_current_event_loop()
@@ -324,7 +326,7 @@ def call_soon(self, callback, *args):
324326
return handle
325327

326328
def _call_soon(self, callback, args, check_loop):
327-
if tasks.iscoroutinefunction(callback):
329+
if coroutines.iscoroutinefunction(callback):
328330
raise TypeError("coroutines cannot be used with call_soon()")
329331
if self._debug and check_loop:
330332
self._assert_is_current_event_loop()
@@ -361,7 +363,7 @@ def call_soon_threadsafe(self, callback, *args):
361363
return handle
362364

363365
def run_in_executor(self, executor, callback, *args):
364-
if tasks.iscoroutinefunction(callback):
366+
if coroutines.iscoroutinefunction(callback):
365367
raise TypeError("coroutines cannot be used with run_in_executor()")
366368
if isinstance(callback, events.Handle):
367369
assert not args
@@ -389,7 +391,7 @@ def getaddrinfo(self, host, port, *,
389391
def getnameinfo(self, sockaddr, flags=0):
390392
return self.run_in_executor(None, socket.getnameinfo, sockaddr, flags)
391393

392-
@tasks.coroutine
394+
@coroutine
393395
def create_connection(self, protocol_factory, host=None, port=None, *,
394396
ssl=None, family=0, proto=0, flags=0, sock=None,
395397
local_addr=None, server_hostname=None):
@@ -505,7 +507,7 @@ def create_connection(self, protocol_factory, host=None, port=None, *,
505507
sock, protocol_factory, ssl, server_hostname)
506508
return transport, protocol
507509

508-
@tasks.coroutine
510+
@coroutine
509511
def _create_connection_transport(self, sock, protocol_factory, ssl,
510512
server_hostname):
511513
protocol = protocol_factory()
@@ -521,7 +523,7 @@ def _create_connection_transport(self, sock, protocol_factory, ssl,
521523
yield from waiter
522524
return transport, protocol
523525

524-
@tasks.coroutine
526+
@coroutine
525527
def create_datagram_endpoint(self, protocol_factory,
526528
local_addr=None, remote_addr=None, *,
527529
family=0, proto=0, flags=0):
@@ -593,7 +595,7 @@ def create_datagram_endpoint(self, protocol_factory,
593595
transport = self._make_datagram_transport(sock, protocol, r_addr)
594596
return transport, protocol
595597

596-
@tasks.coroutine
598+
@coroutine
597599
def create_server(self, protocol_factory, host=None, port=None,
598600
*,
599601
family=socket.AF_UNSPEC,
@@ -672,23 +674,23 @@ def create_server(self, protocol_factory, host=None, port=None,
672674
self._start_serving(protocol_factory, sock, ssl, server)
673675
return server
674676

675-
@tasks.coroutine
677+
@coroutine
676678
def connect_read_pipe(self, protocol_factory, pipe):
677679
protocol = protocol_factory()
678680
waiter = futures.Future(loop=self)
679681
transport = self._make_read_pipe_transport(pipe, protocol, waiter)
680682
yield from waiter
681683
return transport, protocol
682684

683-
@tasks.coroutine
685+
@coroutine
684686
def connect_write_pipe(self, protocol_factory, pipe):
685687
protocol = protocol_factory()
686688
waiter = futures.Future(loop=self)
687689
transport = self._make_write_pipe_transport(pipe, protocol, waiter)
688690
yield from waiter
689691
return transport, protocol
690692

691-
@tasks.coroutine
693+
@coroutine
692694
def subprocess_shell(self, protocol_factory, cmd, *, stdin=subprocess.PIPE,
693695
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
694696
universal_newlines=False, shell=True, bufsize=0,
@@ -706,7 +708,7 @@ def subprocess_shell(self, protocol_factory, cmd, *, stdin=subprocess.PIPE,
706708
protocol, cmd, True, stdin, stdout, stderr, bufsize, **kwargs)
707709
return transport, protocol
708710

709-
@tasks.coroutine
711+
@coroutine
710712
def subprocess_exec(self, protocol_factory, program, *args,
711713
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
712714
stderr=subprocess.PIPE, universal_newlines=False,

‎Lib/asyncio/base_subprocess.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import subprocess
33

44
from . import protocols
5-
from . import tasks
65
from . import transports
6+
from .coroutines import coroutine
77

88

99
class BaseSubprocessTransport(transports.SubprocessTransport):
@@ -65,7 +65,7 @@ def terminate(self):
6565
def kill(self):
6666
self._proc.kill()
6767

68-
@tasks.coroutine
68+
@coroutine
6969
def _post_init(self):
7070
proc = self._proc
7171
loop = self._loop

‎Lib/asyncio/coroutines.py‎

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
__all__ = ['coroutine',
2+
'iscoroutinefunction', 'iscoroutine']
3+
4+
import functools
5+
import inspect
6+
import os
7+
import sys
8+
import traceback
9+
10+
from . import events
11+
from . import futures
12+
from .log import logger
13+
14+
# If you set _DEBUG to true, @coroutine will wrap the resulting
15+
# generator objects in a CoroWrapper instance (defined below). That
16+
# instance will log a message when the generator is never iterated
17+
# over, which may happen when you forget to use "yield from" with a
18+
# coroutine call. Note that the value of the _DEBUG flag is taken
19+
# when the decorator is used, so to be of any use it must be set
20+
# before you define your coroutines. A downside of using this feature
21+
# is that tracebacks show entries for the CoroWrapper.__next__ method
22+
# when _DEBUG is true.
23+
_DEBUG = (not sys.flags.ignore_environment
24+
and bool(os.environ.get('PYTHONASYNCIODEBUG')))
25+
26+
_PY35 = (sys.version_info >= (3, 5))
27+
28+
class CoroWrapper:
29+
# Wrapper for coroutine in _DEBUG mode.
30+
31+
def __init__(self, gen, func):
32+
assert inspect.isgenerator(gen), gen
33+
self.gen = gen
34+
self.func = func
35+
self._source_traceback = traceback.extract_stack(sys._getframe(1))
36+
37+
def __iter__(self):
38+
return self
39+
40+
def __next__(self):
41+
return next(self.gen)
42+
43+
def send(self, *value):
44+
# We use `*value` because of a bug in CPythons prior
45+
# to 3.4.1. See issue #21209 and test_yield_from_corowrapper
46+
# for details. This workaround should be removed in 3.5.0.
47+
if len(value) == 1:
48+
value = value[0]
49+
return self.gen.send(value)
50+
51+
def throw(self, exc):
52+
return self.gen.throw(exc)
53+
54+
def close(self):
55+
return self.gen.close()
56+
57+
@property
58+
def gi_frame(self):
59+
return self.gen.gi_frame
60+
61+
@property
62+
def gi_running(self):
63+
return self.gen.gi_running
64+
65+
@property
66+
def gi_code(self):
67+
return self.gen.gi_code
68+
69+
def __del__(self):
70+
# Be careful accessing self.gen.frame -- self.gen might not exist.
71+
gen = getattr(self, 'gen', None)
72+
frame = getattr(gen, 'gi_frame', None)
73+
if frame is not None and frame.f_lasti == -1:
74+
func = events._format_callback(self.func, ())
75+
tb = ''.join(traceback.format_list(self._source_traceback))
76+
message = ('Coroutine %s was never yielded from\n'
77+
'Coroutine object created at (most recent call last):\n'
78+
'%s'
79+
% (func, tb.rstrip()))
80+
logger.error(message)
81+
82+
83+
def coroutine(func):
84+
"""Decorator to mark coroutines.
85+
86+
If the coroutine is not yielded from before it is destroyed,
87+
an error message is logged.
88+
"""
89+
if inspect.isgeneratorfunction(func):
90+
coro = func
91+
else:
92+
@functools.wraps(func)
93+
def coro(*args, **kw):
94+
res = func(*args, **kw)
95+
if isinstance(res, futures.Future) or inspect.isgenerator(res):
96+
res = yield from res
97+
return res
98+
99+
if not _DEBUG:
100+
wrapper = coro
101+
else:
102+
@functools.wraps(func)
103+
def wrapper(*args, **kwds):
104+
w = CoroWrapper(coro(*args, **kwds), func)
105+
if w._source_traceback:
106+
del w._source_traceback[-1]
107+
w.__name__ = func.__name__
108+
if _PY35:
109+
w.__qualname__ = func.__qualname__
110+
w.__doc__ = func.__doc__
111+
return w
112+
113+
wrapper._is_coroutine = True # For iscoroutinefunction().
114+
return wrapper
115+
116+
117+
def iscoroutinefunction(func):
118+
"""Return True if func is a decorated coroutine function."""
119+
return getattr(func, '_is_coroutine', False)
120+
121+
122+
def iscoroutine(obj):
123+
"""Return True if obj is a coroutine object."""
124+
return isinstance(obj, CoroWrapper) or inspect.isgenerator(obj)
125+
126+
127+
def _format_coroutine(coro):
128+
assert iscoroutine(coro)
129+
if _PY35:
130+
coro_name = coro.__qualname__
131+
else:
132+
coro_name = coro.__name__
133+
134+
filename = coro.gi_code.co_filename
135+
if coro.gi_frame is not None:
136+
lineno = coro.gi_frame.f_lineno
137+
return '%s() at %s:%s' % (coro_name, filename, lineno)
138+
else:
139+
lineno = coro.gi_code.co_firstlineno
140+
return '%s() done at %s:%s' % (coro_name, filename, lineno)

‎Lib/asyncio/locks.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from . import events
88
from . import futures
9-
from . import tasks
9+
from .coroutines import coroutine
1010

1111

1212
class _ContextManager:
@@ -112,7 +112,7 @@ def locked(self):
112112
"""Return True if lock is acquired."""
113113
return self._locked
114114

115-
@tasks.coroutine
115+
@coroutine
116116
def acquire(self):
117117
"""Acquire a lock.
118118
@@ -225,7 +225,7 @@ def clear(self):
225225
to true again."""
226226
self._value = False
227227

228-
@tasks.coroutine
228+
@coroutine
229229
def wait(self):
230230
"""Block until the internal flag is true.
231231
@@ -278,7 +278,7 @@ def __repr__(self):
278278
extra = '{},waiters:{}'.format(extra, len(self._waiters))
279279
return '<{} [{}]>'.format(res[1:-1], extra)
280280

281-
@tasks.coroutine
281+
@coroutine
282282
def wait(self):
283283
"""Wait until notified.
284284
@@ -306,7 +306,7 @@ def wait(self):
306306
finally:
307307
yield from self.acquire()
308308

309-
@tasks.coroutine
309+
@coroutine
310310
def wait_for(self, predicate):
311311
"""Wait until a predicate becomes true.
312312
@@ -402,7 +402,7 @@ def locked(self):
402402
"""Returns True if semaphore can not be acquired immediately."""
403403
return self._value == 0
404404

405-
@tasks.coroutine
405+
@coroutine
406406
def acquire(self):
407407
"""Acquire a semaphore.
408408

0 commit comments

Comments
 (0)