[Python-checkins] cpython (merge 3.5 -> default): Issue 24316: Fix types.coroutine() to accept objects from Cython

yury.selivanov python-checkins at python.org
Fri May 29 15:06:34 CEST 2015


https://hg.python.org/cpython/rev/748c55375225
changeset:   96351:748c55375225
parent:      96349:a7a9c8631d0e
parent:      96350:7356f71fb0a4
user:        Yury Selivanov <yselivanov at sprymix.com>
date:        Fri May 29 09:06:24 2015 -0400
summary:
  Issue 24316: Fix types.coroutine() to accept objects from Cython

(Merge 3.5)

files:
  Lib/test/test_types.py |  32 +++++++++++++-
  Lib/types.py           |  66 +++++++++++++++++++----------
  2 files changed, 72 insertions(+), 26 deletions(-)


diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -1196,11 +1196,39 @@
                 pass
         def bar(): pass
 
-        samples = [Foo, Foo(), bar, None, int, 1]
+        samples = [None, 1, object()]
         for sample in samples:
-            with self.assertRaisesRegex(TypeError, 'expects a generator'):
+            with self.assertRaisesRegex(TypeError,
+                                        'types.coroutine.*expects a callable'):
                 types.coroutine(sample)
 
+    def test_wrong_func(self):
+        @types.coroutine
+        def foo():
+            pass
+        @types.coroutine
+        def gen():
+            def _gen(): yield
+            return _gen()
+
+        for sample in (foo, gen):
+            with self.assertRaisesRegex(TypeError,
+                                        'callable wrapped .* non-coroutine'):
+                sample()
+
+    def test_duck_coro(self):
+        class CoroLike:
+            def send(self): pass
+            def throw(self): pass
+            def close(self): pass
+            def __await__(self): pass
+
+        coro = CoroLike()
+        @types.coroutine
+        def foo():
+            return coro
+        self.assertIs(coro, foo())
+
     def test_genfunc(self):
         def gen():
             yield
diff --git a/Lib/types.py b/Lib/types.py
--- a/Lib/types.py
+++ b/Lib/types.py
@@ -43,30 +43,6 @@
 del sys, _f, _g, _C,                              # Not for export
 
 
-_CO_GENERATOR = 0x20
-_CO_ITERABLE_COROUTINE = 0x100
-
-def coroutine(func):
-    """Convert regular generator function to a coroutine."""
-
-    # TODO: Implement this in C.
-
-    if (not isinstance(func, (FunctionType, MethodType)) or
-            not isinstance(getattr(func, '__code__', None), CodeType) or
-            not (func.__code__.co_flags & _CO_GENERATOR)):
-        raise TypeError('coroutine() expects a generator function')
-
-    co = func.__code__
-    func.__code__ = CodeType(
-        co.co_argcount, co.co_kwonlyargcount, co.co_nlocals, co.co_stacksize,
-        co.co_flags | _CO_ITERABLE_COROUTINE,
-        co.co_code,
-        co.co_consts, co.co_names, co.co_varnames, co.co_filename, co.co_name,
-        co.co_firstlineno, co.co_lnotab, co.co_freevars, co.co_cellvars)
-
-    return func
-
-
 # Provide a PEP 3115 compliant mechanism for class creation
 def new_class(name, bases=(), kwds=None, exec_body=None):
     """Create a class object dynamically using the appropriate metaclass."""
@@ -182,4 +158,46 @@
         return result
 
 
+import functools as _functools
+import collections.abc as _collections_abc
+
+def coroutine(func):
+    """Convert regular generator function to a coroutine."""
+
+    # We don't want to import 'dis' or 'inspect' just for
+    # these constants.
+    _CO_GENERATOR = 0x20
+    _CO_ITERABLE_COROUTINE = 0x100
+
+    if not callable(func):
+        raise TypeError('types.coroutine() expects a callable')
+
+    if (isinstance(func, FunctionType) and
+        isinstance(getattr(func, '__code__', None), CodeType) and
+        (func.__code__.co_flags & _CO_GENERATOR)):
+
+        # TODO: Implement this in C.
+        co = func.__code__
+        func.__code__ = CodeType(
+            co.co_argcount, co.co_kwonlyargcount, co.co_nlocals,
+            co.co_stacksize,
+            co.co_flags | _CO_ITERABLE_COROUTINE,
+            co.co_code,
+            co.co_consts, co.co_names, co.co_varnames, co.co_filename,
+            co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars,
+            co.co_cellvars)
+        return func
+
+    @_functools.wraps(func)
+    def wrapped(*args, **kwargs):
+        coro = func(*args, **kwargs)
+        if not isinstance(coro, _collections_abc.Coroutine):
+            raise TypeError(
+                'callable wrapped with types.coroutine() returned '
+                'non-coroutine: {!r}'.format(coro))
+        return coro
+
+    return wrapped
+
+
 __all__ = [n for n in globals() if n[:1] != '_']

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


More information about the Python-checkins mailing list