From 083ad0e94ca5dab2804a8351c1aa871322b2f8ed Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Fri, 2 Jan 2015 21:17:11 +0200 Subject: [PATCH] cpython-uasyncio: uasyncio compatibility module for CPython. Implements scheduling a coroutine by yielding it. Related discussion: https://groups.google.com/d/msg/python-tulip/emU4_qyPVQM/eS8G0bnmBIEJ --- cpython-uasyncio/example_yield_coro.py | 21 +++++++ cpython-uasyncio/metadata.txt | 3 + cpython-uasyncio/patch.diff | 27 +++++++++ cpython-uasyncio/setup.py | 18 ++++++ cpython-uasyncio/uasyncio.py | 80 ++++++++++++++++++++++++++ 5 files changed, 149 insertions(+) create mode 100644 cpython-uasyncio/example_yield_coro.py create mode 100644 cpython-uasyncio/metadata.txt create mode 100644 cpython-uasyncio/patch.diff create mode 100644 cpython-uasyncio/setup.py create mode 100644 cpython-uasyncio/uasyncio.py diff --git a/cpython-uasyncio/example_yield_coro.py b/cpython-uasyncio/example_yield_coro.py new file mode 100644 index 00000000..2291162a --- /dev/null +++ b/cpython-uasyncio/example_yield_coro.py @@ -0,0 +1,21 @@ +import uasyncio as asyncio + + +def run1(): + for i in range(1): + print('Hello World') + yield from asyncio.sleep(2) + print("run1 finished") + +def run2(): + for i in range(3): + print('bar') + yield run1() + yield from asyncio.sleep(1) + + +import logging +logging.basicConfig(level=logging.INFO) +loop = asyncio.get_event_loop() +loop.create_task(run2()) +loop.run_forever() diff --git a/cpython-uasyncio/metadata.txt b/cpython-uasyncio/metadata.txt new file mode 100644 index 00000000..046c30f5 --- /dev/null +++ b/cpython-uasyncio/metadata.txt @@ -0,0 +1,3 @@ +srctype = cpython-backport +type = module +version = 0.1 diff --git a/cpython-uasyncio/patch.diff b/cpython-uasyncio/patch.diff new file mode 100644 index 00000000..be874237 --- /dev/null +++ b/cpython-uasyncio/patch.diff @@ -0,0 +1,27 @@ +This patch shows changes done to asyncio.tasks.Task._step() from CPython 3.4.2. + +--- tasks.py 2015-01-01 10:51:40.707114866 +0200 ++++ uasyncio.py 2015-01-01 10:54:20.172402890 +0200 +@@ -46,13 +55,16 @@ + # Bare yield relinquishes control for one event loop iteration. + self._loop.call_soon(self._step) + elif inspect.isgenerator(result): ++ #print("Scheduling", result) ++ self._loop.create_task(result) ++ self._loop.call_soon(self._step) + # Yielding a generator is just wrong. +- self._loop.call_soon( +- self._step, None, +- RuntimeError( +- 'yield was used instead of yield from for ' +- 'generator in task {!r} with {}'.format( +- self, result))) ++# self._loop.call_soon( ++# self._step, None, ++# RuntimeError( ++# 'yield was used instead of yield from for ' ++# 'generator in task {!r} with {}'.format( ++# self, result))) + else: + # Yielding something else is an error. + self._loop.call_soon( diff --git a/cpython-uasyncio/setup.py b/cpython-uasyncio/setup.py new file mode 100644 index 00000000..2b21bddf --- /dev/null +++ b/cpython-uasyncio/setup.py @@ -0,0 +1,18 @@ +import sys +# Remove current dir from sys.path, otherwise setuptools will peek up our +# module instead of system. +sys.path.pop(0) +from setuptools import setup + + +setup(name='micropython-cpython-uasyncio', + version='0.1', + description='MicroPython module uasyncio ported to CPython', + long_description='This is MicroPython compatibility module, allowing applications using\nMicroPython-specific features to run on CPython.\n', + url='https://github.com/micropython/micropython/issues/405', + author='MicroPython Developers', + author_email='micro-python@googlegroups.com', + maintainer='MicroPython Developers', + maintainer_email='micro-python@googlegroups.com', + license='Python', + py_modules=['uasyncio']) diff --git a/cpython-uasyncio/uasyncio.py b/cpython-uasyncio/uasyncio.py new file mode 100644 index 00000000..84e24409 --- /dev/null +++ b/cpython-uasyncio/uasyncio.py @@ -0,0 +1,80 @@ +import inspect +import asyncio +import asyncio.futures as futures +from asyncio import * + + +OrgTask = Task + +class Task(OrgTask): + + def _step(self, value=None, exc=None): + assert not self.done(), \ + '_step(): already done: {!r}, {!r}, {!r}'.format(self, value, exc) + if self._must_cancel: + if not isinstance(exc, futures.CancelledError): + exc = futures.CancelledError() + self._must_cancel = False + coro = self._coro + self._fut_waiter = None + + self.__class__._current_tasks[self._loop] = self + # Call either coro.throw(exc) or coro.send(value). + try: + if exc is not None: + result = coro.throw(exc) + elif value is not None: + result = coro.send(value) + else: + result = next(coro) + except StopIteration as exc: + self.set_result(exc.value) + except futures.CancelledError as exc: + super().cancel() # I.e., Future.cancel(self). + except Exception as exc: + self.set_exception(exc) + except BaseException as exc: + self.set_exception(exc) + raise + else: + if isinstance(result, futures.Future): + # Yielded Future must come from Future.__iter__(). + if result._blocking: + result._blocking = False + result.add_done_callback(self._wakeup) + self._fut_waiter = result + if self._must_cancel: + if self._fut_waiter.cancel(): + self._must_cancel = False + else: + self._loop.call_soon( + self._step, None, + RuntimeError( + 'yield was used instead of yield from ' + 'in task {!r} with {!r}'.format(self, result))) + elif result is None: + # Bare yield relinquishes control for one event loop iteration. + self._loop.call_soon(self._step) + elif inspect.isgenerator(result): + #print("Scheduling", result) + self._loop.create_task(result) + self._loop.call_soon(self._step) + # Yielding a generator is just wrong. +# self._loop.call_soon( +# self._step, None, +# RuntimeError( +# 'yield was used instead of yield from for ' +# 'generator in task {!r} with {}'.format( +# self, result))) + else: + # Yielding something else is an error. + self._loop.call_soon( + self._step, None, + RuntimeError( + 'Task got bad yield: {!r}'.format(result))) + finally: + self.__class__._current_tasks.pop(self._loop) + self = None # Needed to break cycles when an exception occurs. + + +asyncio.tasks.Task = Task