[Python-checkins] cpython (3.4): asyncio: Support PEP 492. Issue #24017.

yury.selivanov python-checkins at python.org
Tue May 12 04:31:31 CEST 2015


https://hg.python.org/cpython/rev/957478e95b26
changeset:   95964:957478e95b26
branch:      3.4
parent:      95961:9d9e445d25dc
user:        Yury Selivanov <yselivanov at sprymix.com>
date:        Mon May 11 22:27:25 2015 -0400
summary:
  asyncio: Support PEP 492. Issue #24017.

files:
  Lib/asyncio/base_events.py                |  47 +++++-
  Lib/asyncio/coroutines.py                 |  77 +++++++++-
  Lib/asyncio/futures.py                    |   4 +
  Lib/asyncio/tasks.py                      |   8 +-
  Lib/test/test_asyncio/test_base_events.py |   3 +-
  Lib/test/test_asyncio/test_tasks.py       |   4 +-
  6 files changed, 116 insertions(+), 27 deletions(-)


diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -191,8 +191,8 @@
         self._thread_id = None
         self._clock_resolution = time.get_clock_info('monotonic').resolution
         self._exception_handler = None
-        self._debug = (not sys.flags.ignore_environment
-                       and bool(os.environ.get('PYTHONASYNCIODEBUG')))
+        self.set_debug((not sys.flags.ignore_environment
+                        and bool(os.environ.get('PYTHONASYNCIODEBUG'))))
         # In debug mode, if the execution of a callback or a step of a task
         # exceed this duration in seconds, the slow callback/task is logged.
         self.slow_callback_duration = 0.1
@@ -360,13 +360,18 @@
             return
         if self._debug:
             logger.debug("Close %r", self)
-        self._closed = True
-        self._ready.clear()
-        self._scheduled.clear()
-        executor = self._default_executor
-        if executor is not None:
-            self._default_executor = None
-            executor.shutdown(wait=False)
+        try:
+            self._closed = True
+            self._ready.clear()
+            self._scheduled.clear()
+            executor = self._default_executor
+            if executor is not None:
+                self._default_executor = None
+                executor.shutdown(wait=False)
+        finally:
+            # It is important to unregister "sys.coroutine_wrapper"
+            # if it was registered.
+            self.set_debug(False)
 
     def is_closed(self):
         """Returns True if the event loop was closed."""
@@ -1199,3 +1204,27 @@
 
     def set_debug(self, enabled):
         self._debug = enabled
+        wrapper = coroutines.debug_wrapper
+
+        try:
+            set_wrapper = sys.set_coroutine_wrapper
+        except AttributeError:
+            pass
+        else:
+            current_wrapper = sys.get_coroutine_wrapper()
+            if enabled:
+                if current_wrapper not in (None, wrapper):
+                    warnings.warn(
+                        "loop.set_debug(True): cannot set debug coroutine "
+                        "wrapper; another wrapper is already set %r" %
+                        current_wrapper, RuntimeWarning)
+                else:
+                    set_wrapper(wrapper)
+            else:
+                if current_wrapper not in (None, wrapper):
+                    warnings.warn(
+                        "loop.set_debug(False): cannot unset debug coroutine "
+                        "wrapper; another wrapper was set %r" %
+                        current_wrapper, RuntimeWarning)
+                else:
+                    set_wrapper(None)
diff --git a/Lib/asyncio/coroutines.py b/Lib/asyncio/coroutines.py
--- a/Lib/asyncio/coroutines.py
+++ b/Lib/asyncio/coroutines.py
@@ -14,6 +14,9 @@
 from .log import logger
 
 
+_PY35 = sys.version_info >= (3, 5)
+
+
 # Opcode of "yield from" instruction
 _YIELD_FROM = opcode.opmap['YIELD_FROM']
 
@@ -30,6 +33,27 @@
           and bool(os.environ.get('PYTHONASYNCIODEBUG')))
 
 
+try:
+    types.coroutine
+except AttributeError:
+    native_coroutine_support = False
+else:
+    native_coroutine_support = True
+
+try:
+    _iscoroutinefunction = inspect.iscoroutinefunction
+except AttributeError:
+    _iscoroutinefunction = lambda func: False
+
+try:
+    inspect.CO_COROUTINE
+except AttributeError:
+    _is_native_coro_code = lambda code: False
+else:
+    _is_native_coro_code = lambda code: (code.co_flags &
+                                         inspect.CO_COROUTINE)
+
+
 # Check for CPython issue #21209
 def has_yield_from_bug():
     class MyGen:
@@ -54,16 +78,27 @@
 del has_yield_from_bug
 
 
+def debug_wrapper(gen):
+    # This function is called from 'sys.set_coroutine_wrapper'.
+    # We only wrap here coroutines defined via 'async def' syntax.
+    # Generator-based coroutines are wrapped in @coroutine
+    # decorator.
+    if _is_native_coro_code(gen.gi_code):
+        return CoroWrapper(gen, None)
+    else:
+        return gen
+
+
 class CoroWrapper:
     # Wrapper for coroutine object in _DEBUG mode.
 
-    def __init__(self, gen, func):
-        assert inspect.isgenerator(gen), gen
+    def __init__(self, gen, func=None):
+        assert inspect.isgenerator(gen) or inspect.iscoroutine(gen), gen
         self.gen = gen
-        self.func = func
+        self.func = func # Used to unwrap @coroutine decorator
         self._source_traceback = traceback.extract_stack(sys._getframe(1))
