Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port greenio to Trollius #5

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
84 changes: 72 additions & 12 deletions greenio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,46 @@


import greenlet
import sys

try:
import trollius
except ImportError:
trollius = None
try:
import asyncio
except ImportError:
if trollius is not None:
asyncio = trollius
else:
raise

if trollius is not None:
def _async(future):
# trollius iscoroutine() accepts trollius and asyncio coroutine
# objects
if trollius.iscoroutine(future):
loop = asyncio.get_event_loop()
return loop.create_task(future)
else:
return future

def _create_task(coro):
loop = asyncio.get_event_loop()
return loop.create_task(coro)
else:
def _async(future):
if asyncio.iscoroutine(future):
return GreenTask(future)
else:
return future

import asyncio
from asyncio import unix_events, tasks, futures
def _create_task(coro):
return GreenTask(coro)

_FUTURE_CLASSES = (asyncio.Future,)
if trollius is not None and trollius is not asyncio:
_FUTURE_CLASSES += (trollius.Future,)

import sys

Expand All @@ -28,16 +65,16 @@ class _TaskGreenlet(greenlet.greenlet):
``@greenio.task`` is executed in this greenlet"""


class GreenTask(asyncio.Task):
class _GreenTaskMixin(object):
def __init__(self, *args, **kwargs):
self._greenlet = None
super(GreenTask, self).__init__(*args, **kwargs)
super(_GreenTaskMixin, self).__init__(*args, **kwargs)

def _step(self, value=None, exc=None):
if self._greenlet is None:
# Means that the task is not currently in a suspended greenlet
# waiting for results for "yield_from"
ovr = super(GreenTask, self)._step
ovr = super(_GreenTaskMixin, self)._step
self._greenlet = _TaskGreenlet(ovr)

# Store a reference to the current task for "yield_from"
Expand Down Expand Up @@ -93,21 +130,43 @@ def run_forever(self, *args, **kwargs):
return self._green_run(ovr, args, kwargs)


class GreenUnixSelectorLoop(_GreenLoopMixin, asyncio.SelectorEventLoop):
class GreenTask(_GreenTaskMixin, asyncio.Task):
pass


class GreenEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
class GreenUnixSelectorLoop(_GreenLoopMixin, asyncio.SelectorEventLoop):
def create_task(self, coro):
return GreenTask(coro)


class GreenEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
def new_event_loop(self):
return GreenUnixSelectorLoop()


if trollius is not None:
if trollius is not asyncio:
class GreenTrolliusTask(_GreenTaskMixin, trollius.Task):
pass

class GreenTrolliusUnixSelectorLoop(_GreenLoopMixin, trollius.SelectorEventLoop):
def create_task(self, coro):
return GreenTrolliusTask(coro)

class GreenTrolliusEventLoopPolicy(asyncio.DefaultEventLoopPolicy):

def new_event_loop(self):
return GreenTrolliusUnixSelectorLoop()
else:
GreenTrolliusTask = GreenTask
GreenTrolliusUnixSelectorLoop = GreenUnixSelectorLoop
GreenTrolliusEventLoopPolicy = GreenEventLoopPolicy


def yield_from(future):
"""A function to use instead of ``yield from`` statement."""

if asyncio.iscoroutine(future):
future = GreenTask(future)
future = _async(future)

gl = greenlet.getcurrent()

Expand All @@ -126,7 +185,7 @@ def yield_from(future):

task = gl.task

if not isinstance(future, futures.Future):
if not isinstance(future, _FUTURE_CLASSES):
raise RuntimeError(
'greenlet.yield_from was supposed to receive only Futures, '
'got {!r} in task {!r}'.format(future, task))
Expand All @@ -149,10 +208,11 @@ def task(func):
"""A decorator, allows use of ``yield_from`` in the decorated or
subsequent coroutines."""

coro = asyncio.coroutine(func)
coro_func = asyncio.coroutine(func)

def task_wrapper(*args, **kwds):
return GreenTask(coro(*args, **kwds))
coro_obj = coro_func(*args, **kwds)
return _create_task(coro_obj)

return task_wrapper

Expand Down
5 changes: 4 additions & 1 deletion greenio/socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
from them.
"""
from __future__ import absolute_import
import asyncio
try:
import asyncio
except ImportError:
import trollius as asyncio
from socket import error, SOCK_STREAM
from socket import socket as std_socket

Expand Down
11 changes: 8 additions & 3 deletions tests/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@
##


import asyncio
import unittest
try:
import asyncio
from unittest import TestCase
except ImportError:
import trollius as asyncio
from trollius.test_utils import TestCase
import socket

import greenio
import greenio.socket as greensocket

import socket as std_socket


class SocketTests(unittest.TestCase):
class SocketTests(TestCase):

def setUp(self):
asyncio.set_event_loop_policy(greenio.GreenEventLoopPolicy())
Expand Down
181 changes: 123 additions & 58 deletions tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@


