summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEevee (Alex Munroe) <eevee.git@veekun.com>2014-05-11 18:39:39 -0700
committerEevee (Alex Munroe) <eevee.git@veekun.com>2014-05-11 21:30:22 -0700
commitdd227290a3831bf2b8716610332e28648bc63697 (patch)
treea52335a7d6e5d0c194904ae8dbecef4770ade90b
parent28079171a037e9bdd7a190fe8bd1446e02c8071f (diff)
downloadurwid-dd227290a3831bf2b8716610332e28648bc63697.tar.gz
Add AsyncioEventLoop. Fixes #52.
-rw-r--r--urwid/__init__.py2
-rwxr-xr-xurwid/main_loop.py121
-rw-r--r--urwid/tests/test_event_loops.py18
3 files changed, 136 insertions, 5 deletions
diff --git a/urwid/__init__.py b/urwid/__init__.py
index a6cacf8..bc5170e 100644
--- a/urwid/__init__.py
+++ b/urwid/__init__.py
@@ -53,7 +53,7 @@ from urwid.command_map import (CommandMap, command_map,
CURSOR_PAGE_UP, CURSOR_PAGE_DOWN, CURSOR_MAX_LEFT, CURSOR_MAX_RIGHT,
ACTIVATE)
from urwid.main_loop import (ExitMainLoop, MainLoop, SelectEventLoop,
- GLibEventLoop, TornadoEventLoop)
+ GLibEventLoop, TornadoEventLoop, AsyncioEventLoop)
try:
from urwid.main_loop import TwistedEventLoop
except ImportError:
diff --git a/urwid/main_loop.py b/urwid/main_loop.py
index 2c314d8..84ed2d6 100755
--- a/urwid/main_loop.py
+++ b/urwid/main_loop.py
@@ -552,7 +552,7 @@ class SelectEventLoop(object):
def alarm(self, seconds, callback):
"""
- Call callback() given time from from now. No parameters are
+ Call callback() a given time from now. No parameters are
passed to callback.
Returns a handle that may be passed to remove_alarm()
@@ -699,7 +699,7 @@ class GLibEventLoop(object):
def alarm(self, seconds, callback):
"""
- Call callback() given time from from now. No parameters are
+ Call callback() a given time from now. No parameters are
passed to callback.
Returns a handle that may be passed to remove_alarm()
@@ -1030,7 +1030,7 @@ class TwistedEventLoop(object):
def alarm(self, seconds, callback):
"""
- Call callback() given time from from now. No parameters are
+ Call callback() a given time from now. No parameters are
passed to callback.
Returns a handle that may be passed to remove_alarm()
@@ -1169,6 +1169,121 @@ class TwistedEventLoop(object):
return wrapper
+class AsyncioEventLoop(object):
+ """
+ Event loop based on the standard library ``asyncio`` module.
+
+ ``asyncio`` is new in Python 3.4, but also exists as a backport on PyPI for
+ Python 3.3. The ``trollius`` package is available for older Pythons with
+ slightly different syntax, but also works with this loop.
+ """
+ _we_started_event_loop = False
+
+ _idle_emulation_delay = 1.0/256 # a short time (in seconds)
+
+ def __init__(self, **kwargs):
+ if 'loop' in kwargs:
+ self._loop = kwargs.pop('loop')
+ else:
+ import asyncio
+ self._loop = asyncio.get_event_loop()
+
+ def alarm(self, seconds, callback):
+ """
+ Call callback() a given time from now. No parameters are
+ passed to callback.
+
+ Returns a handle that may be passed to remove_alarm()
+
+ seconds -- time in seconds to wait before calling callback
+ callback -- function to call from event loop
+ """
+ return self._loop.call_later(seconds, callback)
+
+ def remove_alarm(self, handle):
+ """
+ Remove an alarm.
+
+ Returns True if the alarm exists, False otherwise
+ """
+ existed = not handle._cancelled
+ handle.cancel()
+ return existed
+
+ def watch_file(self, fd, callback):
+ """
+ Call callback() when fd has some data to read. No parameters
+ are passed to callback.
+
+ Returns a handle that may be passed to remove_watch_file()
+
+ fd -- file descriptor to watch for input
+ callback -- function to call when input is available
+ """
+ self._loop.add_reader(fd, callback)
+ return fd
+
+ def remove_watch_file(self, handle):
+ """
+ Remove an input file.
+
+ Returns True if the input file exists, False otherwise
+ """
+ return self._loop.remove_reader(handle)
+
+ def enter_idle(self, callback):
+ """
+ Add a callback for entering idle.
+
+ Returns a handle that may be passed to remove_idle()
+ """
+ # XXX there's no such thing as "idle" in most event loops; this fakes
+ # it the same way as Twisted, by scheduling the callback to be called
+ # repeatedly
+ mutable_handle = [None]
+ def faux_idle_callback():
+ callback()
+ mutable_handle[0] = self._loop.call_later(
+ self._idle_emulation_delay, faux_idle_callback)
+
+ mutable_handle[0] = self._loop.call_later(
+ self._idle_emulation_delay, faux_idle_callback)
+
+ return mutable_handle
+
+ def remove_enter_idle(self, handle):
+ """
+ Remove an idle callback.
+
+ Returns True if the handle was removed.
+ """
+ # `handle` is just a list containing the current actual handle
+ return self.remove_alarm(handle[0])
+
+ _exc_info = None
+
+ def _exception_handler(self, loop, context):
+ exc = context.get('exception')
+ if exc:
+ loop.stop()
+ if not isinstance(exc, ExitMainLoop):
+ # Store the exc_info so we can re-raise after the loop stops
+ import sys
+ self._exc_info = sys.exc_info()
+ else:
+ loop.default_exception_handler(context)
+
+ def run(self):
+ """
+ Start the event loop. Exit the loop when any callback raises
+ an exception. If ExitMainLoop is raised, exit cleanly.
+ """
+ self._loop.set_exception_handler(self._exception_handler)
+ self._loop.run_forever()
+ if self._exc_info:
+ raise self._exc_info[0], self._exc_info[1], self._exc_info[2]
+ self._exc_info = None
+
def _refl(name, rval=None, exit=False):
"""
diff --git a/urwid/tests/test_event_loops.py b/urwid/tests/test_event_loops.py
index 0793602..c85bbed 100644
--- a/urwid/tests/test_event_loops.py
+++ b/urwid/tests/test_event_loops.py
@@ -34,6 +34,8 @@ class EventLoopTestMixin(object):
self.assertTrue(evl.remove_watch_file(handle))
self.assertFalse(evl.remove_watch_file(handle))
+ _expected_idle_handle = 1
+
def test_run(self):
evl = self.evl
out = []
@@ -50,7 +52,9 @@ class EventLoopTestMixin(object):
1/0
handle = evl.alarm(0.01, exit_clean)
handle = evl.alarm(0.005, say_hello)
- self.assertEqual(evl.enter_idle(say_waiting), 1)
+ idle_handle = evl.enter_idle(say_waiting)
+ if self._expected_idle_handle is not None:
+ self.assertEqual(idle_handle, 1)
evl.run()
self.assertTrue("hello" in out, out)
self.assertTrue("clean exit"in out, out)
@@ -129,3 +133,15 @@ else:
self.assertTrue("ta" in out, out)
self.assertTrue("hello" in out, out)
self.assertTrue("clean exit" in out, out)
+
+
+try:
+ import asyncio
+except ImportError:
+ pass
+else:
+ class AsyncioEventLoopTest(unittest.TestCase, EventLoopTestMixin):
+ def setUp(self):
+ self.evl = urwid.AsyncioEventLoop()
+
+ _expected_idle_handle = None