├── .gitignore ├── MANIFEST.in ├── NOTES.rst ├── README.rst ├── aiouv ├── __init__.py ├── _events.py └── _transports.py ├── examples └── echo-multi.py ├── run_aiotest.py ├── runtests.py ├── setup.py ├── tests └── aiouv_events_test.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Hide asyncio folder 2 | 3 | asyncio/ 4 | 5 | # Created by .ignore support plugin (hsz.mobi) 6 | ### Python template 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | env/ 17 | pyvenv/ 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .coverage 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | 57 | # Sphinx documentation 58 | docs/_build/ 59 | 60 | # PyBuilder 61 | target/ 62 | 63 | 64 | ### JetBrains template 65 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 66 | 67 | *.iml 68 | 69 | ## Directory-based project format: 70 | .idea/ 71 | # if you remove the above rule, at least ignore the following: 72 | 73 | # User-specific stuff: 74 | # .idea/workspace.xml 75 | # .idea/tasks.xml 76 | # .idea/dictionaries 77 | 78 | # Sensitive or high-churn files: 79 | # .idea/dataSources.ids 80 | # .idea/dataSources.xml 81 | # .idea/sqlDataSources.xml 82 | # .idea/dynamic.xml 83 | # .idea/uiDesigner.xml 84 | 85 | # Gradle: 86 | # .idea/gradle.xml 87 | # .idea/libraries 88 | 89 | # Mongo Explorer plugin: 90 | # .idea/mongoSettings.xml 91 | 92 | ## File-based project format: 93 | *.ipr 94 | *.iws 95 | 96 | ## Plugin-specific files: 97 | 98 | # IntelliJ 99 | out/ 100 | 101 | # mpeltonen/sbt-idea plugin 102 | .idea_modules/ 103 | 104 | # JIRA plugin 105 | atlassian-ide-plugin.xml 106 | 107 | # Crashlytics plugin (for Android Studio and IntelliJ) 108 | com_crashlytics_export_strings.xml 109 | crashlytics.properties 110 | crashlytics-build.properties 111 | 112 | 113 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include examples/*.py 2 | include tests/*.py 3 | include runtests.py 4 | -------------------------------------------------------------------------------- /NOTES.rst: -------------------------------------------------------------------------------- 1 | 2 | ==================== 3 | Implementation notes 4 | ==================== 5 | 6 | This is a non exhaustive list of things about how things were 7 | implemented like they are in uv_events.py 8 | 9 | Wakeup 10 | ====== 11 | 12 | While a socket pair + a Poll handle could have been used, pyuv 13 | comes with a dedicated handle for this task, Async. 14 | 15 | Ordering 16 | ======== 17 | 18 | Tulip enforces certain order of execution by design, which doesn't 19 | really match the order in which pyuv would execute the given callbacks. 20 | The solution is to use a single Idle handle (which gets executed every 21 | loop iteration, when there are pending callbacks) which processes the 22 | ready callbacks queue. Other handles just append to this queue, and there 23 | is a single place where the queue is processed: the idle handle. Thus order 24 | is preserved. 25 | 26 | Stop 27 | ==== 28 | 29 | Loop stop is always executed at the beginning of the next event loop 30 | iteration. Calling stop will also prevent the loop from blocking for io 31 | on that iteration. If any callback raises an exception, the loop is stopped. 32 | 33 | Close 34 | ===== 35 | 36 | After calling close the loop is destroyed and all resources are freed so 37 | the loop cannot be used anymore. 38 | 39 | Signals 40 | ======= 41 | 42 | Signals are not handled using Python's `signal` module but using pyuv's 43 | Signal handles. These handles allow signals to be processed on any thread 44 | and on multiple event loops at the same time (all callbacks will be called). 45 | Callbacks are always called on the event loop thread. 46 | 47 | Currently Signal handles are ref'd, this means that if an event loop has a single 48 | signal handler and loop.run() is called, it will not return unless the signal handler 49 | is removed or loop.stop() is explicitly called. This may change in the future. 50 | 51 | NOTE: Tulip doesn't allow multiple signal handlers per signal. Rose could easily 52 | implement it because pyuv supports it, though. 53 | 54 | KeyboardInterrupt 55 | ================= 56 | 57 | If the loop is blocked polling, pressing Ctrl+C won't interrupt it and raise 58 | KeyboardInterrupt. This is due to how libuv handles signals. More info and a 59 | possible solution here: https://github.com/saghul/pyuv/commit/3b43285bc66883c4466cb4413de80842dce98621 60 | and here: https://github.com/saghul/pyuv/commit/6e71bf7da350c6ced6bdc4375ed6ba8cd2a9d2f2 61 | 62 | Callback execution 63 | ================== 64 | 65 | Currently aiouv runs all callbacks in an Idle handle, which runs before i/o has been performed and 66 | prevents the loop from blocking for i/o in case it's still active. 67 | All operations will queue the handles in the _ready queue and the aforementioned Idle handle will 68 | execute them in order. 69 | 70 | If any of the handlers raises BaseException sys.exc_info() 71 | will be saved in _last_exc and the processing will not continue, then, if _last_exc is not None it 72 | will be raised. Also, loop will be stopped. 73 | 74 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | ======================================= 3 | aiouv: a PEP-3156 compatible event loop 4 | ======================================= 5 | 6 | 7 | Overview 8 | ======== 9 | 10 | `PEP-3156 `_ is a proposal for asynchronous I/O in Python, 11 | starting with Python 3.3. The reference implementation is codenamed Tulip and can be found 12 | `here `_. It's part of the standard library in 3.4 and also 13 | available in PyPI under the name **asyncio**. 14 | 15 | aiouv is an event loop implementation for asyncio based on `pyuv `_. 16 | 17 | 18 | Installation 19 | ============ 20 | 21 | aiouv depends on pyuv >= 1.0:: 22 | 23 | pip3 install -U pyuv 24 | On Python 3.3, aiouv also requires asyncio >= 0.4.1:: 25 | 26 | pip3 install -U asyncio 27 | Extra API functions 28 | =================== 29 | 30 | aiouv provides a few functions to create TCP, and UDP connections as well as cross 31 | platform named pipes support. 32 | 33 | listen_tcp(loop, protocol_factory, addr) 34 | ---------------------------------------- 35 | 36 | Creates a TCP server listening in the specified address (IP / port tuple). On new 37 | connections a new `TCPTransport` will be created and a protocol will be created using 38 | the supplied factory. 39 | 40 | Returns the listening handle. 41 | 42 | connect_tcp(loop, protocol_factory, addr, bindaddr=None) 43 | -------------------------------------------------------- 44 | 45 | Create a TCP connection to the specified address. If bindaddr is specified, the local 46 | address is bound to it. 47 | 48 | Returns a tuple with the created transport and protocol. 49 | 50 | listen_pipe(loop, protocol_factory, name) 51 | ----------------------------------------- 52 | 53 | Creates a Pipe server listening in the specified pipe name. On new 54 | connections a new `PipeTransport` will be created and a protocol will be created using 55 | the supplied factory. 56 | 57 | Returns the listening handle. 58 | 59 | connect_pipe(loop, protocol_factory, name) 60 | ------------------------------------------ 61 | 62 | Create a Pipe connection to the specified pipe name. 63 | 64 | Returns a tuple with the created transport and protocol. 65 | 66 | create_udp_endpoint(loop, protocol_factory, local_addr=None, remote_addr=None) 67 | ------------------------------------------------------------------------------ 68 | 69 | Created a UDP endpoint. If local_addr is specified, the local interface will be bound 70 | to it. If remote_addr is specified, the remote target is set and it doesn't need 71 | to be specified when calling sendto(). 72 | 73 | Returns a tuple with the created transport and protocol. 74 | 75 | 76 | Running the test suite 77 | ====================== 78 | 79 | From the toplevel directory, run: 80 | 81 | :: 82 | 83 | hg clone https://code.google.com/p/tulip/ asyncio 84 | export PYTHONPATH=asyncio/ 85 | python runtests.py -v aiouv_events_test 86 | 87 | -------------------------------------------------------------------------------- /aiouv/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import asyncio 3 | 4 | from ._events import EventLoop 5 | from ._transports import connect_tcp, listen_tcp, connect_pipe, listen_pipe, create_udp_endpoint 6 | 7 | __all__ = ['EventLoopPolicy', 'EventLoop', 8 | 'connect_tcp', 'listen_tcp', 9 | 'connect_pipe', 'listen_pipe', 10 | 'create_udp_endpoint'] 11 | 12 | 13 | class EventLoopPolicy(asyncio.events.BaseDefaultEventLoopPolicy): 14 | """In this policy, each thread has its own event loop.""" 15 | 16 | _loop_factory = EventLoop -------------------------------------------------------------------------------- /aiouv/_events.py: -------------------------------------------------------------------------------- 1 | 2 | import collections 3 | import os 4 | import pyuv 5 | import socket 6 | import sys 7 | import time 8 | 9 | try: 10 | import ssl 11 | except ImportError: 12 | ssl = None 13 | 14 | try: 15 | import signal 16 | except ImportError: 17 | signal = None 18 | 19 | import asyncio 20 | from asyncio import base_events 21 | from asyncio import selector_events 22 | from asyncio.log import logger 23 | 24 | 25 | # Argument for default thread pool executor creation. 26 | _MAX_WORKERS = 5 27 | 28 | 29 | class TimerHandle(asyncio.Handle): 30 | 31 | def __init__(self, callback, args, timer, loop): 32 | super().__init__(callback, args, loop) 33 | self._timer = timer 34 | 35 | def cancel(self): 36 | super().cancel() 37 | if self._timer and not self._timer.closed: 38 | # cancel() is not supposed to be thread safe, this is clearly 39 | # not either 40 | loop = self._timer.loop._rose_loop 41 | self._timer.close() 42 | loop._timers.remove(self._timer) 43 | del self._timer.handler 44 | self._timer = None 45 | 46 | 47 | class EventLoop(base_events.BaseEventLoop): 48 | 49 | def __init__(self): 50 | super().__init__() 51 | self._loop = pyuv.Loop() 52 | self._loop._rose_loop = self 53 | self._default_executor = None 54 | self._last_exc = None 55 | self._running = False 56 | 57 | self._fd_map = {} 58 | self._signal_handlers = {} 59 | self._subprocesses = {} 60 | self._ready = collections.deque() 61 | self._timers = collections.deque() 62 | 63 | self._waker = pyuv.Async(self._loop, self._async_cb) 64 | self._ready_processor = pyuv.Idle(self._loop) 65 | 66 | def run_forever(self): 67 | if self._running: 68 | raise RuntimeError('Event loop is running.') 69 | self._running = True 70 | try: 71 | self._run(pyuv.UV_RUN_DEFAULT) 72 | finally: 73 | self._running = False 74 | 75 | def run_until_complete(self, future): 76 | _stop_callback = lambda x: self.stop() 77 | future = asyncio.async(future, loop=self) 78 | future.add_done_callback(_stop_callback) 79 | self.run_forever() 80 | future.remove_done_callback(_stop_callback) 81 | if not future.done(): 82 | raise RuntimeError('Event loop stopped before Future completed.') 83 | return future.result() 84 | 85 | def stop(self): 86 | self._loop.stop() 87 | self._waker.send() 88 | 89 | def close(self): 90 | self._fd_map.clear() 91 | self._signal_handlers.clear() 92 | self._subprocesses.clear() 93 | self._ready.clear() 94 | self._timers.clear() 95 | 96 | handles = self._loop.handles 97 | for handle in handles: 98 | if not handle.closed: 99 | handle.close() 100 | 101 | # Run a loop iteration so that close callbacks are called and resources are freed 102 | self._loop.run(pyuv.UV_RUN_DEFAULT) 103 | 104 | del self._loop._rose_loop 105 | self._loop = None 106 | 107 | def is_running(self): 108 | return self._running 109 | 110 | # Methods scheduling callbacks. All these return Handles. 111 | 112 | def call_soon(self, callback, *args): 113 | handler = asyncio.Handle(callback, args, self) 114 | self._add_callback(handler) 115 | return handler 116 | 117 | def call_later(self, delay, callback, *args): 118 | if delay <= 0: 119 | return self.call_soon(callback, *args) 120 | timer = pyuv.Timer(self._loop) 121 | handler = TimerHandle(callback, args, timer, self) 122 | timer.handler = handler 123 | timer.start(self._timer_cb, delay, 0) 124 | self._timers.append(timer) 125 | return handler 126 | 127 | def call_at(self, when, callback, *args): 128 | return self.call_later(when - self.time(), callback, *args) 129 | 130 | def time(self): 131 | return time.monotonic() 132 | 133 | # Methods for interacting with threads. 134 | 135 | # run_in_executor - inherited from BaseEventLoop 136 | # set_default_executor - inherited from BaseEventLoop 137 | 138 | def call_soon_threadsafe(self, callback, *args): 139 | handler = asyncio.Handle(callback, args, self) 140 | # We don't use _add_callback here because starting the Idle handle 141 | # is not threadsafe. Instead, we queue the callback and in the Async 142 | # handle callback (which is run in the loop thread) we start the 143 | # Idle handle if needed 144 | self._ready.append(handler) 145 | self._waker.send() 146 | return handler 147 | 148 | # Network I/O methods returning Futures. 149 | 150 | # getaddrinfo - inherited from BaseEventLoop 151 | # getnameinfo - inherited from BaseEventLoop 152 | # create_connection - inherited from BaseEventLoop 153 | # create_datagram_endpoint - inherited from BaseEventLoop 154 | # connect_read_pipe - inherited from BaseEventLoop 155 | # connect_write_pipe - inherited from BaseEventLoop 156 | # start_serving - inherited from BaseEventLoop 157 | # start_serving_datagram - inherited from BaseEventLoop 158 | 159 | def _start_serving(self, protocol_factory, sock, sslcontext=None, server=None): 160 | # Needed by BaseEventLoop.start_serving 161 | self.add_reader(sock.fileno(), self._accept_connection, protocol_factory, sock, sslcontext, server) 162 | 163 | def _stop_serving(self, sock): 164 | self.remove_reader(sock.fileno()) 165 | sock.close() 166 | 167 | def _accept_connection(self, protocol_factory, sock, sslcontext=None, server=None): 168 | try: 169 | conn, addr = sock.accept() 170 | conn.setblocking(False) 171 | except (BlockingIOError, InterruptedError): 172 | pass # False alarm. 173 | except: 174 | # Bad error. Stop serving. 175 | self.remove_reader(sock.fileno()) 176 | sock.close() 177 | # There's nowhere to send the error, so just log it. 178 | # TODO: Someone will want an error handler for this. 179 | logger.exception('Accept failed') 180 | else: 181 | if sslcontext: 182 | self._make_ssl_transport(conn, protocol_factory(), sslcontext, None, server_side=True, extra={'peername': addr}, server=server) 183 | else: 184 | self._make_socket_transport(conn, protocol_factory(), extra={'peername': addr}, server=server) 185 | # It's now up to the protocol to handle the connection. 186 | 187 | def stop_serving(self, sock): 188 | self.remove_reader(sock.fileno()) 189 | sock.close() 190 | 191 | # Ready-based callback registration methods. 192 | # The add_*() methods return None. 193 | # The remove_*() methods return True if something was removed, 194 | # False if there was nothing to delete. 195 | 196 | def add_reader(self, fd, callback, *args): 197 | handler = asyncio.Handle(callback, args, self) 198 | try: 199 | poll_h = self._fd_map[fd] 200 | except KeyError: 201 | poll_h = self._create_poll_handle(fd) 202 | self._fd_map[fd] = poll_h 203 | poll_h.pevents |= pyuv.UV_READABLE 204 | poll_h.read_handler = handler 205 | poll_h.start(poll_h.pevents, self._poll_cb) 206 | 207 | def remove_reader(self, fd): 208 | try: 209 | poll_h = self._fd_map[fd] 210 | except KeyError: 211 | return False 212 | else: 213 | handler = poll_h.read_handler 214 | poll_h.pevents &= ~pyuv.UV_READABLE 215 | poll_h.read_handler = None 216 | if poll_h.pevents == 0: 217 | del self._fd_map[fd] 218 | poll_h.close() 219 | else: 220 | poll_h.start(poll_h.pevents, self._poll_cb) 221 | if handler: 222 | handler.cancel() 223 | return True 224 | return False 225 | 226 | def add_writer(self, fd, callback, *args): 227 | handler = asyncio.Handle(callback, args, self) 228 | try: 229 | poll_h = self._fd_map[fd] 230 | except KeyError: 231 | poll_h = self._create_poll_handle(fd) 232 | self._fd_map[fd] = poll_h 233 | poll_h.pevents |= pyuv.UV_WRITABLE 234 | poll_h.write_handler = handler 235 | poll_h.start(poll_h.pevents, self._poll_cb) 236 | 237 | def remove_writer(self, fd): 238 | try: 239 | poll_h = self._fd_map[fd] 240 | except KeyError: 241 | return False 242 | else: 243 | handler = poll_h.write_handler 244 | poll_h.pevents &= ~pyuv.UV_WRITABLE 245 | poll_h.write_handler = None 246 | if poll_h.pevents == 0: 247 | del self._fd_map[fd] 248 | poll_h.close() 249 | else: 250 | poll_h.start(poll_h.pevents, self._poll_cb) 251 | if handler: 252 | handler.cancel() 253 | return True 254 | return False 255 | 256 | # Completion based I/O methods returning Futures. 257 | 258 | def sock_recv(self, sock, n): 259 | fut = asyncio.Future(loop=self) 260 | self._sock_recv(fut, False, sock, n) 261 | return fut 262 | 263 | def _sock_recv(self, fut, registered, sock, n): 264 | fd = sock.fileno() 265 | if registered: 266 | # Remove the callback early. It should be rare that the 267 | # selector says the fd is ready but the call still returns 268 | # EAGAIN, and I am willing to take a hit in that case in 269 | # order to simplify the common case. 270 | self.remove_reader(fd) 271 | if fut.cancelled(): 272 | return 273 | try: 274 | data = sock.recv(n) 275 | except (BlockingIOError, InterruptedError): 276 | self.add_reader(fd, self._sock_recv, fut, True, sock, n) 277 | except Exception as exc: 278 | fut.set_exception(exc) 279 | else: 280 | fut.set_result(data) 281 | 282 | def sock_sendall(self, sock, data): 283 | fut = asyncio.Future(loop=self) 284 | if data: 285 | self._sock_sendall(fut, False, sock, data) 286 | else: 287 | fut.set_result(None) 288 | return fut 289 | 290 | def _sock_sendall(self, fut, registered, sock, data): 291 | fd = sock.fileno() 292 | if registered: 293 | self.remove_writer(fd) 294 | if fut.cancelled(): 295 | return 296 | try: 297 | n = sock.send(data) 298 | except (BlockingIOError, InterruptedError): 299 | n = 0 300 | except Exception as exc: 301 | fut.set_exception(exc) 302 | return 303 | if n == len(data): 304 | fut.set_result(None) 305 | else: 306 | if n: 307 | data = data[n:] 308 | self.add_writer(fd, self._sock_sendall, fut, True, sock, data) 309 | 310 | def sock_connect(self, sock, address): 311 | # That address better not require a lookup! We're not calling 312 | # self.getaddrinfo() for you here. But verifying this is 313 | # complicated; the socket module doesn't have a pattern for 314 | # IPv6 addresses (there are too many forms, apparently). 315 | fut = asyncio.Future(loop=self) 316 | self._sock_connect(fut, False, sock, address) 317 | return fut 318 | 319 | def _sock_connect(self, fut, registered, sock, address): 320 | fd = sock.fileno() 321 | if registered: 322 | self.remove_writer(fd) 323 | if fut.cancelled(): 324 | return 325 | try: 326 | if not registered: 327 | # First time around. 328 | sock.connect(address) 329 | else: 330 | err = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) 331 | if err != 0: 332 | # Jump to the except clause below. 333 | raise socket.error(err, 'Connect call failed') 334 | fut.set_result(None) 335 | except (BlockingIOError, InterruptedError): 336 | self.add_writer(fd, self._sock_connect, fut, True, sock, address) 337 | except Exception as exc: 338 | fut.set_exception(exc) 339 | 340 | def sock_accept(self, sock): 341 | fut = asyncio.Future(loop=self) 342 | self._sock_accept(fut, False, sock) 343 | return fut 344 | 345 | def _sock_accept(self, fut, registered, sock): 346 | fd = sock.fileno() 347 | if registered: 348 | self.remove_reader(fd) 349 | if fut.cancelled(): 350 | return 351 | try: 352 | conn, address = sock.accept() 353 | conn.setblocking(False) 354 | fut.set_result((conn, address)) 355 | except (BlockingIOError, InterruptedError): 356 | self.add_reader(fd, self._sock_accept, fut, True, sock) 357 | except Exception as exc: 358 | fut.set_exception(exc) 359 | 360 | # Signal handling. 361 | 362 | def add_signal_handler(self, sig, callback, *args): 363 | self._validate_signal(sig) 364 | signal_h = pyuv.Signal(self._loop) 365 | handler = asyncio.Handle(callback, args, self) 366 | signal_h.handler = handler 367 | try: 368 | signal_h.start(self._signal_cb, sig) 369 | except Exception as e: 370 | signal_h.close() 371 | raise RuntimeError(str(e)) 372 | else: 373 | self._signal_handlers[sig] = signal_h 374 | return handler 375 | 376 | def remove_signal_handler(self, sig): 377 | self._validate_signal(sig) 378 | try: 379 | signal_h = self._signal_handlers.pop(sig) 380 | except KeyError: 381 | return False 382 | del signal_h.handler 383 | signal_h.close() 384 | return True 385 | 386 | # Private / internal methods 387 | 388 | def _make_socket_transport(self, sock, protocol, waiter=None, *, extra=None, server=None): 389 | return selector_events._SelectorSocketTransport(self, sock, protocol, waiter, extra, server) 390 | 391 | def _make_ssl_transport(self, rawsock, protocol, sslcontext, waiter, *, server_side=False, server_hostname=None, extra=None, server=None): 392 | return selector_events._SelectorSslTransport(self, rawsock, protocol, sslcontext, waiter, server_side, server_hostname, extra, server) 393 | 394 | def _make_datagram_transport(self, sock, protocol, address=None, extra=None): 395 | return selector_events._SelectorDatagramTransport(self, sock, protocol, address, extra) 396 | 397 | def _make_read_pipe_transport(self, pipe, protocol, waiter=None, extra=None): 398 | if sys.platform != 'win32': 399 | from asyncio import unix_events 400 | return unix_events._UnixReadPipeTransport(self, pipe, protocol, waiter, extra) 401 | raise NotImplementedError 402 | 403 | def _make_write_pipe_transport(self, pipe, protocol, waiter=None, extra=None): 404 | if sys.platform != 'win32': 405 | from asyncio import unix_events 406 | return unix_events._UnixWritePipeTransport(self, pipe, protocol, waiter, extra) 407 | # TODO 408 | raise NotImplementedError 409 | 410 | @asyncio.coroutine 411 | def _make_subprocess_transport(self, protocol, args, shell, stdin, stdout, stderr, bufsize, extra=None, **kwargs): 412 | if sys.platform == 'win32': 413 | # TODO 414 | raise NotImplementedError 415 | from asyncio import unix_events 416 | self._reg_sigchld() 417 | transp = unix_events._UnixSubprocessTransport(self, protocol, args, shell, stdin, stdout, stderr, bufsize, extra=None, **kwargs) 418 | self._subprocesses[transp.get_pid()] = transp 419 | yield from transp._post_init() 420 | return transp 421 | 422 | def _reg_sigchld(self): 423 | if signal.SIGCHLD not in self._signal_handlers: 424 | self.add_signal_handler(signal.SIGCHLD, self._sig_chld) 425 | 426 | def _sig_chld(self): 427 | try: 428 | while True: 429 | try: 430 | pid, status = os.waitpid(0, os.WNOHANG) 431 | except ChildProcessError: 432 | break 433 | if pid == 0: 434 | continue 435 | elif os.WIFSIGNALED(status): 436 | returncode = -os.WTERMSIG(status) 437 | elif os.WIFEXITED(status): 438 | returncode = os.WEXITSTATUS(status) 439 | else: 440 | # covered by 441 | # SelectorEventLoopTests.test__sig_chld_unknown_status 442 | # from tests/unix_events_test.py 443 | # bug in coverage.py version 3.6 ??? 444 | continue # pragma: no cover 445 | transp = self._subprocesses.get(pid) 446 | if transp is not None: 447 | transp._process_exited(returncode) 448 | except Exception: 449 | logger.exception('Unknown exception in SIGCHLD handler') 450 | 451 | def _subprocess_closed(self, transport): 452 | pid = transport.get_pid() 453 | self._subprocesses.pop(pid, None) 454 | 455 | def _run(self, mode): 456 | r = self._loop.run(mode) 457 | if self._last_exc is not None: 458 | exc, self._last_exc = self._last_exc, None 459 | raise exc[1] 460 | return r 461 | 462 | def _add_callback(self, callback): 463 | self._ready.append(callback) 464 | if not self._ready_processor.active: 465 | self._ready_processor.start(self._process_ready) 466 | 467 | def _async_cb(self, async): 468 | if not self._ready_processor.active: 469 | self._ready_processor.start(self._process_ready) 470 | 471 | def _timer_cb(self, timer): 472 | assert not timer.handler._cancelled 473 | self._add_callback(timer.handler) 474 | del timer.handler 475 | self._timers.remove(timer) 476 | timer.close() 477 | 478 | def _signal_cb(self, signal_h, signum): 479 | if signal_h.handler._cancelled: 480 | self.remove_signal_handler(signum) 481 | return 482 | self._add_callback(signal_h.handler) 483 | 484 | def _poll_cb(self, poll_h, events, error): 485 | fd = poll_h.fileno() 486 | if error is not None: 487 | # An error happened, signal both readability and writability and 488 | # let the error propagate 489 | if poll_h.read_handler is not None: 490 | if poll_h.read_handler._cancelled: 491 | self.remove_reader(fd) 492 | else: 493 | self._add_callback(poll_h.read_handler) 494 | if poll_h.write_handler is not None: 495 | if poll_h.write_handler._cancelled: 496 | self.remove_writer(fd) 497 | else: 498 | self._add_callback(poll_h.write_handler) 499 | return 500 | 501 | old_events = poll_h.pevents 502 | modified = False 503 | 504 | if events & pyuv.UV_READABLE: 505 | if poll_h.read_handler is not None: 506 | if poll_h.read_handler._cancelled: 507 | self.remove_reader(fd) 508 | modified = True 509 | else: 510 | self._add_callback(poll_h.read_handler) 511 | else: 512 | poll_h.pevents &= ~pyuv.UV_READABLE 513 | if events & pyuv.UV_WRITABLE: 514 | if poll_h.write_handler is not None: 515 | if poll_h.write_handler._cancelled: 516 | self.remove_writer(fd) 517 | modified = True 518 | else: 519 | self._add_callback(poll_h.write_handler) 520 | else: 521 | poll_h.pevents &= ~pyuv.UV_WRITABLE 522 | 523 | if not modified and old_events != poll_h.pevents: 524 | # Rearm the handle 525 | poll_h.start(poll_h.pevents, self._poll_cb) 526 | 527 | def _process_ready(self, handle): 528 | # This is the only place where callbacks are actually *called*. 529 | # All other places just add them to ready. 530 | # Note: We run all currently scheduled callbacks, but not any 531 | # callbacks scheduled by callbacks run this time around -- 532 | # they will be run the next time (after another I/O poll). 533 | # Use an idiom that is threadsafe without using locks. 534 | ntodo = len(self._ready) 535 | for i in range(ntodo): 536 | handler = self._ready.popleft() 537 | if not handler._cancelled: 538 | try: 539 | handler._run() 540 | except BaseException: 541 | self._last_exc = sys.exc_info() 542 | self._loop.stop() 543 | break 544 | handler = None # break cycles when exception occurs 545 | if not self._ready: 546 | self._ready_processor.stop() 547 | 548 | def _create_poll_handle(self, fdobj): 549 | poll_h = pyuv.Poll(self._loop, self._fileobj_to_fd(fdobj)) 550 | poll_h.pevents = 0 551 | poll_h.read_handler = None 552 | poll_h.write_handler = None 553 | return poll_h 554 | 555 | def _fileobj_to_fd(self, fileobj): 556 | """Return a file descriptor from a file object. 557 | 558 | Parameters: 559 | fileobj -- file descriptor, or any object with a `fileno()` method 560 | 561 | Returns: 562 | corresponding file descriptor 563 | """ 564 | if isinstance(fileobj, int): 565 | fd = fileobj 566 | else: 567 | try: 568 | fd = int(fileobj.fileno()) 569 | except (ValueError, TypeError): 570 | raise ValueError("Invalid file object: {!r}".format(fileobj)) 571 | return fd 572 | 573 | def _validate_signal(self, sig): 574 | """Internal helper to validate a signal. 575 | 576 | Raise ValueError if the signal number is invalid or uncatchable. 577 | Raise RuntimeError if there is a problem setting up the handler. 578 | """ 579 | if not isinstance(sig, int): 580 | raise TypeError('sig must be an int, not {!r}'.format(sig)) 581 | if signal is None: 582 | raise RuntimeError('Signals are not supported') 583 | if not (1 <= sig < signal.NSIG): 584 | raise ValueError('sig {} out of range(1, {})'.format(sig, signal.NSIG)) 585 | if sys.platform == 'win32': 586 | raise RuntimeError('Signals are not really supported on Windows') 587 | 588 | -------------------------------------------------------------------------------- /aiouv/_transports.py: -------------------------------------------------------------------------------- 1 | 2 | import pyuv 3 | 4 | from asyncio import futures 5 | from asyncio import tasks 6 | from asyncio import transports 7 | from asyncio.log import logger 8 | 9 | __all__ = ['connect_tcp', 'listen_tcp', 10 | 'connect_pipe', 'listen_pipe', 11 | 'create_udp_endpoint'] 12 | 13 | 14 | class StreamTransport(transports.Transport): 15 | 16 | def __init__(self, loop, protocol, handle, extra=None): 17 | super().__init__(extra) 18 | self._loop = loop 19 | self._protocol = protocol 20 | self._handle = handle 21 | 22 | self._closing = False 23 | self._shutting = False 24 | 25 | self._handle.start_read(self._on_read) 26 | self._loop.call_soon(self._protocol.connection_made, self) 27 | 28 | @property 29 | def loop(self): 30 | return self._loop 31 | 32 | def close(self): 33 | if self._closing: 34 | return 35 | self._closing = True 36 | if not self._shutting: 37 | self._handle.shutdown(self._on_shutdown) 38 | else: 39 | # The handle is already shut, someone called write_eof, so just 40 | # call connection_lost 41 | self._call_connection_lost(None) 42 | 43 | def pause_reading(self): 44 | if self._closing: 45 | return 46 | self._handle.stop_read() 47 | 48 | def resume_reading(self): 49 | if self._closing: 50 | return 51 | self._handle.start_read(self._on_read) 52 | 53 | def write(self, data): 54 | assert isinstance(data, bytes), repr(data) 55 | assert not self._shutting, 'Cannot call write() after write_eof()' 56 | if not data: 57 | return 58 | if self._closing: 59 | return 60 | self._handle.write(data, self._on_write) 61 | 62 | def writelines(self, seq): 63 | assert not self._shutting, 'Cannot call writelines() after write_eof()' 64 | if not seq: 65 | return 66 | if self._closing: 67 | return 68 | self._handle.writelines(seq, self._on_write) 69 | 70 | def write_eof(self): 71 | if self._shutting or self._closing: 72 | return 73 | self._shutting = True 74 | self._handle.shutdown(self._on_shutdown) 75 | 76 | def can_write_eof(self): 77 | return True 78 | 79 | def abort(self): 80 | self._close(None) 81 | 82 | def _close(self, exc): 83 | if self._closing: 84 | return 85 | self._closing = True 86 | self._loop.call_soon(self._call_connection_lost, exc) 87 | 88 | def _call_connection_lost(self, exc): 89 | try: 90 | self._protocol.connection_lost(exc) 91 | finally: 92 | self._handle.close() 93 | self._handle = None 94 | self._protocol = None 95 | self._loop = None 96 | 97 | def _on_read(self, handle, data, error): 98 | if error is not None: 99 | if error == pyuv.errno.UV_EOF: 100 | # connection closed by remote 101 | try: 102 | self._protocol.eof_received() 103 | finally: 104 | self.close() 105 | else: 106 | logger.warning('error reading from connection: {} - {}'.format(error, pyuv.errno.strerror(error))) 107 | exc = ConnectionError(error, pyuv.errno.strerror(error)) 108 | self._close(exc) 109 | else: 110 | self._protocol.data_received(data) 111 | 112 | def _on_write(self, handle, error): 113 | if error is not None: 114 | logger.warning('error writing to connection: {} - {}'.format(error, pyuv.errno.strerror(error))) 115 | exc = ConnectionError(error, pyuv.errno.strerror(error)) 116 | self._close(exc) 117 | return 118 | 119 | def _on_shutdown(self, handle, error): 120 | pass 121 | 122 | 123 | class UDPTransport(transports.DatagramTransport): 124 | 125 | def __init__(self, loop, protocol, handle, addr, extra=None): 126 | super().__init__(extra) 127 | self._loop = loop 128 | self._protocol = protocol 129 | self._handle = handle 130 | 131 | self._addr = addr 132 | self._closing = False 133 | 134 | self._handle.start_recv(self._on_recv) 135 | self._loop.call_soon(self._protocol.connection_made, self) 136 | 137 | @property 138 | def loop(self): 139 | return self._loop 140 | 141 | def sendto(self, data, addr=None): 142 | assert isinstance(data, bytes), repr(data) 143 | if not data: 144 | return 145 | if self._closing: 146 | return 147 | if self._addr: 148 | assert addr in (None, self._addr) 149 | self._handle.send(addr or self._addr, data, self._on_send) 150 | 151 | def abort(self): 152 | self._close(None) 153 | 154 | def close(self): 155 | if self._closing: 156 | return 157 | self._closing = True 158 | self._call_connection_lost(None) 159 | 160 | def _call_connection_lost(self, exc): 161 | try: 162 | self._protocol.connection_lost(exc) 163 | finally: 164 | self._handle.close() 165 | self._handle = None 166 | self._protocol = None 167 | self._loop = None 168 | 169 | def _close(self, exc): 170 | if self._closing: 171 | return 172 | self._closing = True 173 | if self._addr and exc and exc.args[0] == pyuv.errno.UV_ECONNREFUSED: 174 | self._protocol.connection_refused(exc) 175 | self._loop.call_soon(self._call_connection_lost, exc) 176 | 177 | def _on_recv(self, handle, addr, flags, data, error): 178 | if error is not None: 179 | logger.warning('error reading from UDP endpoint: {} - {}'.format(error, pyuv.errno.strerror(error))) 180 | exc = ConnectionError(error, pyuv.errno.strerror(error)) 181 | self._close(exc) 182 | else: 183 | self._protocol.datagram_received(data, addr) 184 | 185 | def _on_send(self, handle, error): 186 | if error is not None: 187 | logger.warning('error sending to UDP endpoint: {} - {}'.format(error, pyuv.errno.strerror(error))) 188 | exc = ConnectionError(error, pyuv.errno.strerror(error)) 189 | self._close(exc) 190 | 191 | 192 | ## Some extra functions for using the extra transports provided by rose 193 | 194 | class TCPTransport(StreamTransport): 195 | pass 196 | 197 | 198 | def _tcp_listen_cb(server, error): 199 | if error is not None: 200 | logger.warning('error processing incoming TCP connection: {} - {}'.format(error, pyuv.errno.strerror(error))) 201 | return 202 | conn = pyuv.TCP(server.loop) 203 | try: 204 | server.accept(conn) 205 | except pyuv.error.TCPError as e: 206 | error = e.args[0] 207 | logger.warning('error accepting incoming TCP connection: {} - {}'.format(error, pyuv.errno.strerror(error))) 208 | return 209 | addr = conn.getpeername() 210 | return TCPTransport(conn.loop._rose_loop, server.protocol_factory(), conn, extra={'peername': addr}) 211 | 212 | 213 | def listen_tcp(loop, protocol_factory, addr): 214 | handle = pyuv.TCP(loop._loop) 215 | handle.bind(addr) 216 | handle.listen(_tcp_listen_cb) 217 | handle.protocol_factory = protocol_factory 218 | return handle 219 | 220 | 221 | @tasks.coroutine 222 | def connect_tcp(loop, protocol_factory, addr, bindaddr=None): 223 | protocol = protocol_factory() 224 | waiter = futures.Future(loop=loop) 225 | 226 | def connect_cb(handle, error): 227 | if error is not None: 228 | waiter.set_exception(ConnectionError(error, pyuv.errno.strerror(error))) 229 | else: 230 | waiter.set_result(None) 231 | 232 | handle = pyuv.TCP(loop._loop) 233 | if bindaddr is not None: 234 | handle.bind((interface, port)) 235 | handle.connect(addr, connect_cb) 236 | 237 | yield from waiter 238 | 239 | addr = handle.getpeername() 240 | transport = TCPTransport(loop, protocol, handle, extra={'peername': addr}) 241 | return transport, protocol 242 | 243 | 244 | class PipeTransport(StreamTransport): 245 | pass 246 | 247 | 248 | def _pipe_listen_cb(server, error): 249 | if error is not None: 250 | logger.warning('error processing incoming Pipe connection: {} - {}'.format(error, pyuv.errno.strerror(error))) 251 | return 252 | conn = pyuv.Pipe(server.loop) 253 | try: 254 | server.accept(conn) 255 | except pyuv.error.PipeError as e: 256 | error = e.args[0] 257 | logger.warning('error accepting incoming Pipe connection: {} - {}'.format(error, pyuv.errno.strerror(error))) 258 | return 259 | return PipeTransport(conn.loop._rose_loop, server.protocol_factory(), conn) 260 | 261 | 262 | def listen_pipe(loop, protocol_factory, name): 263 | handle = pyuv.Pipe(loop._loop) 264 | handle.bind(name) 265 | handle.listen(_pipe_listen_cb) 266 | handle.protocol_factory = protocol_factory 267 | return handle 268 | 269 | 270 | @tasks.coroutine 271 | def connect_pipe(loop, protocol_factory, name): 272 | protocol = protocol_factory() 273 | waiter = futures.Future(loop=loop) 274 | 275 | def connect_cb(handle, error): 276 | if error is not None: 277 | waiter.set_exception(ConnectionError(error, pyuv.errno.strerror(error))) 278 | else: 279 | waiter.set_result(None) 280 | 281 | handle = pyuv.Pipe(loop._loop) 282 | handle.connect(name, connect_cb) 283 | 284 | yield from waiter 285 | 286 | transport = PipeTransport(loop, protocol, handle) 287 | return transport, protocol 288 | 289 | 290 | def create_udp_endpoint(loop, protocol_factory, local_addr=None, remote_addr=None): 291 | if not (local_addr or remote_addr): 292 | raise ValueError('local or remote address must be specified') 293 | handle = pyuv.UDP(loop._loop) 294 | handle.bind(local_addr or ('', 0)) 295 | protocol = protocol_factory() 296 | addr = handle.getsockname() 297 | transport = UDPTransport(loop, protocol, handle, remote_addr, extra={'sockname': addr}) 298 | return transport, protocol 299 | 300 | -------------------------------------------------------------------------------- /examples/echo-multi.py: -------------------------------------------------------------------------------- 1 | 2 | import argparse 3 | import asyncio 4 | import signal 5 | 6 | import aiouv 7 | 8 | 9 | loop = aiouv.EventLoop() 10 | 11 | 12 | class EchoServerProtocol(asyncio.Protocol): 13 | 14 | def connection_made(self, transport): 15 | print("Client connected") 16 | self.transport = transport 17 | 18 | def data_received(self, data): 19 | self.transport.write(data) 20 | 21 | def eof_received(self): 22 | self.transport.close() 23 | 24 | def connection_lost(self, exc): 25 | print('Connection lost:', exc) 26 | 27 | 28 | class EchoClientProtocol(asyncio.Protocol): 29 | 30 | def connection_made(self, transport): 31 | print("Connected!") 32 | self.transport = transport 33 | 34 | def data_received(self, data): 35 | self.transport.write(data) 36 | 37 | def eof_received(self): 38 | self.transport.close() 39 | 40 | def connection_lost(self, exc): 41 | print('Connection closed:', exc) 42 | loop.stop() 43 | 44 | 45 | def start_tcp_server(host, port): 46 | handle = aiouv.listen_tcp(loop, EchoServerProtocol, (host, port)) 47 | print('Echo TCP server listening') 48 | return handle 49 | 50 | 51 | def start_tcp_client(host, port): 52 | t = asyncio.async(aiouv.connect_tcp(loop, EchoClientProtocol, (host, port)), loop=loop) 53 | transport, protocol = loop.run_until_complete(t) 54 | print('Echo TCP client connected') 55 | return transport, protocol 56 | 57 | 58 | def start_pipe_server(name): 59 | handle = aiouv.listen_pipe(loop, EchoServerProtocol, name) 60 | print('Echo Pipe server listening') 61 | return handle 62 | 63 | 64 | def start_pipe_client(name): 65 | t = asyncio.async(aiouv.connect_pipe(loop, EchoClientProtocol, name), loop=loop) 66 | transport, protocol = loop.run_until_complete(t) 67 | print('Echo Pipe client connected') 68 | return transport, protocol 69 | 70 | 71 | parser = argparse.ArgumentParser(description='Multi-Echo example') 72 | parser.add_argument( 73 | '--server', action="store_true", dest='server', 74 | default=False, help='Run a server') 75 | parser.add_argument( 76 | '--client', action="store_true", dest='client', 77 | default=False, help='Run a client') 78 | parser.add_argument( 79 | '--tcp', action="store_true", dest='tcp', 80 | default=False, help='Use TCP') 81 | parser.add_argument( 82 | '--pipe', action="store_true", dest='pipe', 83 | default=False, help='Use named pipes') 84 | parser.add_argument( 85 | 'addr', action="store", 86 | help='address / pipe name') 87 | 88 | 89 | if __name__ == '__main__': 90 | args = parser.parse_args() 91 | if (args.client and args.server) or (not args.client and not args.server): 92 | raise RuntimeError('incorrect parameters') 93 | if (args.tcp and args.pipe) or (not args.tcp and not args.pipe): 94 | raise RuntimeError('incorrect parameters') 95 | 96 | if args.tcp: 97 | host, _ , port = args.addr.rpartition(':') 98 | host = host.strip('[]') 99 | port = int(port) 100 | if args.server: 101 | x = start_tcp_server(host, port) 102 | elif args.client: 103 | x = start_tcp_client(host, port) 104 | elif args.pipe: 105 | name = args.addr 106 | if args.server: 107 | x = start_pipe_server(name) 108 | else: 109 | x = start_pipe_client(name) 110 | 111 | loop.add_signal_handler(signal.SIGINT, loop.stop) 112 | loop.run_forever() 113 | 114 | -------------------------------------------------------------------------------- /run_aiotest.py: -------------------------------------------------------------------------------- 1 | import aiotest.run 2 | import aiouv 3 | 4 | config = aiotest.TestConfig() 5 | config.new_event_pool_policy = aiouv.EventLoopPolicy 6 | aiotest.run.main(config) 7 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | """Run all unittests. 2 | 3 | Usage: 4 | python3 runtests.py [-v] [-q] [pattern] ... 5 | 6 | Where: 7 | -v: verbose 8 | -q: quiet 9 | pattern: optional regex patterns to match test ids (default all tests) 10 | 11 | Note that the test id is the fully qualified name of the test, 12 | including package, module, class and method, 13 | e.g. 'tulip.events_test.PolicyTests.testPolicy'. 14 | """ 15 | 16 | # Originally written by Beech Horn (for NDB). 17 | 18 | import logging 19 | import os 20 | import re 21 | import sys 22 | import unittest 23 | import importlib.machinery 24 | 25 | assert sys.version >= '3.3', 'Please use Python 3.3 or higher.' 26 | 27 | 28 | TULIP_DIR = os.path.join(os.path.dirname(__file__), 'asyncio', 'tests') 29 | ROSE_DIR = os.path.join(os.path.dirname(__file__), 'tests') 30 | 31 | 32 | def load_test_modules(loader, suite, includes, excludes, base_dir): 33 | test_mods = [(f[:-3], f) for f in os.listdir(base_dir) if f.endswith('_test.py')] 34 | mods = [] 35 | for mod, sourcefile in test_mods: 36 | try: 37 | loader_ = importlib.machinery.SourceFileLoader(mod, os.path.join(base_dir, sourcefile)) 38 | mods.append(loader_.load_module()) 39 | except ImportError: 40 | pass 41 | 42 | for mod in mods: 43 | for name in set(dir(mod)): 44 | if name.endswith('Tests'): 45 | test_module = getattr(mod, name) 46 | tests = loader.loadTestsFromTestCase(test_module) 47 | if includes: 48 | tests = [test 49 | for test in tests 50 | if any(re.search(pat, test.id()) for pat in includes)] 51 | if excludes: 52 | tests = [test 53 | for test in tests 54 | if not any(re.search(pat, test.id()) for pat in excludes)] 55 | suite.addTests(tests) 56 | 57 | 58 | def load_tests(includes=(), excludes=()): 59 | loader = unittest.TestLoader() 60 | suite = unittest.TestSuite() 61 | 62 | load_test_modules(loader, suite, includes, excludes, TULIP_DIR) 63 | load_test_modules(loader, suite, includes, excludes, ROSE_DIR) 64 | 65 | return suite 66 | 67 | 68 | def main(): 69 | excludes = [] 70 | includes = [] 71 | patterns = includes # A reference. 72 | v = 1 73 | for arg in sys.argv[1:]: 74 | if arg.startswith('-v'): 75 | v += arg.count('v') 76 | elif arg == '-q': 77 | v = 0 78 | elif arg == '-x': 79 | if patterns is includes: 80 | patterns = excludes 81 | else: 82 | patterns = includes 83 | elif arg and not arg.startswith('-'): 84 | patterns.append(arg) 85 | tests = load_tests(includes, excludes) 86 | logger = logging.getLogger() 87 | if v == 0: 88 | logger.setLevel(logging.CRITICAL) 89 | elif v == 1: 90 | logger.setLevel(logging.ERROR) 91 | elif v == 2: 92 | logger.setLevel(logging.WARNING) 93 | elif v == 3: 94 | logger.setLevel(logging.INFO) 95 | elif v >= 4: 96 | logger.setLevel(logging.DEBUG) 97 | 98 | import asyncio 99 | import aiouv 100 | asyncio.set_event_loop_policy(aiouv.EventLoopPolicy()) 101 | 102 | result = unittest.TextTestRunner(verbosity=v).run(tests) 103 | sys.exit(not result.wasSuccessful()) 104 | 105 | 106 | if __name__ == '__main__': 107 | main() 108 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Release procedure: 4 | # - fill the changelog? 5 | # - run tests: tox 6 | # - update version in setup.py 7 | # - set release date in the changelog? 8 | # - check that "python setup.py sdist" contains all files tracked by 9 | # the Mercurial: update MANIFEST.in if needed 10 | # - git commit 11 | # - git tag VERSION 12 | # - git push 13 | # - git push --tags 14 | # - python setup.py register sdist bdist_wheel upload 15 | # - increment version in setup.py 16 | # - git commit && git push 17 | 18 | import os 19 | import sys 20 | try: 21 | from setuptools import setup 22 | SETUPTOOLS = True 23 | except ImportError: 24 | SETUPTOOLS = False 25 | # Use distutils.core as a fallback. 26 | # We won't be able to build the Wheel file on Windows. 27 | from distutils.core import setup 28 | 29 | with open("README.rst") as fp: 30 | long_description = fp.read() 31 | 32 | requirements = ["pyuv>=1.0"] 33 | if sys.version_info < (3, 4): 34 | requirements.append("asyncio>=0.4.1") 35 | 36 | install_options = { 37 | "name": "aiouv", 38 | "version": "0.0.1", 39 | "license": "MIT license", 40 | "author": 'Saúl Ibarra Corretgé', 41 | 42 | "description": "libuv based event loop for asyncio", 43 | "long_description": long_description, 44 | "url": "http://github.com/saghul/aiouv", 45 | 46 | "classifiers": [ 47 | "Development Status :: 4 - Beta", 48 | "Intended Audience :: Developers", 49 | "License :: OSI Approved :: MIT License", 50 | "Operating System :: Microsoft :: Windows", 51 | "Operating System :: POSIX", 52 | "Programming Language :: Python", 53 | "Programming Language :: Python :: 3.3", 54 | "Programming Language :: Python :: 3.4", 55 | ], 56 | 57 | "packages": ["aiouv"], 58 | # "test_suite": "runtests.runtests", 59 | } 60 | if SETUPTOOLS: 61 | install_options['install_requires'] = requirements 62 | 63 | setup(**install_options) 64 | -------------------------------------------------------------------------------- /tests/aiouv_events_test.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | import asyncio.test_utils 4 | from aiouv import EventLoop 5 | 6 | import imp 7 | import os 8 | 9 | test_events = imp.load_source('test_events', os.path.join(os.path.dirname(__file__), '../asyncio/tests/test_events.py')) 10 | 11 | 12 | class AiouvEventLoopTests(test_events.EventLoopTestsMixin, asyncio.test_utils.TestCase): 13 | def create_event_loop(self): 14 | return EventLoop() 15 | 16 | 17 | if __name__ == '__main__': 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py33,py34 3 | 4 | [testenv] 5 | deps= 6 | aiotest >= 0.2 7 | pyuv >= 1.0 8 | setenv = 9 | PYTHONASYNCIODEBUG = 1 10 | commands= 11 | # python runtests.py -r {posargs} 12 | python run_aiotest.py -r {posargs} 13 | 14 | [testenv:py33] 15 | deps= 16 | aiotest 17 | asyncio >= 0.4.1 18 | pyuv >= 1.0 19 | basepython = python3.5 20 | --------------------------------------------------------------------------------