├── .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 |
--------------------------------------------------------------------------------