├── .gitignore ├── .travis.yml ├── AUTHORS ├── LICENSE ├── MANIFEST.in ├── NEWS ├── README.rst ├── examples ├── httpget.py ├── mysql.py ├── postgres.py └── simple.py ├── greenio ├── __init__.py └── socket.py ├── runtests.py ├── setup.py └── tests ├── test_asyncio_trollius.py ├── test_socket.py ├── test_tasks.py └── test_tasks_trollius.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | *.so 4 | .DS_Store 5 | /build 6 | __pycache__/ 7 | MANIFEST 8 | dist/ 9 | greenio.egg-info/ 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - 2.7 5 | - 3.3 6 | - 3.4 7 | 8 | install: 9 | - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install trollius>=1.0; fi 10 | - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install asyncio; fi 11 | - python setup.py install 12 | 13 | script: 14 | - python runtests.py 15 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Yury Selivanov 2 | Nikolay Kim 3 | Victor Stinner 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013, 2014 Yury Selivanov and contributors. See AUTHORS 2 | for more details. 3 | 4 | All rights reserved. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in LICENSE README 2 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | 0.6.0 2 | ----- 3 | 4 | - Added compatibility with Python 2.7 and Trollius. 5 | - Use of new 'loop.create_task' API. 6 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://travis-ci.org/1st1/greenio.svg?branch=master 2 | :target: https://travis-ci.org/1st1/greenio 3 | 4 | 5 | Support for greenlets in asyncio. 6 | 7 | Requires asyncio, greenlet and Python 3.3 / 3.4. 8 | 9 | See examples and unittests for details. 10 | To run tests: "$ python3 runtests.py" 11 | 12 | License: Apache 2.0 13 | -------------------------------------------------------------------------------- /examples/httpget.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) 2013 Yury Selivanov 3 | # License: Apache 2.0 4 | ## 5 | 6 | 7 | import greenio 8 | import asyncio 9 | 10 | 11 | @asyncio.coroutine 12 | def sleeper(): 13 | while True: 14 | yield from asyncio.sleep(0.05) 15 | print('.') 16 | 17 | 18 | @greenio.task 19 | def get(): 20 | from greenio import socket 21 | 22 | sock = socket.create_connection(('python.org', 80)) 23 | print('connected', sock._sock) 24 | sock.sendall(b'GET / HTTP/1.0\r\n\r\n') 25 | print('sent') 26 | print('rcvd', sock.recv(1024)) 27 | sock.close() 28 | 29 | 30 | @asyncio.coroutine 31 | def run(): 32 | yield from asyncio.wait( 33 | [get(), sleeper()], return_when=asyncio.FIRST_COMPLETED) 34 | 35 | 36 | asyncio.set_event_loop_policy(greenio.GreenEventLoopPolicy()) 37 | loop = asyncio.get_event_loop() 38 | loop.run_until_complete(run()) 39 | loop.close() 40 | -------------------------------------------------------------------------------- /examples/mysql.py: -------------------------------------------------------------------------------- 1 | """PyMySQL example""" 2 | import asyncio 3 | import socket 4 | from pymysql import connections 5 | from greenio import socket as greensocket 6 | 7 | 8 | class GreenConnection(connections.Connection): 9 | 10 | def _connect(self): 11 | try: 12 | if self.unix_socket: 13 | raise NotImplementedError() 14 | else: 15 | sock = greensocket.socket(socket.AF_INET, socket.SOCK_STREAM) 16 | sock.connect((self.host, self.port)) 17 | self.host_info = "socket %s:%d" % (self.host, self.port) 18 | if self.no_delay: 19 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 20 | self.socket = sock 21 | self.rfile = self.socket.makefile("rb") 22 | self.wfile = self.socket.makefile("wb") 23 | self._get_server_information() 24 | self._request_authentication() 25 | self._send_autocommit_mode() 26 | except socket.error as e: 27 | raise Exception( 28 | 2003, "Can't connect to MySQL server on %r (%s)" % ( 29 | self.host, e.args[0])) 30 | 31 | 32 | if __name__ == '__main__': 33 | import greenio 34 | import time 35 | 36 | @asyncio.coroutine 37 | def sleeper(): 38 | # show that we're not blocked 39 | while True: 40 | yield from asyncio.sleep(0.2) 41 | print('.') 42 | 43 | @greenio.task 44 | def db(): 45 | conn = GreenConnection(host='localhost') 46 | 47 | try: 48 | with conn as cur: 49 | print('>> sleeping') 50 | st = time.monotonic() 51 | cur.execute('SELECT SLEEP(2)') 52 | en = time.monotonic() - st 53 | assert en >= 2 54 | print('<< sleeping {:.3f}s'.format(en)) 55 | 56 | cur.execute('SELECT 42') 57 | print('"SELECT 42" -> {!r}'.format(cur.fetchone())) 58 | 59 | print('>> sleeping') 60 | st = time.monotonic() 61 | cur.execute('SELECT SLEEP(1)') 62 | en = time.monotonic() - st 63 | assert en >= 1 64 | print('<< sleeping {:.3f}s'.format(en)) 65 | finally: 66 | conn.close() 67 | 68 | @asyncio.coroutine 69 | def run(): 70 | yield from asyncio.wait([db(), sleeper()], 71 | return_when=asyncio.FIRST_COMPLETED) 72 | 73 | asyncio.set_event_loop_policy(greenio.GreenEventLoopPolicy()) 74 | loop = asyncio.get_event_loop() 75 | loop.run_until_complete(run()) 76 | loop.close() 77 | -------------------------------------------------------------------------------- /examples/postgres.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) 2008-2012 Sprymix Inc. 3 | # License: Apache 2.0 4 | ## 5 | 6 | 7 | """A non-blocking adapter for py-postgresql library. 8 | WARNING: This is just an experiment; use in production is not recommended. 9 | """ 10 | 11 | 12 | import postgresql 13 | from postgresql.python import socket as pg_socket 14 | from postgresql.driver import pq3 15 | 16 | from greenio import socket as greensocket 17 | 18 | 19 | class SocketConnector: 20 | def create_socket_factory(self, **params): 21 | params['socket_extra'] = {'async': self.async} 22 | return SocketFactory(**params) 23 | 24 | def __init__(self, async=False): 25 | self.async = async 26 | 27 | 28 | class IPConnector(pq3.IPConnector, SocketConnector): 29 | def __init__(self, *args, async=False, **kw): 30 | pq3.IPConnector.__init__(self, *args, **kw) 31 | SocketConnector.__init__(self, async=async) 32 | 33 | 34 | class IP4(IPConnector, pq3.IP4): 35 | pass 36 | 37 | 38 | class IP6(IPConnector, pq3.IP6): 39 | pass 40 | 41 | 42 | class Host(SocketConnector, pq3.Host): 43 | def __init__(self, *args, async=False, **kw): 44 | pq3.Host.__init__(self, *args, **kw) 45 | SocketConnector.__init__(self, async=async) 46 | 47 | 48 | class Unix(SocketConnector, pq3.Unix): 49 | def __init__(self, unix=None, async=False, **kw): 50 | pq3.Unix.__init__(self, unix=unix, **kw) 51 | SocketConnector.__init__(self, async=async) 52 | 53 | 54 | class SocketFactory(pg_socket.SocketFactory): 55 | def __init__(self, *args, socket_extra=None, **kw): 56 | super().__init__(*args, **kw) 57 | self.async = (socket_extra.get('async', False) 58 | if socket_extra else False) 59 | 60 | def __call__(self, timeout=None): 61 | if self.async: 62 | s = greensocket.socket(*self.socket_create) 63 | s.connect(self.socket_connect) 64 | return s 65 | else: 66 | return super().__call__(timeout) 67 | 68 | 69 | class Driver(pq3.Driver): 70 | def ip4(self, **kw): 71 | return IP4(driver=self, **kw) 72 | 73 | def ip6(self, **kw): 74 | return IP6(driver=self, **kw) 75 | 76 | def host(self, **kw): 77 | return Host(driver=self, **kw) 78 | 79 | def unix(self, **kw): 80 | return Unix(driver=self, **kw) 81 | 82 | 83 | driver = Driver() 84 | 85 | 86 | def connector_factory(iri, async=False): 87 | params = postgresql.iri.parse(iri) 88 | settings = params.setdefault('settings', {}) 89 | settings['standard_conforming_strings'] = 'on' 90 | params['async'] = async 91 | return driver.fit(**params) 92 | 93 | 94 | if __name__ == '__main__': 95 | import greenio 96 | import time 97 | import asyncio 98 | 99 | @asyncio.coroutine 100 | def sleeper(): 101 | # show that we're not blocked 102 | while True: 103 | yield from asyncio.sleep(0.4) 104 | print('.') 105 | 106 | @greenio.task 107 | def db(): 108 | connection = connector_factory( 109 | 'pq://postgres@localhost:5432', async=True)() 110 | connection.connect() 111 | 112 | try: 113 | print('>> sleeping') 114 | st = time.monotonic() 115 | connection.execute('SELECT pg_sleep(2)') 116 | en = time.monotonic() - st 117 | assert en >= 2 118 | print('<< sleeping {:.3f}s'.format(en)) 119 | 120 | ps = connection.prepare('SELECT 42') 121 | print('"SELECT 42" -> {!r}'.format(ps())) 122 | finally: 123 | connection.close() 124 | 125 | @asyncio.coroutine 126 | def run(): 127 | yield from asyncio.wait( 128 | [db(), sleeper()], return_when=asyncio.FIRST_COMPLETED) 129 | 130 | asyncio.set_event_loop_policy(greenio.GreenEventLoopPolicy()) 131 | loop = asyncio.get_event_loop() 132 | loop.run_until_complete(run()) 133 | loop.close() 134 | -------------------------------------------------------------------------------- /examples/simple.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) 2013 Yury Selivanov 3 | # License: Apache 2.0 4 | ## 5 | 6 | 7 | import greenio 8 | import asyncio 9 | 10 | 11 | @asyncio.coroutine 12 | def bar(): 13 | yield 14 | return 30 15 | 16 | 17 | @asyncio.coroutine 18 | def foo(): 19 | bar_result = greenio.yield_from(asyncio.Task(bar())) 20 | return bar_result + 12 21 | 22 | 23 | @greenio.task 24 | def test(): 25 | print((yield from foo())) 26 | 27 | 28 | asyncio.set_event_loop_policy(greenio.GreenEventLoopPolicy()) 29 | loop = asyncio.get_event_loop() 30 | loop.run_until_complete(test()) 31 | loop.close() 32 | -------------------------------------------------------------------------------- /greenio/__init__.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) 2013 Yury Selivanov 3 | # License: Apache 2.0 4 | ## 5 | 6 | """greenio package allows to compose greenlets and asyncio coroutines.""" 7 | 8 | __all__ = ['task', 'yield_from'] 9 | 10 | 11 | import greenlet 12 | import sys 13 | 14 | try: 15 | import asyncio 16 | except ImportError: 17 | asyncio = None 18 | try: 19 | import trollius 20 | except ImportError: 21 | trollius = None 22 | if asyncio is None: 23 | raise 24 | if asyncio is None: 25 | asyncio = trollius 26 | 27 | def _create_task(coro, loop): 28 | if loop is None: 29 | loop = asyncio.get_event_loop() 30 | if hasattr(loop, 'create_task'): 31 | return loop.create_task(coro) 32 | else: 33 | return GreenTask(coro, loop=loop) 34 | 35 | 36 | if trollius is not None: 37 | def _async(future, loop): 38 | # trollius iscoroutine() accepts trollius and asyncio coroutine 39 | # objects 40 | if trollius.iscoroutine(future): 41 | return _create_task(future, loop) 42 | else: 43 | return future 44 | else: 45 | def _async(future, loop): 46 | if asyncio.iscoroutine(future): 47 | return _create_task(future, loop) 48 | else: 49 | return future 50 | 51 | 52 | _FUTURE_CLASSES = (asyncio.Future,) 53 | if trollius is not None and trollius is not asyncio: 54 | _FUTURE_CLASSES += (trollius.Future,) 55 | 56 | import sys 57 | 58 | 59 | class _LoopGreenlet(greenlet.greenlet): 60 | """Main greenlet (analog to main thread) for the event-loop. 61 | 62 | It's a policy task to provide event-loop implementation with 63 | its "run_*" methods executed in _LoopGreenlet context""" 64 | 65 | 66 | class _TaskGreenlet(greenlet.greenlet): 67 | """Each task (and its subsequent coroutines) decorated with 68 | ``@greenio.task`` is executed in this greenlet""" 69 | 70 | 71 | class _GreenTaskMixin(object): 72 | def __init__(self, *args, **kwargs): 73 | self._greenlet = None 74 | super(_GreenTaskMixin, self).__init__(*args, **kwargs) 75 | 76 | def _step(self, value=None, exc=None): 77 | if self._greenlet is None: 78 | # Means that the task is not currently in a suspended greenlet 79 | # waiting for results for "yield_from" 80 | ovr = super(_GreenTaskMixin, self)._step 81 | self._greenlet = _TaskGreenlet(ovr) 82 | 83 | # Store a reference to the current task for "yield_from" 84 | self._greenlet.task = self 85 | 86 | # Now invoke overloaded "Task._step" in "_TaskGreenlet" 87 | result = self._greenlet.switch(value, exc) 88 | 89 | # If "result" is "_YIELDED" it means that the "yield_from" 90 | # method was called 91 | if result is not _YIELDED: 92 | # And if not - then task jumped out of greenlet without 93 | # calling "yield_from" 94 | self._greenlet.task = None 95 | self._greenlet = None 96 | else: 97 | self.__class__._current_tasks.pop(self._loop) 98 | else: 99 | # The task is in the greenlet, that means that we have a result 100 | # for the "yield_from" 101 | 102 | self.__class__._current_tasks[self._loop] = self 103 | 104 | if exc is not None: 105 | if hasattr(exc, '__traceback__'): 106 | tb = exc.__traceback__ 107 | else: 108 | tb = sys.exc_info()[2] 109 | result = self._greenlet.throw( 110 | type(exc), exc, tb) 111 | else: 112 | result = self._greenlet.switch(value) 113 | 114 | # Again, if "result" is "_YIELDED" then we just called "yield_from" 115 | # again 116 | if result is not _YIELDED: 117 | self._greenlet.task = None 118 | self._greenlet = None 119 | else: 120 | self.__class__._current_tasks.pop(self._loop) 121 | 122 | 123 | class _GreenLoopMixin(object): 124 | def _green_run(self, method, args, kwargs): 125 | return _LoopGreenlet(method).switch(*args, **kwargs) 126 | 127 | def run_until_complete(self, *args, **kwargs): 128 | ovr = super(_GreenLoopMixin, self).run_until_complete 129 | return self._green_run(ovr, args, kwargs) 130 | 131 | def run_forever(self, *args, **kwargs): 132 | ovr = super(_GreenLoopMixin, self).run_forever 133 | return self._green_run(ovr, args, kwargs) 134 | 135 | 136 | class GreenTask(_GreenTaskMixin, asyncio.Task): 137 | pass 138 | 139 | 140 | class GreenUnixSelectorLoop(_GreenLoopMixin, asyncio.SelectorEventLoop): 141 | def create_task(self, coro): 142 | return GreenTask(coro, loop=self) 143 | 144 | 145 | class GreenEventLoopPolicy(asyncio.DefaultEventLoopPolicy): 146 | def new_event_loop(self): 147 | return GreenUnixSelectorLoop() 148 | 149 | 150 | if trollius is not None: 151 | if trollius is not asyncio: 152 | class GreenTrolliusTask(_GreenTaskMixin, trollius.Task): 153 | pass 154 | 155 | class GreenTrolliusUnixSelectorLoop(_GreenLoopMixin, 156 | trollius.SelectorEventLoop): 157 | def create_task(self, coro): 158 | return GreenTrolliusTask(coro) 159 | 160 | class GreenTrolliusEventLoopPolicy(asyncio.DefaultEventLoopPolicy): 161 | 162 | def new_event_loop(self): 163 | return GreenTrolliusUnixSelectorLoop() 164 | else: 165 | GreenTrolliusTask = GreenTask 166 | GreenTrolliusUnixSelectorLoop = GreenUnixSelectorLoop 167 | GreenTrolliusEventLoopPolicy = GreenEventLoopPolicy 168 | 169 | 170 | def yield_from(future, loop=None): 171 | """A function to use instead of ``yield from`` statement.""" 172 | 173 | future = _async(future, loop) 174 | 175 | gl = greenlet.getcurrent() 176 | 177 | if __debug__: 178 | if not isinstance(gl.parent, _LoopGreenlet): 179 | raise RuntimeError( 180 | '"greenio.yield_from" requires GreenEventLoopPolicy ' 181 | 'or compatible') 182 | # or something went horribly wrong... 183 | 184 | if not isinstance(gl, _TaskGreenlet): 185 | raise RuntimeError( 186 | '"greenio.yield_from" was supposed to be called from a ' 187 | '"greenio.task" or a subsequent coroutine') 188 | # ...ditto 189 | 190 | task = gl.task 191 | 192 | if not isinstance(future, _FUTURE_CLASSES): 193 | raise RuntimeError( 194 | 'greenlet.yield_from was supposed to receive only Futures, ' 195 | 'got {!r} in task {!r}'.format(future, task)) 196 | 197 | # "_wakeup" will call the "_step" method (which we overloaded in 198 | # GreenTask, and therefore wakeup the awaiting greenlet) 199 | future.add_done_callback(task._wakeup) 200 | task._fut_waiter = future 201 | 202 | # task cancellation has been delayed. 203 | if task._must_cancel: 204 | if task._fut_waiter.cancel(): 205 | task._must_cancel = False 206 | 207 | # Jump out of the current task greenlet (we'll return to GreenTask._step) 208 | return gl.parent.switch(_YIELDED) 209 | 210 | 211 | def task(func, loop=None): 212 | """A decorator, allows use of ``yield_from`` in the decorated or 213 | subsequent coroutines.""" 214 | 215 | coro_func = asyncio.coroutine(func) 216 | 217 | def task_wrapper(*args, **kwds): 218 | coro_obj = coro_func(*args, **kwds) 219 | return _create_task(coro_obj, loop) 220 | 221 | return task_wrapper 222 | 223 | 224 | class _YIELDED(object): 225 | """Marker, don't use it""" 226 | -------------------------------------------------------------------------------- /greenio/socket.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) 2013 Yury Selivanov 3 | # License: Apache 2.0 4 | ## 5 | """Greensocket (non-blocking) for asyncio. 6 | 7 | Use ``greenio.socket`` in the same way as you would use stdlib's 8 | ``socket.socket`` in ``greenio.task`` tasks or coroutines invoked 9 | from them. 10 | """ 11 | from __future__ import absolute_import 12 | from greenio import asyncio 13 | from socket import error, SOCK_STREAM 14 | from socket import socket as std_socket 15 | 16 | from . import yield_from 17 | from . import _GreenLoopMixin 18 | 19 | 20 | class socket: 21 | 22 | def __init__(self, *args, **kwargs): 23 | _from_sock = kwargs.pop('_from_sock', None) 24 | if _from_sock: 25 | own_sock = None 26 | self._sock = _from_sock 27 | else: 28 | own_sock = std_socket(*args, **kwargs) 29 | self._sock = own_sock 30 | try: 31 | self._sock.setblocking(False) 32 | self._loop = asyncio.get_event_loop() 33 | assert isinstance(self._loop, _GreenLoopMixin), \ 34 | 'greenio event loop is required' 35 | except: 36 | if own_sock is not None: 37 | # An unexpected error has occurred. Close the 38 | # socket object if it was created in __init__. 39 | own_sock.close() 40 | raise 41 | 42 | @classmethod 43 | def from_socket(cls, sock): 44 | return cls(_from_sock=sock) 45 | 46 | @property 47 | def family(self): 48 | return self._sock.family 49 | 50 | @property 51 | def type(self): 52 | return self._sock.type 53 | 54 | @property 55 | def proto(self): 56 | return self._sock.proto 57 | 58 | def _proxy(attr): 59 | def proxy(self, *args, **kwargs): 60 | meth = getattr(self._sock, attr) 61 | return meth(*args, **kwargs) 62 | 63 | proxy.__name__ = attr 64 | proxy.__qualname__ = attr 65 | proxy.__doc__ = getattr(getattr(std_socket, attr), '__doc__', None) 66 | return proxy 67 | 68 | def _copydoc(func): 69 | func.__doc__ = getattr( 70 | getattr(std_socket, func.__name__), '__doc__', None) 71 | return func 72 | 73 | @_copydoc 74 | def setblocking(self, flag): 75 | if flag: 76 | raise error('greenio.socket does not support blocking mode') 77 | 78 | @_copydoc 79 | def recv(self, nbytes): 80 | fut = self._loop.sock_recv(self._sock, nbytes) 81 | yield_from(fut) 82 | return fut.result() 83 | 84 | @_copydoc 85 | def connect(self, addr): 86 | fut = self._loop.sock_connect(self._sock, addr) 87 | yield_from(fut) 88 | return fut.result() 89 | 90 | @_copydoc 91 | def sendall(self, data, flags=0): 92 | assert not flags 93 | fut = self._loop.sock_sendall(self._sock, data) 94 | yield_from(fut) 95 | return fut.result() 96 | 97 | @_copydoc 98 | def send(self, data, flags=0): 99 | self.sendall(data, flags) 100 | return len(data) 101 | 102 | @_copydoc 103 | def accept(self): 104 | fut = self._loop.sock_accept(self._sock) 105 | yield_from(fut) 106 | sock, addr = fut.result() 107 | return self.__class__.from_socket(sock), addr 108 | 109 | @_copydoc 110 | def makefile(self, mode, *args, **kwargs): 111 | if mode == 'rb': 112 | return ReadFile(self._loop, self._sock) 113 | elif mode == 'wb': 114 | return WriteFile(self._loop, self._sock) 115 | raise NotImplementedError 116 | 117 | bind = _proxy('bind') 118 | listen = _proxy('listen') 119 | getsockname = _proxy('getsockname') 120 | getpeername = _proxy('getpeername') 121 | gettimeout = _proxy('gettimeout') 122 | getsockopt = _proxy('getsockopt') 123 | setsockopt = _proxy('setsockopt') 124 | fileno = _proxy('fileno') 125 | # socket.detach() was added in Python 3.2 126 | if hasattr(std_socket, 'detach'): 127 | detach = _proxy('detach') 128 | close = _proxy('close') 129 | shutdown = _proxy('shutdown') 130 | 131 | del _copydoc, _proxy 132 | 133 | 134 | class ReadFile: 135 | 136 | def __init__(self, loop, sock): 137 | self._loop = loop 138 | self._sock = sock 139 | self._buf = bytearray() 140 | 141 | def read(self, size): 142 | while 1: 143 | if size <= len(self._buf): 144 | data = self._buf[:size] 145 | del self._buf[:size] 146 | return data 147 | 148 | fut = self._loop.sock_recv(self._sock, size - len(self._buf)) 149 | yield_from(fut) 150 | res = fut.result() 151 | self._buf.extend(res) 152 | 153 | if size <= len(self._buf): 154 | data = self._buf[:size] 155 | del self._buf[:size] 156 | return data 157 | else: 158 | data = self._buf[:] 159 | del self._buf[:] 160 | return data 161 | 162 | def close(self): 163 | pass 164 | 165 | 166 | class WriteFile: 167 | 168 | def __init__(self, loop, sock): 169 | self._loop = loop 170 | self._sock = sock 171 | 172 | def write(self, data): 173 | fut = self._loop.sock_sendall(self._sock, data) 174 | yield_from(fut) 175 | return fut.result() 176 | 177 | def flush(self): 178 | pass 179 | 180 | def close(self): 181 | pass 182 | 183 | 184 | def create_connection(address, timeout=None): 185 | loop = asyncio.get_event_loop() 186 | host, port = address 187 | 188 | rslt = yield_from( 189 | loop.getaddrinfo(host, port, family=0, type=SOCK_STREAM)) 190 | 191 | for res in rslt: 192 | af, socktype, proto, canonname, sa = res 193 | sock = None 194 | 195 | try: 196 | sock = socket(af, socktype, proto) 197 | sock.connect(sa) 198 | return sock 199 | except ConnectionError: 200 | if sock: 201 | sock.close() 202 | 203 | raise error('unable to connect to {!r}'.format(address)) 204 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | # Copied from Tulip codebase, covered by Apache 2.0 License 2 | # 3 | 4 | """Run all unittests. 5 | 6 | Usage: 7 | python3 runtests.py [-v] [-q] [pattern] ... 8 | 9 | Where: 10 | -v: verbose 11 | -q: quiet 12 | pattern: optional regex patterns to match test ids (default all tests) 13 | 14 | Note that the test id is the fully qualified name of the test, 15 | including package, module, class and method, 16 | e.g. 'tests.events_test.PolicyTests.testPolicy'. 17 | 18 | runtests.py with --coverage argument is equivalent of: 19 | 20 | $(COVERAGE) run --branch runtests.py -v 21 | $(COVERAGE) html $(list of files) 22 | $(COVERAGE) report -m $(list of files) 23 | 24 | """ 25 | 26 | # Originally written by Beech Horn (for NDB). 27 | 28 | from __future__ import print_function 29 | import argparse 30 | import logging 31 | import os 32 | import re 33 | import sys 34 | import subprocess 35 | import unittest 36 | if sys.version_info >= (3, 3): 37 | import importlib.machinery 38 | else: 39 | import imp 40 | 41 | ARGS = argparse.ArgumentParser(description="Run all unittests.") 42 | ARGS.add_argument( 43 | '-v', action="store", dest='verbose', 44 | nargs='?', const=1, type=int, default=0, help='verbose') 45 | ARGS.add_argument( 46 | '-x', action="store_true", dest='exclude', help='exclude tests') 47 | ARGS.add_argument( 48 | '-q', action="store_true", dest='quiet', help='quiet') 49 | ARGS.add_argument( 50 | '--tests', action="store", dest='testsdir', default='tests', 51 | help='tests directory') 52 | ARGS.add_argument( 53 | '--coverage', action="store", dest='coverage', nargs='?', const='', 54 | help='enable coverage report and provide python files directory') 55 | ARGS.add_argument( 56 | 'pattern', action="store", nargs="*", 57 | help='optional regex patterns to match test ids (default all tests)') 58 | 59 | COV_ARGS = argparse.ArgumentParser(description="Run all unittests.") 60 | COV_ARGS.add_argument( 61 | '--coverage', action="store", dest='coverage', nargs='?', const='', 62 | help='enable coverage report and provide python files directory') 63 | 64 | 65 | if sys.version_info >= (3, 3): 66 | def load_module(modname, sourcefile): 67 | loader = importlib.machinery.SourceFileLoader(modname, sourcefile) 68 | return loader.load_module() 69 | else: 70 | def load_module(modname, sourcefile): 71 | return imp.load_source(modname, sourcefile) 72 | 73 | 74 | def load_modules(basedir, suffix='.py'): 75 | def list_dir(prefix, dir): 76 | files = [] 77 | 78 | modpath = os.path.join(dir, '__init__.py') 79 | if os.path.isfile(modpath): 80 | mod = os.path.split(dir)[-1] 81 | files.append(('{}{}'.format(prefix, mod), modpath)) 82 | 83 | prefix = '{}{}.'.format(prefix, mod) 84 | 85 | for name in os.listdir(dir): 86 | path = os.path.join(dir, name) 87 | 88 | if os.path.isdir(path): 89 | files.extend(list_dir('{}{}.'.format(prefix, name), path)) 90 | else: 91 | if (name != '__init__.py' and 92 | name.endswith(suffix) and 93 | not name.startswith(('.', '_'))): 94 | files.append(('{}{}'.format(prefix, name[:-3]), path)) 95 | 96 | return files 97 | 98 | mods = [] 99 | for modname, sourcefile in list_dir('', basedir): 100 | if modname == 'runtests': 101 | continue 102 | if (modname in ('test_tasks', 'test_asyncio_trollius') 103 | and sys.version_info <= (3, 3)): 104 | print("Skipping '{0}': need at least Python 3.3".format(modname), 105 | file=sys.stderr) 106 | continue 107 | try: 108 | mod = load_module(modname, sourcefile) 109 | mods.append((mod, sourcefile)) 110 | except Exception as err: 111 | print("Skipping '{}': {}".format(modname, err), file=sys.stderr) 112 | 113 | return mods 114 | 115 | 116 | def load_tests(testsdir, includes=(), excludes=()): 117 | mods = [mod for mod, _ in load_modules(testsdir)] 118 | 119 | loader = unittest.TestLoader() 120 | suite = unittest.TestSuite() 121 | 122 | for mod in mods: 123 | for name in set(dir(mod)): 124 | if name.endswith('Tests'): 125 | test_module = getattr(mod, name) 126 | tests = loader.loadTestsFromTestCase(test_module) 127 | if includes: 128 | tests = [test 129 | for test in tests 130 | if any(re.search(pat, test.id()) 131 | for pat in includes)] 132 | if excludes: 133 | tests = [test 134 | for test in tests 135 | if not any(re.search(pat, test.id()) 136 | for pat in excludes)] 137 | suite.addTests(tests) 138 | 139 | return suite 140 | 141 | 142 | def runtests(): 143 | args = ARGS.parse_args() 144 | 145 | testsdir = os.path.abspath(args.testsdir) 146 | if not os.path.isdir(testsdir): 147 | print("Tests directory is not found: {}\n".format(testsdir)) 148 | ARGS.print_help() 149 | return 150 | 151 | excludes = includes = [] 152 | if args.exclude: 153 | excludes = args.pattern 154 | else: 155 | includes = args.pattern 156 | 157 | v = 0 if args.quiet else args.verbose + 1 158 | 159 | tests = load_tests(args.testsdir, includes, excludes) 160 | logger = logging.getLogger() 161 | if v == 0: 162 | logger.setLevel(logging.CRITICAL) 163 | elif v == 1: 164 | logger.setLevel(logging.ERROR) 165 | elif v == 2: 166 | logger.setLevel(logging.WARNING) 167 | elif v == 3: 168 | logger.setLevel(logging.INFO) 169 | elif v >= 4: 170 | logger.setLevel(logging.DEBUG) 171 | result = unittest.TextTestRunner(verbosity=v).run(tests) 172 | sys.exit(not result.wasSuccessful()) 173 | 174 | 175 | def runcoverage(sdir, args): 176 | """ 177 | To install coverage3 for Python 3, you need: 178 | - Distribute (http://packages.python.org/distribute/) 179 | 180 | What worked for me: 181 | - download http://python-distribute.org/distribute_setup.py 182 | * curl -O http://python-distribute.org/distribute_setup.py 183 | - python3 distribute_setup.py 184 | - python3 -m easy_install coverage 185 | """ 186 | try: 187 | import coverage 188 | except ImportError: 189 | print("Coverage package is not found.") 190 | print(runcoverage.__doc__) 191 | return 192 | 193 | sdir = os.path.abspath(sdir) 194 | if not os.path.isdir(sdir): 195 | print("Python files directory is not found: {}\n".format(sdir)) 196 | ARGS.print_help() 197 | return 198 | 199 | mods = [source for _, source in load_modules(sdir)] 200 | coverage = [sys.executable, '-m', 'coverage'] 201 | 202 | try: 203 | subprocess.check_call( 204 | coverage + ['run', '--branch', 'runtests.py'] + args) 205 | except: 206 | pass 207 | else: 208 | subprocess.check_call(coverage + ['html'] + mods) 209 | subprocess.check_call(coverage + ['report'] + mods) 210 | 211 | 212 | if __name__ == '__main__': 213 | if '--coverage' in sys.argv: 214 | cov_args, args = COV_ARGS.parse_known_args() 215 | runcoverage(cov_args.coverage, args) 216 | else: 217 | runtests() 218 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | extra = {} 5 | f = open('README.rst', 'r') 6 | try: 7 | extra['long_description'] = f.read() 8 | finally: 9 | f.close() 10 | 11 | 12 | setup( 13 | name='greenio', 14 | version='0.6.0', 15 | description="Greenlets for asyncio (PEP 3156).", 16 | url='https://github.com/1st1/greenio/', 17 | license='Apache 2.0', 18 | packages=['greenio'], 19 | install_requires=['greenlet'], 20 | ) 21 | -------------------------------------------------------------------------------- /tests/test_asyncio_trollius.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test interoperability between asyncio and trollius. Trollius event loop should 3 | support asyncio coroutines. 4 | """ 5 | 6 | import asyncio 7 | import greenio 8 | import trollius 9 | import unittest 10 | from trollius import From, Return 11 | from trollius.test_utils import TestCase 12 | 13 | 14 | class TrolliusEventLoopTests(TestCase): 15 | def setUp(self): 16 | policy = greenio.GreenTrolliusEventLoopPolicy() 17 | asyncio.set_event_loop_policy(policy) 18 | trollius.set_event_loop_policy(policy) 19 | self.loop = trollius.new_event_loop() 20 | policy.set_event_loop(self.loop) 21 | 22 | def tearDown(self): 23 | self.loop.close() 24 | asyncio.set_event_loop_policy(None) 25 | 26 | def test_trollius_coroutine(self): 27 | @trollius.coroutine 28 | def bar(): 29 | yield From(None) 30 | raise Return(30) 31 | 32 | @trollius.coroutine 33 | def foo(): 34 | bar_result = greenio.yield_from(bar()) 35 | return bar_result + 12 36 | 37 | @greenio.task 38 | def test(): 39 | res = yield From(foo()) 40 | raise Return(res) 41 | 42 | fut = test() 43 | self.loop.run_until_complete(fut) 44 | 45 | self.assertEqual(fut.result(), 42) 46 | 47 | def test_asyncio_coroutine(self): 48 | @asyncio.coroutine 49 | def bar(): 50 | yield from [] 51 | return 30 52 | 53 | @asyncio.coroutine 54 | def foo(): 55 | bar_result = greenio.yield_from(bar()) 56 | return bar_result + 12 57 | 58 | @greenio.task 59 | def test(): 60 | res = yield From(foo()) 61 | raise Return(res) 62 | 63 | fut = test() 64 | self.loop.run_until_complete(fut) 65 | 66 | self.assertEqual(fut.result(), 42) 67 | 68 | 69 | if __name__ == '__main__': 70 | unittest.main() 71 | -------------------------------------------------------------------------------- /tests/test_socket.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) 2013 Yury Selivanov 3 | # License: Apache 2.0 4 | ## 5 | 6 | 7 | try: 8 | import asyncio 9 | except ImportError: 10 | asyncio = None 11 | try: 12 | import trollius 13 | except ImportError: 14 | trollius = None 15 | if asyncio is None and trollius is None: 16 | raise ImportError("asyncio and trollius modules are missing") 17 | 18 | try: 19 | from trollius.test_utils import TestCase 20 | except ImportError: 21 | from unittest import TestCase 22 | 23 | import greenio 24 | import greenio.socket as greensocket 25 | 26 | import socket as std_socket 27 | 28 | 29 | class SocketMixin(object): 30 | asyncio = None 31 | event_loop_policy = greenio.GreenEventLoopPolicy 32 | 33 | def setUp(self): 34 | policy = self.event_loop_policy() 35 | self.asyncio.set_event_loop_policy(policy) 36 | self.loop = policy.new_event_loop() 37 | policy.set_event_loop(self.loop) 38 | 39 | def tearDown(self): 40 | self.loop.close() 41 | self.asyncio.set_event_loop_policy(None) 42 | 43 | def test_socket_wrong_event_loop(self): 44 | loop = self.asyncio.DefaultEventLoopPolicy().new_event_loop() 45 | self.addCleanup(loop.close) 46 | self.asyncio.set_event_loop(loop) 47 | self.assertRaises(AssertionError, greensocket.socket) 48 | 49 | def test_socket_docs(self): 50 | self.assertIn('accept connections', greensocket.socket.listen.__doc__) 51 | # On Python 2.7, socket.recv() has no documentation 52 | if std_socket.socket.recv.__doc__: 53 | self.assertIn('Receive', greensocket.socket.recv.__doc__) 54 | 55 | def test_socket_setblocking(self): 56 | sock = greensocket.socket() 57 | self.assertEquals(sock.gettimeout(), 0) 58 | with self.assertRaisesRegex( 59 | greensocket.error, 'does not support blocking mode'): 60 | sock.setblocking(True) 61 | sock.close() 62 | 63 | def test_socket_echo(self): 64 | import threading 65 | import time 66 | 67 | non_local = {'addr': None, 'check': 0} 68 | ev = threading.Event() 69 | 70 | def server(sock_factory): 71 | socket = sock_factory() 72 | socket.bind(('127.0.0.1', 0)) 73 | 74 | assert socket.fileno() is not None 75 | 76 | non_local['addr'] = socket.getsockname() 77 | socket.listen(1) 78 | 79 | ev.set() 80 | 81 | sock, client_addrs = socket.accept() 82 | assert isinstance(sock, sock_factory) 83 | 84 | data = b'' 85 | while not data.endswith(b'\r'): 86 | data += sock.recv(1024) 87 | 88 | sock.sendall(data) 89 | 90 | ev.wait() 91 | ev.clear() 92 | 93 | sock.close() 94 | socket.close() 95 | 96 | def client(sock_factory): 97 | ev.wait() 98 | ev.clear() 99 | time.sleep(0.1) 100 | 101 | sock = sock_factory() 102 | sock.connect(non_local['addr']) 103 | 104 | data = b'hello greenlets\r' 105 | sock.sendall(data) 106 | 107 | rep = b'' 108 | while not rep.endswith(b'\r'): 109 | rep += sock.recv(1024) 110 | 111 | self.assertEqual(data, rep) 112 | ev.set() 113 | 114 | non_local['check'] += 1 115 | 116 | sock.close() 117 | 118 | ev.clear() 119 | thread = threading.Thread(target=client, args=(std_socket.socket,)) 120 | thread.setDaemon(True) 121 | thread.start() 122 | self.loop.run_until_complete( 123 | greenio.task(server)(greensocket.socket)) 124 | thread.join(1) 125 | self.assertEqual(non_local['check'], 1) 126 | 127 | non_local['addr'] = None 128 | ev.clear() 129 | thread = threading.Thread(target=server, args=(std_socket.socket,)) 130 | thread.setDaemon(True) 131 | thread.start() 132 | self.loop.run_until_complete( 133 | greenio.task(client)(greensocket.socket)) 134 | thread.join(1) 135 | self.assertEqual(non_local['check'], 2) 136 | 137 | def test_files_socket_echo(self): 138 | import threading 139 | import time 140 | 141 | non_local = {'check': 0, 'addr': None} 142 | ev = threading.Event() 143 | 144 | def server(sock_factory): 145 | socket = sock_factory() 146 | socket.bind(('127.0.0.1', 0)) 147 | 148 | assert socket.fileno() is not None 149 | 150 | non_local['addr'] = socket.getsockname() 151 | socket.listen(1) 152 | 153 | ev.set() 154 | 155 | sock, client_addrs = socket.accept() 156 | assert isinstance(sock, sock_factory) 157 | 158 | rfile = sock.makefile('rb') 159 | data = rfile.read(1024) 160 | while not data.endswith(b'\r'): 161 | data += rfile.read(1024) 162 | 163 | wfile = sock.makefile('wb') 164 | wfile.write(data) 165 | 166 | ev.wait() 167 | ev.clear() 168 | 169 | sock.close() 170 | socket.close() 171 | 172 | def client(sock_factory): 173 | ev.wait() 174 | ev.clear() 175 | time.sleep(0.1) 176 | 177 | sock = sock_factory() 178 | sock.connect(non_local['addr']) 179 | 180 | data = b'hello greenlets\r' 181 | sock.sendall(data) 182 | 183 | rep = b'' 184 | while not rep.endswith(b'\r'): 185 | rep += sock.recv(1024) 186 | 187 | self.assertEqual(data, rep) 188 | ev.set() 189 | 190 | non_local['check'] += 1 191 | 192 | sock.close() 193 | 194 | ev.clear() 195 | thread = threading.Thread(target=client, args=(std_socket.socket,)) 196 | thread.setDaemon(True) 197 | thread.start() 198 | self.loop.run_until_complete( 199 | greenio.task(server)(greensocket.socket)) 200 | thread.join(1) 201 | self.assertEqual(non_local['check'], 1) 202 | 203 | if asyncio is not None: 204 | class SocketTests(SocketMixin, TestCase): 205 | asyncio = asyncio 206 | event_loop_policy = greenio.GreenEventLoopPolicy 207 | 208 | if trollius is not None: 209 | class TrolliusSocketTests(SocketMixin, TestCase): 210 | asyncio = trollius 211 | event_loop_policy = greenio.GreenTrolliusEventLoopPolicy 212 | 213 | def setUp(self): 214 | super(TrolliusSocketTests, self).setUp() 215 | if asyncio is not None: 216 | policy = trollius.get_event_loop_policy() 217 | asyncio.set_event_loop_policy(policy) 218 | -------------------------------------------------------------------------------- /tests/test_tasks.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) 2013 Yury Selivanov 3 | # License: Apache 2.0 4 | ## 5 | 6 | 7 | import asyncio 8 | import greenio 9 | import unittest 10 | 11 | 12 | class TaskTests(unittest.TestCase): 13 | def setUp(self): 14 | policy = greenio.GreenEventLoopPolicy() 15 | asyncio.set_event_loop_policy(policy) 16 | self.loop = policy.new_event_loop() 17 | policy.set_event_loop(self.loop) 18 | 19 | def tearDown(self): 20 | self.loop.close() 21 | asyncio.set_event_loop_policy(None) 22 | 23 | def test_task_yield_from_plain(self): 24 | @asyncio.coroutine 25 | def bar(): 26 | yield from [] 27 | return 30 28 | 29 | @asyncio.coroutine 30 | def foo(): 31 | bar_result = greenio.yield_from(bar()) 32 | return bar_result + 12 33 | 34 | @greenio.task 35 | def test(): 36 | return (yield from foo()) 37 | 38 | fut = test() 39 | self.loop.run_until_complete(fut) 40 | 41 | self.assertEqual(fut.result(), 42) 42 | 43 | def test_task_yield_from_exception_propagation(self): 44 | CHK = 0 45 | 46 | @asyncio.coroutine 47 | def bar(): 48 | yield 49 | yield 50 | 1/0 51 | 52 | @greenio.task 53 | def foo(): 54 | greenio.yield_from(bar()) 55 | 56 | @asyncio.coroutine 57 | def test(): 58 | try: 59 | return (yield from foo()) 60 | except ZeroDivisionError: 61 | nonlocal CHK 62 | CHK += 1 63 | 64 | self.loop.run_until_complete(test()) 65 | self.assertEqual(CHK, 1) 66 | 67 | def test_task_yield_from_coroutine(self): 68 | @asyncio.coroutine 69 | def bar(): 70 | yield from [] 71 | return 5 72 | 73 | @greenio.task 74 | def foo(): 75 | return greenio.yield_from(bar()) 76 | 77 | fut = foo() 78 | self.loop.run_until_complete(fut) 79 | self.assertEqual(fut.result(), 5) 80 | 81 | def test_task_yield_from_invalid(self): 82 | def bar(): 83 | pass 84 | 85 | if hasattr(asyncio.AbstractEventLoop, 'create_task'): 86 | err_msg = (r"^greenlet.yield_from was supposed to receive " 87 | r"only Futures, got .* in task .*$") 88 | else: 89 | err_msg = (r'^"greenio\.yield_from" was supposed to be called ' 90 | r'from a "greenio\.task" or a subsequent coroutine$') 91 | 92 | @asyncio.coroutine 93 | def foo(): 94 | with self.assertRaisesRegex(RuntimeError, err_msg): 95 | greenio.yield_from(bar) 96 | 97 | self.loop.run_until_complete(foo()) 98 | -------------------------------------------------------------------------------- /tests/test_tasks_trollius.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) 2013 Yury Selivanov 3 | # License: Apache 2.0 4 | ## 5 | 6 | 7 | from trollius import From, Return 8 | from trollius.test_utils import TestCase 9 | import greenio 10 | import trollius 11 | try: 12 | import asyncio 13 | except ImportError: 14 | asyncio = None 15 | 16 | 17 | class TrolliusTaskTests(TestCase): 18 | def setUp(self): 19 | policy = greenio.GreenTrolliusEventLoopPolicy() 20 | trollius.set_event_loop_policy(policy) 21 | if asyncio is not None: 22 | asyncio.set_event_loop_policy(policy) 23 | self.loop = policy.new_event_loop() 24 | policy.set_event_loop(self.loop) 25 | 26 | def tearDown(self): 27 | self.loop.close() 28 | trollius.set_event_loop_policy(None) 29 | 30 | def test_task_yield_from_plain(self): 31 | @trollius.coroutine 32 | def bar(): 33 | yield From(None) 34 | raise Return(30) 35 | 36 | @trollius.coroutine 37 | def foo(): 38 | bar_result = greenio.yield_from(bar()) 39 | return bar_result + 12 40 | 41 | @greenio.task 42 | def test(): 43 | res = yield From(foo()) 44 | raise Return(res) 45 | 46 | fut = test() 47 | self.loop.run_until_complete(fut) 48 | 49 | self.assertEqual(fut.result(), 42) 50 | 51 | def test_task_yield_from_exception_propagation(self): 52 | non_local = {'CHK': 0} 53 | 54 | @trollius.coroutine 55 | def bar(): 56 | yield From(None) 57 | yield From(None) 58 | 1/0 59 | 60 | @greenio.task 61 | def foo(): 62 | greenio.yield_from(bar()) 63 | 64 | @trollius.coroutine 65 | def test(): 66 | try: 67 | res = yield From(foo()) 68 | raise Return(res) 69 | except ZeroDivisionError: 70 | non_local['CHK'] += 1 71 | 72 | self.loop.run_until_complete(test()) 73 | self.assertEqual(non_local['CHK'], 1) 74 | 75 | def test_task_yield_from_coroutine(self): 76 | @trollius.coroutine 77 | def bar(): 78 | yield From(None) 79 | raise Return(5) 80 | 81 | @greenio.task 82 | def foo(): 83 | return greenio.yield_from(bar()) 84 | 85 | fut = foo() 86 | self.loop.run_until_complete(fut) 87 | self.assertEqual(fut.result(), 5) 88 | 89 | def test_task_yield_from_invalid(self): 90 | def bar(): 91 | pass 92 | 93 | err_msg = (r"^greenlet.yield_from was supposed to receive " 94 | r"only Futures, got .* in task .*$") 95 | 96 | @trollius.coroutine 97 | def foo(): 98 | with self.assertRaisesRegex(RuntimeError, err_msg): 99 | greenio.yield_from(bar) 100 | 101 | self.loop.run_until_complete(foo()) 102 | --------------------------------------------------------------------------------