blob: cdb1ea8d889811f93bfddca629f0f3118b8706a5 [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))
67
68 def __iter__(self):
69 return self
70
71 def __next__(self):
72 return next(self.gen)
73
Victor Stinnerb75380f2014-06-30 14:39:11 +020074 if _YIELD_FROM_BUG:
75 # For for CPython issue #21209: using "yield from" and a custom
76 # generator, generator.send(tuple) unpacks the tuple instead of passing
77 # the tuple unchanged. Check if the caller is a generator using "yield
78 # from" to decide if the parameter should be unpacked or not.
79 def send(self, *value):
80 frame = sys._getframe()
81 caller = frame.f_back
82 assert caller.f_lasti >= 0
83 if caller.f_code.co_code[caller.f_lasti] != _YIELD_FROM:
84 value = value[0]
85 return self.gen.send(value)
86 else:
87 def send(self, value):
88 return self.gen.send(value)
Victor Stinnerf951d282014-06-29 00:46:45 +020089
90 def throw(self, exc):
91 return self.gen.throw(exc)
92
93 def close(self):
94 return self.gen.close()
95
96 @property
97 def gi_frame(self):
98 return self.gen.gi_frame
99
100 @property
101 def gi_running(self):
102 return self.gen.gi_running
103
104 @property
105 def gi_code(self):
106 return self.gen.gi_code
107
108 def __del__(self):
109 # Be careful accessing self.gen.frame -- self.gen might not exist.
110 gen = getattr(self, 'gen', None)
111 frame = getattr(gen, 'gi_frame', None)
112 if frame is not None and frame.f_lasti == -1:
113 func = events._format_callback(self.func, ())
114 tb = ''.join(traceback.format_list(self._source_traceback))
115 message = ('Coroutine %s was never yielded from\n'
116 'Coroutine object created at (most recent call last):\n'
117 '%s'
118 % (func, tb.rstrip()))
119 logger.error(message)
120
121
122def coroutine(func):
123 """Decorator to mark coroutines.
124
125 If the coroutine is not yielded from before it is destroyed,
126 an error message is logged.
127 """
128 if inspect.isgeneratorfunction(func):
129 coro = func
130 else:
131 @functools.wraps(func)
132 def coro(*args, **kw):
133 res = func(*args, **kw)
134 if isinstance(res, futures.Future) or inspect.isgenerator(res):
135 res = yield from res
136 return res
137
138 if not _DEBUG:
139 wrapper = coro
140 else:
141 @functools.wraps(func)
142 def wrapper(*args, **kwds):
143 w = CoroWrapper(coro(*args, **kwds), func)
144 if w._source_traceback:
145 del w._source_traceback[-1]
146 w.__name__ = func.__name__
147 if _PY35:
148 w.__qualname__ = func.__qualname__
149 w.__doc__ = func.__doc__
150 return w
151
152 wrapper._is_coroutine = True # For iscoroutinefunction().
153 return wrapper
154
155
156def iscoroutinefunction(func):
157 """Return True if func is a decorated coroutine function."""
158 return getattr(func, '_is_coroutine', False)
159
160
Victor Stinnerb75380f2014-06-30 14:39:11 +0200161_COROUTINE_TYPES = (CoroWrapper, types.GeneratorType)
162
Victor Stinnerf951d282014-06-29 00:46:45 +0200163def iscoroutine(obj):
164 """Return True if obj is a coroutine object."""
Victor Stinnerb75380f2014-06-30 14:39:11 +0200165 return isinstance(obj, _COROUTINE_TYPES)
Victor Stinnerf951d282014-06-29 00:46:45 +0200166
167
168def _format_coroutine(coro):
169 assert iscoroutine(coro)
170 if _PY35:
171 coro_name = coro.__qualname__
172 else:
173 coro_name = coro.__name__
174
175 filename = coro.gi_code.co_filename
176 if coro.gi_frame is not None:
177 lineno = coro.gi_frame.f_lineno
178 return '%s() at %s:%s' % (coro_name, filename, lineno)
179 else:
180 lineno = coro.gi_code.co_firstlineno
181 return '%s() done at %s:%s' % (coro_name, filename, lineno)