blob: 7654a0b9e0529bef4d687068f464005aaef49c3c [file] [log] [blame]
Victor Stinnerf951d282014-06-29 00:46:45 +02001__all__ = ['coroutine',
2 'iscoroutinefunction', 'iscoroutine']
3
4import functools
5import inspect
Victor Stinnerb75380f2014-06-30 14:39:11 +02006import opcode
Victor Stinnerf951d282014-06-29 00:46:45 +02007import os
8import sys
9import traceback
Victor Stinnerb75380f2014-06-30 14:39:11 +020010import types
Victor Stinnerf951d282014-06-29 00:46:45 +020011
12from . import events
13from . import futures
14from .log import logger
15
Victor Stinnerb75380f2014-06-30 14:39:11 +020016
17# Opcode of "yield from" instruction
18_YIELD_FROM = opcode.opmap['YIELD_FROM']
19
Victor Stinnerf951d282014-06-29 00:46:45 +020020# If you set _DEBUG to true, @coroutine will wrap the resulting
21# generator objects in a CoroWrapper instance (defined below). That
22# instance will log a message when the generator is never iterated
23# over, which may happen when you forget to use "yield from" with a
24# coroutine call. Note that the value of the _DEBUG flag is taken
25# when the decorator is used, so to be of any use it must be set
26# before you define your coroutines. A downside of using this feature
27# is that tracebacks show entries for the CoroWrapper.__next__ method
28# when _DEBUG is true.
29_DEBUG = (not sys.flags.ignore_environment
30 and bool(os.environ.get('PYTHONASYNCIODEBUG')))
31
32_PY35 = (sys.version_info >= (3, 5))
33
Victor Stinnerb75380f2014-06-30 14:39:11 +020034
35# Check for CPython issue #21209
36def has_yield_from_bug():
37 class MyGen:
38 def __init__(self):
39 self.send_args = None
40 def __iter__(self):
41 return self
42 def __next__(self):
43 return 42
44 def send(self, *what):
45 self.send_args = what
46 return None
47 def yield_from_gen(gen):
48 yield from gen
49 value = (1, 2, 3)
50 gen = MyGen()
51 coro = yield_from_gen(gen)
52 next(coro)
53 coro.send(value)
54 return gen.send_args != (value,)
55_YIELD_FROM_BUG = has_yield_from_bug()
56del has_yield_from_bug
57
58
Victor Stinnerf951d282014-06-29 00:46:45 +020059class CoroWrapper:
60 # Wrapper for coroutine in _DEBUG mode.
61
62 def __init__(self, gen, func):
63 assert inspect.isgenerator(gen), gen
64 self.gen = gen
65 self.func = func
66 self._source_traceback = traceback.extract_stack(sys._getframe(1))
Victor Stinnera9acbe82014-07-05 15:29:41 +020067 # __name__, __qualname__, __doc__ attributes are set by the coroutine()
68 # decorator
69
70 def __repr__(self):
71 return ('<%s %s>'
72 % (self.__class__.__name__, _format_coroutine(self)))
Victor Stinnerf951d282014-06-29 00:46:45 +020073
74 def __iter__(self):
75 return self
76
77 def __next__(self):
78 return next(self.gen)
79
Victor Stinnerb75380f2014-06-30 14:39:11 +020080 if _YIELD_FROM_BUG:
81 # For for CPython issue #21209: using "yield from" and a custom
82 # generator, generator.send(tuple) unpacks the tuple instead of passing
83 # the tuple unchanged. Check if the caller is a generator using "yield
84 # from" to decide if the parameter should be unpacked or not.
85 def send(self, *value):
86 frame = sys._getframe()
87 caller = frame.f_back
88 assert caller.f_lasti >= 0
89 if caller.f_code.co_code[caller.f_lasti] != _YIELD_FROM:
90 value = value[0]
91 return self.gen.send(value)
92 else:
93 def send(self, value):
94 return self.gen.send(value)
Victor Stinnerf951d282014-06-29 00:46:45 +020095
96 def throw(self, exc):
97 return self.gen.throw(exc)
98
99 def close(self):
100 return self.gen.close()
101
102 @property
103 def gi_frame(self):
104 return self.gen.gi_frame
105
106 @property
107 def gi_running(self):
108 return self.gen.gi_running
109
110 @property
111 def gi_code(self):
112 return self.gen.gi_code
113
114 def __del__(self):
115 # Be careful accessing self.gen.frame -- self.gen might not exist.
116 gen = getattr(self, 'gen', None)
117 frame = getattr(gen, 'gi_frame', None)
118 if frame is not None and frame.f_lasti == -1:
119 func = events._format_callback(self.func, ())
Victor Stinner2dba23a2014-07-03 00:59:00 +0200120 msg = 'Coroutine %s was never yielded from' % func
121 tb = getattr(self, '_source_traceback', ())
122 if tb:
123 tb = ''.join(traceback.format_list(tb))
124 msg += ('\nCoroutine object created at '
125 '(most recent call last):\n')
126 msg += tb.rstrip()
127 logger.error(msg)
Victor Stinnerf951d282014-06-29 00:46:45 +0200128
129
130def coroutine(func):
131 """Decorator to mark coroutines.
132
133 If the coroutine is not yielded from before it is destroyed,
134 an error message is logged.
135 """
136 if inspect.isgeneratorfunction(func):
137 coro = func
138 else:
139 @functools.wraps(func)
140 def coro(*args, **kw):
141 res = func(*args, **kw)
142 if isinstance(res, futures.Future) or inspect.isgenerator(res):
143 res = yield from res
144 return res
145
146 if not _DEBUG:
147 wrapper = coro
148 else:
149 @functools.wraps(func)
150 def wrapper(*args, **kwds):
151 w = CoroWrapper(coro(*args, **kwds), func)
152 if w._source_traceback:
153 del w._source_traceback[-1]
154 w.__name__ = func.__name__
155 if _PY35:
156 w.__qualname__ = func.__qualname__
157 w.__doc__ = func.__doc__
158 return w
159
160 wrapper._is_coroutine = True # For iscoroutinefunction().
161 return wrapper
162
163
164def iscoroutinefunction(func):
165 """Return True if func is a decorated coroutine function."""
166 return getattr(func, '_is_coroutine', False)
167
168
Victor Stinnerb75380f2014-06-30 14:39:11 +0200169_COROUTINE_TYPES = (CoroWrapper, types.GeneratorType)
170
Victor Stinnerf951d282014-06-29 00:46:45 +0200171def iscoroutine(obj):
172 """Return True if obj is a coroutine object."""
Victor Stinnerb75380f2014-06-30 14:39:11 +0200173 return isinstance(obj, _COROUTINE_TYPES)
Victor Stinnerf951d282014-06-29 00:46:45 +0200174
175
176def _format_coroutine(coro):
177 assert iscoroutine(coro)
178 if _PY35:
179 coro_name = coro.__qualname__
180 else:
181 coro_name = coro.__name__
182
183 filename = coro.gi_code.co_filename
184 if coro.gi_frame is not None:
185 lineno = coro.gi_frame.f_lineno
186 return '%s() at %s:%s' % (coro_name, filename, lineno)
187 else:
188 lineno = coro.gi_code.co_firstlineno
189 return '%s() done at %s:%s' % (coro_name, filename, lineno)