-        # __name__, __qualname__, __doc__ attributes are set by the coroutine()
-        # decorator
+        self.__name__ = getattr(gen, '__name__', None)
+        self.__qualname__ = getattr(gen, '__qualname__', None)
 
     def __repr__(self):
         coro_repr = _format_coroutine(self)
@@ -75,6 +110,9 @@
     def __iter__(self):
         return self
 
+    if _PY35:
+        __await__ = __iter__ # make compatible with 'await' expression
+
     def __next__(self):
         return next(self.gen)
 
@@ -133,6 +171,14 @@
     If the coroutine is not yielded from before it is destroyed,
     an error message is logged.
     """
+    is_coroutine = _iscoroutinefunction(func)
+    if is_coroutine and _is_native_coro_code(func.__code__):
+        # In Python 3.5 that's all we need to do for coroutines
+        # defiend with "async def".
+        # Wrapping in CoroWrapper will happen via
+        # 'sys.set_coroutine_wrapper' function.
+        return func
+
     if inspect.isgeneratorfunction(func):
         coro = func
     else:
@@ -144,18 +190,22 @@
             return res
 
     if not _DEBUG:
-        wrapper = coro
+        if native_coroutine_support:
+            wrapper = types.coroutine(coro)
+        else:
+            wrapper = coro
     else:
         @functools.wraps(func)
         def wrapper(*args, **kwds):
-            w = CoroWrapper(coro(*args, **kwds), func)
+            w = CoroWrapper(coro(*args, **kwds), func=func)
             if w._source_traceback:
                 del w._source_traceback[-1]
-            if hasattr(func, '__name__'):
-                w.__name__ = func.__name__
-            if hasattr(func, '__qualname__'):
-                w.__qualname__ = func.__qualname__
-            w.__doc__ = func.__doc__
+            # Python < 3.5 does not implement __qualname__
+            # on generator objects, so we set it manually.
+            # We use getattr as some callables (such as
+            # functools.partial may lack __qualname__).
+            w.__name__ = getattr(func, '__name__', None)
+            w.__qualname__ = getattr(func, '__qualname__', None)
             return w
 
     wrapper._is_coroutine = True  # For iscoroutinefunction().
@@ -164,7 +214,8 @@
 
 def iscoroutinefunction(func):
     """Return True if func is a decorated coroutine function."""
-    return getattr(func, '_is_coroutine', False)
+    return (getattr(func, '_is_coroutine', False) or
+            _iscoroutinefunction(func))
 
 
 _COROUTINE_TYPES = (types.GeneratorType, CoroWrapper)
diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py
--- a/Lib/asyncio/futures.py
+++ b/Lib/asyncio/futures.py
@@ -19,6 +19,7 @@
 _FINISHED = 'FINISHED'
 
 _PY34 = sys.version_info >= (3, 4)
+_PY35 = sys.version_info >= (3, 5)
 
 Error = concurrent.futures._base.Error
 CancelledError = concurrent.futures.CancelledError
@@ -387,6 +388,9 @@
         assert self.done(), "yield from wasn't used with future"
         return self.result()  # May raise too.
 
+    if _PY35:
+        __await__ = __iter__ # make compatible with 'await' expression
+
 
 def wrap_future(fut, *, loop=None):
     """Wrap concurrent.futures.Future object."""
diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py
--- a/Lib/asyncio/tasks.py
+++ b/Lib/asyncio/tasks.py
@@ -11,6 +11,7 @@
 import inspect
 import linecache
 import sys
+import types
 import traceback
 import warnings
 import weakref
@@ -73,7 +74,10 @@
         super().__init__(loop=loop)
         if self._source_traceback:
             del self._source_traceback[-1]
-        self._coro = iter(coro)  # Use the iterator just in case.
+        if coro.__class__ is types.GeneratorType:
+            self._coro = coro
+        else:
+            self._coro = iter(coro)  # Use the iterator just in case.
         self._fut_waiter = None
         self._must_cancel = False
         self._loop.call_soon(self._step)
@@ -236,7 +240,7 @@
             elif value is not None:
                 result = coro.send(value)
             else:
-                result = next(coro)
+                result = coro.send(None)
         except StopIteration as exc:
             self.set_result(exc.value)
         except futures.CancelledError as exc:
diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py
--- a/Lib/test/test_asyncio/test_base_events.py
+++ b/Lib/test/test_asyncio/test_base_events.py
@@ -61,7 +61,8 @@
             NotImplementedError,
             self.loop._make_write_pipe_transport, m, m)
         gen = self.loop._make_subprocess_transport(m, m, m, m, m, m, m)
-        self.assertRaises(NotImplementedError, next, iter(gen))
+        with self.assertRaises(NotImplementedError):
+            gen.send(None)
 
     def test_close(self):
         self.assertFalse(self.loop.is_closed())
diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -1638,7 +1638,7 @@
             return a
 
         def call(arg):
-            cw = asyncio.coroutines.CoroWrapper(foo(), foo)
+            cw = asyncio.coroutines.CoroWrapper(foo())
             cw.send(None)
             try:
                 cw.send(arg)
@@ -1653,7 +1653,7 @@
     def test_corowrapper_weakref(self):
         wd = weakref.WeakValueDictionary()
         def foo(): yield from []
-        cw = asyncio.coroutines.CoroWrapper(foo(), foo)
+        cw = asyncio.coroutines.CoroWrapper(foo())
         wd['cw'] = cw  # Would fail without __weakref__ slot.
         cw.gen = None  # Suppress warning from __del__.
 

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list