Skip to content

Commit

Permalink
[BREAKING CHANGE] Refactoring: Split event loop in several modules (#537
Browse files Browse the repository at this point in the history
)

* [BREAKING CHANGE] Refactoring: Split event loop in several modules

* `urwid.main_loop` is split into multiple modules which is easier to maintain
* `urwid.compat` is not used anymore and removed
* `TornadoEventLoop`, `GLibEventLoop`, `TwistedEventLoop` and `TrioEventLoop`
  accessible ONLY if required dependencies installed
  (like: Tornado installed -> `TornadoEventLoop` is accessible for import)
* `TornadoEventLoop` use the same idle logic as `AsyncioLoop`:
  tornado.ioloop.IOLoop is asyncio based.
* Trio < 0.15 is not supported. Version 0.15 was released almost 3 years ago.
* Tornado < 5.0 is not supported. Tornado 5.0 was released 5 years ago.

* Remove useless shebang

* `EventLoop` should be real abstract
* add new module docstrings

* Fix docstrings

* remove unneeded import

---------

Co-authored-by: Aleksei Stepanov <alekseis@nvidia.com>
  • Loading branch information
penguinolog and Aleksei Stepanov committed Apr 18, 2023
1 parent db10343 commit d1710f0
Show file tree
Hide file tree
Showing 18 changed files with 1,935 additions and 1,701 deletions.
4 changes: 1 addition & 3 deletions docs/manual/mainloop.rst
Expand Up @@ -143,9 +143,7 @@ This event loop integrates with Tornado.
``AsyncioEventLoop``
--------------------

This event loop integrates with the asyncio module in Python 3.4,
the asyncio package available for Python 3.3 or the trollius
package available for Python 2.
This event loop integrates with the asyncio module in Python.

::

Expand Down
2 changes: 1 addition & 1 deletion test_requirements.txt
@@ -1,4 +1,4 @@
tornado<5.0.0
tornado>=5
coverage[toml]
twisted
trio
32 changes: 23 additions & 9 deletions urwid/__init__.py
Expand Up @@ -98,7 +98,7 @@
scale_bar_values,
)
from urwid.listbox import ListBox, ListBoxError, ListWalker, ListWalkerError, SimpleFocusListWalker, SimpleListWalker
from urwid.main_loop import AsyncioEventLoop, ExitMainLoop, GLibEventLoop, MainLoop, SelectEventLoop, TornadoEventLoop
from urwid.event_loop import EventLoop, AsyncioEventLoop, ExitMainLoop, MainLoop, SelectEventLoop
from urwid.monitored_list import MonitoredFocusList, MonitoredList
from urwid.signals import MetaSignals, Signals, connect_signal, disconnect_signal, emit_signal, register_signal
from urwid.version import VERSION, __version__
Expand Down Expand Up @@ -141,14 +141,6 @@
)
from urwid.wimp import Button, CheckBox, CheckBoxError, PopUpLauncher, PopUpTarget, RadioButton, SelectableIcon

try:
from urwid.main_loop import TwistedEventLoop
except ImportError:
pass
try:
from urwid.main_loop import TrioEventLoop
except ImportError:
pass
from urwid import raw_display
from urwid.display_common import (
BLACK,
Expand Down Expand Up @@ -197,3 +189,25 @@
within_double_byte,
)
from urwid.vterm import TermCanvas, TermCharset, Terminal, TermModes, TermScroller

# Optional event loops with external dependencies

try:
from urwid.event_loop import TornadoEventLoop
except ImportError:
pass

try:
from urwid.event_loop import GLibEventLoop
except ImportError:
pass

try:
from urwid.event_loop import TwistedEventLoop
except ImportError:
pass

try:
from urwid.event_loop import TrioEventLoop
except ImportError:
pass
36 changes: 0 additions & 36 deletions urwid/compat.py

This file was deleted.

33 changes: 33 additions & 0 deletions urwid/event_loop/__init__.py
@@ -0,0 +1,33 @@
"""Package with EventLoop implementations for urwid."""

from __future__ import annotations

from .abstract_loop import EventLoop, ExitMainLoop
from .asyncio_loop import AsyncioEventLoop
from .main_loop import MainLoop
from .select_loop import SelectEventLoop

try:
from .twisted_loop import TwistedEventLoop
except ImportError:
pass

try:
from .tornado_loop import TornadoEventLoop
except ImportError:
pass

try:
from .glib_loop import GLibEventLoop
except ImportError:
pass

try:
from .twisted_loop import TwistedEventLoop
except ImportError:
pass

try:
from .trio_loop import TrioEventLoop
except ImportError:
pass
143 changes: 143 additions & 0 deletions urwid/event_loop/abstract_loop.py
@@ -0,0 +1,143 @@
# Urwid main loop code
# Copyright (C) 2004-2012 Ian Ward
# Copyright (C) 2008 Walter Mundt
# Copyright (C) 2009 Andrew Psaltis
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Urwid web site: https://urwid.org/

"""Abstract shared code for urwid EventLoop implementation."""

from __future__ import annotations

import abc
import signal
import typing
from collections.abc import Callable

if typing.TYPE_CHECKING:
from types import FrameType

__all__ = ("ExitMainLoop", "EventLoop")


class ExitMainLoop(Exception):
"""
When this exception is raised within a main loop the main loop
will exit cleanly.
"""
pass


class EventLoop(abc.ABC):
"""
Abstract class representing an event loop to be used by :class:`MainLoop`.
"""

@abc.abstractmethod
def alarm(self, seconds: float | int, callback: Callable[[], typing.Any]) -> typing.Any:
"""
Call callback() a given time from now. No parameters are
passed to callback.
This method has no default implementation.
Returns a handle that may be passed to remove_alarm()
seconds -- floating point time to wait before calling callback
callback -- function to call from event loop
"""

@abc.abstractmethod
def enter_idle(self, callback):
"""
Add a callback for entering idle.
This method has no default implementation.
Returns a handle that may be passed to remove_idle()
"""

@abc.abstractmethod
def remove_alarm(self, handle) -> bool:
"""
Remove an alarm.
This method has no default implementation.
Returns True if the alarm exists, False otherwise
"""

@abc.abstractmethod
def remove_enter_idle(self, handle) -> bool:
"""
Remove an idle callback.
This method has no default implementation.
Returns True if the handle was removed.
"""

@abc.abstractmethod
def remove_watch_file(self, handle) -> bool:
"""
Remove an input file.
This method has no default implementation.
Returns True if the input file exists, False otherwise
"""

@abc.abstractmethod
def run(self) -> None:
"""
Start the event loop. Exit the loop when any callback raises
an exception. If ExitMainLoop is raised, exit cleanly.
This method has no default implementation.
"""

@abc.abstractmethod
def watch_file(self, fd: int, callback: Callable[[], typing.Any]):
"""
Call callback() when fd has some data to read. No parameters
are passed to callback.
This method has no default implementation.
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
"""

def set_signal_handler(
self,
signum: int,
handler: Callable[[int, FrameType | None], typing.Any] | int | signal.Handlers,
) -> Callable[[int, FrameType | None], typing.Any] | int | signal.Handlers | None:
"""
Sets the signal handler for signal signum.
The default implementation of :meth:`set_signal_handler`
is simply a proxy function that calls :func:`signal.signal()`
and returns the resulting value.
signum -- signal number
handler -- function (taking signum as its single argument),
or `signal.SIG_IGN`, or `signal.SIG_DFL`
"""
return signal.signal(signum, handler)

0 comments on commit d1710f0

Please sign in to comment.