import greenio
import asyncio
import unittest


class TaskTests(unittest.TestCase):
try:
trollius = None
import asyncio
from unittest import TestCase
except ImportError:
import trollius
from trollius import From, Return
asyncio = trollius
from trollius.test_utils import TestCase


class TaskTests(TestCase):
def setUp(self):
asyncio.set_event_loop_policy(greenio.GreenEventLoopPolicy())
self.loop = asyncio.new_event_loop()
Expand All @@ -19,73 +26,131 @@ def tearDown(self):
self.loop.close()
asyncio.set_event_loop_policy(None)

def test_task_yield_from_plain(self):
@asyncio.coroutine
def bar():
yield
return 30

@asyncio.coroutine
def foo():
bar_result = greenio.yield_from(asyncio.Task(bar()))
return bar_result + 12

@greenio.task
def test():
return (yield from foo())
if trollius is not None:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the need to do this, but it still feels very wrong :(

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, can you elaborate? What's "wrong"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the need: "yield from" syntax raises a SyntaxError on Python 2, you just cannot import the module.

It may be possible to use different files, but I prefer to keep all code in a single code base.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that, that's the part that feels wrong, big #ifdef trollius around blocks of code.

But I guess this is how it goes....

def test_task_yield_from_plain(self):
@asyncio.coroutine
def bar():
yield From(None)
raise Return(30)

@asyncio.coroutine
def foo():
bar_result = greenio.yield_from(bar())
return bar_result + 12

@greenio.task
def test():
res = yield From(foo())
raise Return(res)

fut = test()
self.loop.run_until_complete(fut)

self.assertEqual(fut.result(), 42)

def test_task_yield_from_exception_propagation(self):
non_local = {'CHK': 0}

@asyncio.coroutine
def bar():
yield From(None)
yield From(None)
1/0

@greenio.task
def foo():
greenio.yield_from(bar())

@asyncio.coroutine
def test():
try:
res = yield From(foo())
raise Return(res)
except ZeroDivisionError:
non_local['CHK'] += 1

self.loop.run_until_complete(test())
self.assertEqual(non_local['CHK'], 1)

def test_task_yield_from_coroutine(self):
@asyncio.coroutine
def bar():
yield From(None)
raise Return(5)

@greenio.task
def foo():
return greenio.yield_from(bar())

fut = foo()
self.loop.run_until_complete(fut)
self.assertEqual(fut.result(), 5)
else:
exec('''if 1:
def test_task_yield_from_plain(self):
@asyncio.coroutine
def bar():
yield from []
return 30

@asyncio.coroutine
def foo():
bar_result = greenio.yield_from(bar())
return bar_result + 12

@greenio.task
def test():
return (yield from foo())

fut = test()
self.loop.run_until_complete(fut)
fut = test()
self.loop.run_until_complete(fut)

self.assertEqual(fut.result(), 42)
self.assertEqual(fut.result(), 42)

def test_task_yield_from_exception_propagation(self):
CHK = 0
def test_task_yield_from_exception_propagation(self):
non_local = {'CHK': 0}

@asyncio.coroutine
def bar():
yield
yield
1/0
@asyncio.coroutine
def bar():
yield
yield
1/0

@greenio.task
def foo():
greenio.yield_from(asyncio.Task(bar()))
@greenio.task
def foo():
greenio.yield_from(bar())

@asyncio.coroutine
def test():
try:
return (yield from foo())
except ZeroDivisionError:
nonlocal CHK
CHK += 1
@asyncio.coroutine
def test():
try:
return (yield from foo())
except ZeroDivisionError:
non_local['CHK'] += 1

self.loop.run_until_complete(test())
self.assertEqual(CHK, 1)
self.loop.run_until_complete(test())
self.assertEqual(non_local['CHK'], 1)

def test_task_yield_from_coroutine(self):
@asyncio.coroutine
def bar():
yield from []
return 5
def test_task_yield_from_coroutine(self):
@asyncio.coroutine
def bar():
yield from []
return 5

@greenio.task
def foo():
return greenio.yield_from(bar())
@greenio.task
def foo():
return greenio.yield_from(bar())

fut = foo()
self.loop.run_until_complete(fut)
self.assertEqual(fut.result(), 5)
fut = foo()
self.loop.run_until_complete(fut)
self.assertEqual(fut.result(), 5)
''')

def test_task_yield_from_invalid(self):
def bar():
pass
err_msg = 'greenlet.yield_from was supposed to receive only Futures, '

@asyncio.coroutine
def foo():
with self.assertRaisesRegex(
RuntimeError,
'"greenio.yield_from" was supposed to be called'):
greenio.yield_from(bar)
with self.assertRaisesRegex(RuntimeError, err_msg):
greenio.yield_from(5)

self.loop.run_until_complete(foo())