diff --git a/.qubesbuilder b/.qubesbuilder index 0d773eb..f88bd63 100644 --- a/.qubesbuilder +++ b/.qubesbuilder @@ -9,6 +9,10 @@ vm: deb: build: - debian-pkg/debian + source: + commands: + - '@PLUGINS_DIR@/source_deb/scripts/debian-quilt @SOURCE_DIR@/series-debian.conf @BUILD_DIR@/debian/patches' + source: files: - url: https://files.pythonhosted.org/packages/source/g/gbulb/gbulb-@VERSION@.tar.gz diff --git a/0001-Correct-markup-on-links-in-changelog.patch b/0001-Correct-markup-on-links-in-changelog.patch new file mode 100644 index 0000000..44ffebd --- /dev/null +++ b/0001-Correct-markup-on-links-in-changelog.patch @@ -0,0 +1,46 @@ +From 3fe5d3f394ded71d0671b54d8431885b57657132 Mon Sep 17 00:00:00 2001 +From: Russell Keith-Magee +Date: Sun, 20 Feb 2022 10:02:54 +0800 +Subject: [PATCH 1/6] Correct markup on links in changelog. + +--- + CHANGELOG.rst | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/CHANGELOG.rst b/CHANGELOG.rst +index 3d08c18..7c610ad 100644 +--- a/CHANGELOG.rst ++++ b/CHANGELOG.rst +@@ -11,7 +11,7 @@ Bugfixes + + * Corrected the import of ``InvalidStateError`` to fix an error seen on Python 3.8+. (`#56 `__) + +-* Reverted the fix from #47; that change led to file descriptor leaks. (`#52 `_) ++* Reverted the fix from #47; that change led to file descriptor leaks. (`#52 `__) + + + 0.6.2 (2021-10-24) +@@ -20,17 +20,17 @@ Bugfixes + Features + ^^^^^^^^ + +-* Added support for Python 3.10. (`#50 `_) ++* Added support for Python 3.10. (`#50 `__) + + Bugfixes + ^^^^^^^^ + +-* Corrects a problem where a socket isn't forgotten and causes 100% CPU load. (`#47 `_) ++* Corrects a problem where a socket isn't forgotten and causes 100% CPU load. (`#47 `__) + + Improved Documentation + ^^^^^^^^^^^^^^^^^^^^^^ + +-* (`#49 `_) ++* (`#49 `__) + + + 0.6.1 (2018-08-09) +-- +2.37.3 + diff --git a/0002-Prevent-the-GTK-event-loop-from-reusing-the-default-.patch b/0002-Prevent-the-GTK-event-loop-from-reusing-the-default-.patch new file mode 100644 index 0000000..345dbb4 --- /dev/null +++ b/0002-Prevent-the-GTK-event-loop-from-reusing-the-default-.patch @@ -0,0 +1,103 @@ +From a04579d25fdedf24ca8c60c84e2683f725683378 Mon Sep 17 00:00:00 2001 +From: Yuki Schlarb +Date: Wed, 12 Oct 2022 22:48:06 +0200 +Subject: [PATCH 2/6] =?UTF-8?q?Prevent=20the=20GTK=C2=A0event=20loop=20fro?= + =?UTF-8?q?m=20reusing=20the=20default=20GLib=20main=20context=20for=20eve?= + =?UTF-8?q?ry=20instance?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This caused “fun” migration of all scheduled callbacks between event loop +instances with equally “fun” to debug asyncio low-level exceptions when +using multi-threading with several concurrently running event loops. + +It is completely unclear to the author of this change why the original change +causes this breakage was introduced in 2015 in commit fde03cde in the first +place. Perhaps the intention was merely to use the GLib default main context +on the default main loop instance? Or perhaps its supposed to be shared between +all instances of the main loop on the UI/main thread (some kind of recursion +workaround maybe)? +--- + src/gbulb/glib_events.py | 11 ++++++----- + src/gbulb/gtk.py | 16 ++-------------- + 2 files changed, 8 insertions(+), 19 deletions(-) + +diff --git a/src/gbulb/glib_events.py b/src/gbulb/glib_events.py +index 057eab5..43933b4 100644 +--- a/src/gbulb/glib_events.py ++++ b/src/gbulb/glib_events.py +@@ -935,6 +935,8 @@ class GLibEventLoopPolicy(events.AbstractEventLoopPolicy): + threads by default have no event loop. + """ + ++ EventLoopCls = GLibEventLoop ++ + # TODO add a parameter to synchronise with GLib's thread default contexts + # (g_main_context_push_thread_default()) + def __init__(self, application=None): +@@ -970,12 +972,11 @@ class GLibEventLoopPolicy(events.AbstractEventLoopPolicy): + + def new_event_loop(self): + """Create a new event loop and return it.""" +- if not self._default_loop and isinstance( +- threading.current_thread(), threading._MainThread +- ): ++ if not self._default_loop and \ ++ threading.main_thread().ident == threading.get_ident(): + loop = self.get_default_loop() + else: +- loop = GLibEventLoop() ++ loop = self.EventLoopCls() + loop._policy = self + + return loop +@@ -987,7 +988,7 @@ class GLibEventLoopPolicy(events.AbstractEventLoopPolicy): + return self._default_loop + + def _new_default_loop(self): +- loop = GLibEventLoop( ++ loop = self.EventLoopCls( + context=GLib.main_context_default(), application=self._application + ) + loop._policy = self +diff --git a/src/gbulb/gtk.py b/src/gbulb/gtk.py +index 1227264..48f375d 100644 +--- a/src/gbulb/gtk.py ++++ b/src/gbulb/gtk.py +@@ -1,6 +1,6 @@ + import threading + +-from gi.repository import GLib, Gtk ++from gi.repository import Gtk + + from .glib_events import GLibEventLoop, GLibEventLoopPolicy + +@@ -17,7 +17,6 @@ class GtkEventLoop(GLibEventLoop): + def __init__(self, **kwargs): + self._recursive = 0 + self._recurselock = threading.Lock() +- kwargs["context"] = GLib.main_context_default() + + super().__init__(**kwargs) + +@@ -55,15 +54,4 @@ class GtkEventLoop(GLibEventLoop): + class GtkEventLoopPolicy(GLibEventLoopPolicy): + """Gtk-based event loop policy. Use this if you are using Gtk.""" + +- def _new_default_loop(self): +- loop = GtkEventLoop(application=self._application) +- loop._policy = self +- return loop +- +- def new_event_loop(self): +- if not self._default_loop: +- loop = self.get_default_loop() +- else: +- loop = GtkEventLoop() +- loop._policy = self +- return loop ++ EventLoopCls = GtkEventLoop +-- +2.37.3 + diff --git a/0003-Apply-pre-commit-changes.patch b/0003-Apply-pre-commit-changes.patch new file mode 100644 index 0000000..09278cb --- /dev/null +++ b/0003-Apply-pre-commit-changes.patch @@ -0,0 +1,625 @@ +From 7f25245f271be8f6a7703819f261c9eb205e38dc Mon Sep 17 00:00:00 2001 +From: Russell Keith-Magee +Date: Fri, 2 Dec 2022 14:27:45 +0800 +Subject: [PATCH 3/6] Apply pre-commit changes. + +--- + examples/gtk.py | 12 ++--- + examples/test-gtk.py | 5 ++- + examples/wait_signal.py | 14 +++--- + src/gbulb/__init__.py | 12 ----- + src/gbulb/glib_events.py | 95 ++++++++++++++++++++------------------- + src/gbulb/gtk.py | 16 ++++--- + src/gbulb/transports.py | 8 ++-- + tests/test_glib_events.py | 7 ++- + tests/test_gtk.py | 7 +-- + tests/test_utils.py | 16 ++++--- + 10 files changed, 94 insertions(+), 98 deletions(-) + +diff --git a/examples/gtk.py b/examples/gtk.py +index 9e8da09..8b60cf5 100644 +--- a/examples/gtk.py ++++ b/examples/gtk.py +@@ -1,16 +1,16 @@ + import asyncio + +-import gbulb +- + from gi.repository import Gtk + ++import gbulb ++ + + @asyncio.coroutine + def counter(label): + i = 0 + while True: + label.set_text(str(i)) +- print('incrementing', i) ++ print("incrementing", i) + yield from asyncio.sleep(1) + i += 1 + +@@ -24,8 +24,8 @@ def main(): + + vbox.pack_start(display, True, True, 0) + +- win = Gtk.Window(title='Counter window') +- win.connect('delete-event', lambda *args: loop.stop()) ++ win = Gtk.Window(title="Counter window") ++ win.connect("delete-event", lambda *args: loop.stop()) + win.add(vbox) + + win.show_all() +@@ -34,5 +34,5 @@ def main(): + loop.run_forever() + + +-if __name__ == '__main__': ++if __name__ == "__main__": + main() +diff --git a/examples/test-gtk.py b/examples/test-gtk.py +index 5cbfd96..7d54874 100755 +--- a/examples/test-gtk.py ++++ b/examples/test-gtk.py +@@ -1,12 +1,13 @@ + #!/usr/bin/env python3 +-from gi.repository import Gtk + import asyncio ++ ++from gi.repository import Gtk ++ + import gbulb + import gbulb.gtk + + + class ProgressBarWindow(Gtk.Window): +- + def __init__(self): + Gtk.Window.__init__(self, title="ProgressBar Demo") + self.set_border_width(10) +diff --git a/examples/wait_signal.py b/examples/wait_signal.py +index 5516a26..51b947f 100644 +--- a/examples/wait_signal.py ++++ b/examples/wait_signal.py +@@ -1,9 +1,9 @@ + import asyncio + +-import gbulb +- + from gi.repository import Gtk + ++import gbulb ++ + + @asyncio.coroutine + def counter(label): +@@ -17,8 +17,8 @@ def counter(label): + @asyncio.coroutine + def text_watcher(label): + while True: +- yield from gbulb.wait_signal(label, 'changed') +- print('label changed', label.get_text()) ++ yield from gbulb.wait_signal(label, "changed") ++ print("label changed", label.get_text()) + + + def main(): +@@ -30,8 +30,8 @@ def main(): + + vbox.pack_start(display, True, True, 0) + +- win = Gtk.Window(title='Counter window') +- win.connect('delete-event', lambda *args: loop.stop()) ++ win = Gtk.Window(title="Counter window") ++ win.connect("delete-event", lambda *args: loop.stop()) + win.add(vbox) + + win.show_all() +@@ -41,5 +41,5 @@ def main(): + loop.run_forever() + + +-if __name__ == '__main__': ++if __name__ == "__main__": + main() +diff --git a/src/gbulb/__init__.py b/src/gbulb/__init__.py +index 4113b06..60ec96a 100644 +--- a/src/gbulb/__init__.py ++++ b/src/gbulb/__init__.py +@@ -1,16 +1,4 @@ + from .glib_events import * # noqa: F401,F403 + from .utils import * # noqa: F401,F403 + +-# __all__ = [ +-# '__version__', +-# ] +- +-# Examples of valid version strings +-# __version__ = '1.2.3.dev1' # Development release 1 +-# __version__ = '1.2.3a1' # Alpha Release 1 +-# __version__ = '1.2.3b1' # Beta Release 1 +-# __version__ = '1.2.3rc1' # RC Release 1 +-# __version__ = '1.2.3' # Final Release +-# __version__ = '1.2.3.post1' # Post Release 1 +- + __version__ = "0.6.3" +diff --git a/src/gbulb/glib_events.py b/src/gbulb/glib_events.py +index 43933b4..aba5f8e 100644 +--- a/src/gbulb/glib_events.py ++++ b/src/gbulb/glib_events.py +@@ -1,4 +1,4 @@ +-"""PEP 3156 event loop based on GLib""" ++"""PEP 3156 event loop based on GLib.""" + + import asyncio + import os +@@ -7,10 +7,10 @@ import socket + import sys + import threading + import weakref +-from asyncio import constants, events, sslproto, tasks, CancelledError ++from asyncio import CancelledError, constants, events, sslproto, tasks + + try: +- from gi.repository import GLib, Gio ++ from gi.repository import Gio, GLib + except ImportError: # pragma: no cover + GLib = None + Gio = None +@@ -18,19 +18,16 @@ except ImportError: # pragma: no cover + + from . import transports + +- + if hasattr(os, "set_blocking"): + + def _set_nonblocking(fd): + os.set_blocking(fd, False) + +- + elif sys.platform == "win32": + + def _set_nonblocking(fd): + pass + +- + else: + import fcntl + +@@ -50,7 +47,6 @@ if sys.platform == "win32": + class AbstractChildWatcher: + pass + +- + else: + from asyncio.unix_events import AbstractChildWatcher + +@@ -63,6 +59,7 @@ class GLibChildWatcher(AbstractChildWatcher): + # On windows on has to open a process handle for the given PID number + # before it's possible to use GLib's `child_watch_add` on it + if sys.platform == "win32": ++ + def _create_handle_for_pid(self, pid): + import _winapi + +@@ -74,6 +71,7 @@ class GLibChildWatcher(AbstractChildWatcher): + _winapi.CloseHandle(handle) + + else: ++ + def _create_handle_for_pid(self, pid): + return pid + +@@ -185,17 +183,16 @@ if sys.platform == "win32": + def close(self): + pass + +- + else: + from asyncio import unix_events + + class GLibBaseEventLoopPlatformExt(unix_events.SelectorEventLoop): +- """ +- Semi-hack that allows us to leverage the existing implementation of Unix domain sockets +- without having to actually implement a selector based event loop. ++ """Semi-hack that allows us to leverage the existing implementation of ++ Unix domain sockets without having to actually implement a selector ++ based event loop. + +- Note that both `__init__` and `close` DO NOT and SHOULD NOT ever call their parent +- implementation! ++ Note that both `__init__` and `close` DO NOT and SHOULD NOT ever ++ call their parent implementation! + """ + + def __init__(self): +@@ -233,16 +230,19 @@ else: + + + class _BaseEventLoop(asyncio.BaseEventLoop): +- """ +- Extra inheritance step that needs to be inserted so that we only ever indirectly inherit from +- `asyncio.BaseEventLoop`. This is necessary as the Unix implementation will also indirectly ++ """Extra inheritance step that needs to be inserted so that we only ever ++ indirectly inherit from `asyncio.BaseEventLoop`. ++ ++ This is necessary as the Unix implementation will also indirectly + inherit from that class (thereby creating diamond inheritance). +- Python permits and fully supports diamond inheritance so this is not a problem. However it +- is, on the other hand, not permitted to inherit from a class both directly *and* indirectly – +- hence we add this intermediate class to make sure that can never happen (see +- https://stackoverflow.com/q/29214888 for a minimal example a forbidden inheritance tree) and +- https://www.python.org/download/releases/2.3/mro/ for some extensive documentation of the +- allowed inheritance structures in python. ++ Python permits and fully supports diamond inheritance so this is not ++ a problem. However it is, on the other hand, not permitted to ++ inherit from a class both directly *and* indirectly – hence we add ++ this intermediate class to make sure that can never happen (see ++ https://stackoverflow.com/q/29214888 for a minimal example a ++ forbidden inheritance tree) and ++ https://www.python.org/download/releases/2.3/mro/ for some extensive ++ documentation of the allowed inheritance structures in python. + """ + + +@@ -316,7 +316,7 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + server_hostname=None, + extra=None, + server=None, +- ssl_handshake_timeout=None ++ ssl_handshake_timeout=None, + ): + """Create SSL transport.""" + # sslproto._is_sslproto_available was removed from asyncio, starting from Python 3.7. +@@ -341,7 +341,7 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + waiter, + server_side, + server_hostname, +- **extra_protocol_kwargs ++ **extra_protocol_kwargs, + ) + transports.SocketTransport( + self, rawsock, ssl_protocol, extra=extra, server=server +@@ -376,7 +376,7 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + stderr, + bufsize, + extra=None, +- **kwargs ++ **kwargs, + ): + """Create subprocess transport.""" + with events.get_child_watcher() as watcher: +@@ -392,7 +392,7 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + bufsize, + waiter=waiter, + extra=extra, +- **kwargs ++ **kwargs, + ) + + watcher.add_child_handler( +@@ -482,12 +482,14 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + from asyncio import coroutines + + if coroutines.iscoroutine(callback) or coroutines.iscoroutinefunction(callback): +- raise TypeError("coroutines cannot be used with {}()".format(name)) ++ raise TypeError(f"coroutines cannot be used with {name}()") + + def _ensure_fd_no_transport(self, fd): + """Ensure that the given file descriptor is NOT used by any transport. + +- Adding another reader to a fd that is already being waited for causes a hang on Windows.""" ++ Adding another reader to a fd that is already being waited for ++ causes a hang on Windows. ++ """ + try: + transport = self._transports[fd] + except KeyError: +@@ -530,7 +532,8 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + def _channel_from_fileobj(self, fileobj): + """Create GLib IOChannel for the given file object. + +- On windows this will only work for files and pipes returned GLib's C library. ++ On windows this will only work for files and pipes returned ++ GLib's C library. + """ + fd = self._fileobj_to_fd(fileobj) + +@@ -556,14 +559,14 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + try: + fd = int(fileobj.fileno()) + except (AttributeError, TypeError, ValueError): +- raise ValueError("Invalid file object: {!r}".format(fileobj)) ++ raise ValueError(f"Invalid file object: {fileobj!r}") + if fd < 0: +- raise ValueError("Invalid file descriptor: {}".format(fd)) ++ raise ValueError(f"Invalid file descriptor: {fd}") + return fd + + def _delayed(self, source, callback=None, *args): +- """Create a future that will complete after the given GLib Source object has become ready +- and the data it tracks has been processed.""" ++ """Create a future that will complete after the given GLib Source ++ object has become ready and the data it tracks has been processed.""" + future = None + + def handle_ready(*args): +@@ -590,14 +593,13 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + return future + + def _socket_handle_errors(self, sock): +- """Raise exceptions for error states (SOL_ERROR) on the given socket object.""" ++ """Raise exceptions for error states (SOL_ERROR) on the given socket ++ object.""" + errno = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) + if errno != 0: + if sys.platform == "win32": +- msg = socket.errorTab.get(errno, "Error {0}".format(errno)) +- raise OSError( +- errno, "[WinError {0}] {1}".format(errno, msg), None, errno +- ) ++ msg = socket.errorTab.get(errno, f"Error {errno}") ++ raise OSError(errno, f"[WinError {errno}] {msg}", None, errno) + else: + raise OSError(errno, os.strerror(errno)) + +@@ -631,7 +633,7 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + async def accept_coro(future, conn): + # Coroutine closing the accept socket if the future is cancelled + try: +- return (await future) ++ return await future + except CancelledError: + sock.close() + raise +@@ -676,6 +678,7 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + ##################################### + def _channel_read(self, channel, nbytes, read_func=None): + if read_func is None: ++ + def read_func(channel, nbytes): + return channel.read(nbytes) + +@@ -928,17 +931,17 @@ class GLibEventLoop(GLibBaseEventLoop): + + + class GLibEventLoopPolicy(events.AbstractEventLoopPolicy): +- """Default GLib event loop policy ++ """Default GLib event loop policy. + +- In this policy, each thread has its own event loop. However, we only +- automatically create an event loop by default for the main thread; other +- threads by default have no event loop. ++ In this policy, each thread has its own event loop. However, we ++ only automatically create an event loop by default for the main ++ thread; other threads by default have no event loop. + """ + + EventLoopCls = GLibEventLoop + + # TODO add a parameter to synchronise with GLib's thread default contexts +- # (g_main_context_push_thread_default()) ++ # (i.e., g_main_context_push_thread_default()) + def __init__(self, application=None): + self._default_loop = None + self._application = application +@@ -960,8 +963,8 @@ class GLibEventLoopPolicy(events.AbstractEventLoopPolicy): + def set_child_watcher(self, watcher): + """Set a child watcher. + +- Must be an an instance of GLibChildWatcher, as it ties in with GLib +- appropriately. ++ Must be an an instance of GLibChildWatcher, as it ties in with ++ GLib appropriately. + """ + + if watcher is not None and not isinstance(watcher, GLibChildWatcher): +diff --git a/src/gbulb/gtk.py b/src/gbulb/gtk.py +index 48f375d..f6f2789 100644 +--- a/src/gbulb/gtk.py ++++ b/src/gbulb/gtk.py +@@ -10,8 +10,8 @@ __all__ = ["GtkEventLoop", "GtkEventLoopPolicy"] + class GtkEventLoop(GLibEventLoop): + """Gtk-based event loop. + +- This loop supports recursion in Gtk, for example for implementing modal +- windows. ++ This loop supports recursion in Gtk, for example for implementing ++ modal windows. + """ + + def __init__(self, **kwargs): +@@ -24,8 +24,8 @@ class GtkEventLoop(GLibEventLoop): + """Run the event loop until Gtk.main_quit is called. + + May be called multiple times to recursively start it again. This +- is useful for implementing asynchronous-like dialogs in code that +- is otherwise not asynchronous, for example modal dialogs. ++ is useful for implementing asynchronous-like dialogs in code ++ that is otherwise not asynchronous, for example modal dialogs. + """ + if self.is_running(): + with self._recurselock: +@@ -41,7 +41,8 @@ class GtkEventLoop(GLibEventLoop): + def stop(self): + """Stop the inner-most event loop. + +- If it's also the outer-most event loop, the event loop will stop. ++ If it's also the outer-most event loop, the event loop will ++ stop. + """ + with self._recurselock: + r = self._recursive +@@ -52,6 +53,9 @@ class GtkEventLoop(GLibEventLoop): + + + class GtkEventLoopPolicy(GLibEventLoopPolicy): +- """Gtk-based event loop policy. Use this if you are using Gtk.""" ++ """Gtk-based event loop policy. ++ ++ Use this if you are using Gtk. ++ """ + + EventLoopCls = GtkEventLoop +diff --git a/src/gbulb/transports.py b/src/gbulb/transports.py +index f2169e0..9e7d446 100644 +--- a/src/gbulb/transports.py ++++ b/src/gbulb/transports.py +@@ -1,7 +1,7 @@ + import collections + import socket + import subprocess +-from asyncio import base_subprocess, transports, CancelledError, InvalidStateError ++from asyncio import CancelledError, InvalidStateError, base_subprocess, transports + + + class BaseTransport(transports.BaseTransport): +@@ -370,9 +370,7 @@ class DatagramTransport(Transport, transports.DatagramTransport): + return + + if self._address and addr not in (None, self._address): +- raise ValueError( +- "Invalid address: must be None or {0}".format(self._address) +- ) ++ raise ValueError(f"Invalid address: must be None or {self._address}") + + # Do not copy the data yet, as we might be able to send it synchronously + super().write((data, addr)) +@@ -421,5 +419,5 @@ class SubprocessTransport(base_subprocess.BaseSubprocessTransport): + stdout=stdout, + stderr=stderr, + bufsize=bufsize, +- **kwargs ++ **kwargs, + ) +diff --git a/tests/test_glib_events.py b/tests/test_glib_events.py +index 9dc959e..8e52c10 100644 +--- a/tests/test_glib_events.py ++++ b/tests/test_glib_events.py +@@ -1,9 +1,8 @@ + import asyncio + import sys ++from unittest import mock, skipIf + + import pytest +- +-from unittest import mock, skipIf + from gi.repository import Gio, GLib + + is_windows = sys.platform == "win32" +@@ -111,7 +110,7 @@ class TestBaseGLibEventLoop: + assert not glib_loop.remove_signal_handler(signal.SIGHUP) + + @skipIf(is_windows, "Unix signal handlers are not supported on Windows") +- @pytest.mark.filterwarnings('ignore:g_unix_signal_source_new') ++ @pytest.mark.filterwarnings("ignore:g_unix_signal_source_new") + def test_remove_signal_handler_sigkill(self, glib_loop): + import signal + +@@ -119,7 +118,7 @@ class TestBaseGLibEventLoop: + glib_loop.add_signal_handler(signal.SIGKILL, None) + + @skipIf(is_windows, "Unix signal handlers are not supported on Windows") +- @pytest.mark.filterwarnings('ignore:g_unix_signal_source_new') ++ @pytest.mark.filterwarnings("ignore:g_unix_signal_source_new") + def test_remove_signal_handler_sigill(self, glib_loop): + import signal + +diff --git a/tests/test_gtk.py b/tests/test_gtk.py +index 673b8e9..7fd6c91 100644 +--- a/tests/test_gtk.py ++++ b/tests/test_gtk.py +@@ -2,7 +2,8 @@ import pytest + + try: + import gi +- gi.require_version('Gtk', '3.0') ++ ++ gi.require_version("Gtk", "3.0") + + from gi.repository import Gtk + except ImportError: # pragma: no cover +@@ -39,12 +40,12 @@ class TestGtkEventLoopPolicy: + loop_count += 1 + + if loop_count == 10: +- print("loop {} stopped".format(i)) ++ print(f"loop {i} stopped") + gtk_loop.stop() + else: + gtk_loop.call_soon(inner) + gtk_loop.run() +- print("loop {} stopped".format(i)) ++ print(f"loop {i} stopped") + gtk_loop.stop() + + gtk_loop.call_soon(inner) +diff --git a/tests/test_utils.py b/tests/test_utils.py +index fdee3ce..4d31d5b 100644 +--- a/tests/test_utils.py ++++ b/tests/test_utils.py +@@ -13,9 +13,10 @@ import pytest + ], + ) + def test_install(gtk, gtk_available): +- from gbulb import install + import sys + ++ from gbulb import install ++ + called = False + + def set_event_loop_policy(pol): +@@ -48,6 +49,7 @@ def test_install(gtk, gtk_available): + + def test_get_event_loop(): + import asyncio ++ + import gbulb + + assert asyncio.get_event_loop() is gbulb.get_event_loop() +@@ -55,7 +57,9 @@ def test_get_event_loop(): + + def test_wait_signal(glib_loop): + import asyncio ++ + from gi.repository import GObject ++ + from gbulb import wait_signal + + class TestObject(GObject.GObject): +@@ -77,16 +81,16 @@ def test_wait_signal(glib_loop): + assert r == (t, "frozen brains tell no tales") + called = True + +- glib_loop.run_until_complete( +- asyncio.wait([waiter(), emitter()], timeout=1) +- ) ++ glib_loop.run_until_complete(asyncio.wait([waiter(), emitter()], timeout=1)) + + assert called + + + def test_wait_signal_cancel(glib_loop): + import asyncio ++ + from gi.repository import GObject ++ + from gbulb import wait_signal + + class TestObject(GObject.GObject): +@@ -118,9 +122,7 @@ def test_wait_signal_cancel(glib_loop): + assert r.cancelled() + cancelled = True + +- glib_loop.run_until_complete( +- asyncio.wait([waiter(), emitter()], timeout=1) +- ) ++ glib_loop.run_until_complete(asyncio.wait([waiter(), emitter()], timeout=1)) + + assert cancelled + assert called +-- +2.37.3 + diff --git a/0004-Minor-readme-updates.patch b/0004-Minor-readme-updates.patch new file mode 100644 index 0000000..2ecbe55 --- /dev/null +++ b/0004-Minor-readme-updates.patch @@ -0,0 +1,34 @@ +From f7b184a380090ed9c953e3eda9895c5b57e340c4 Mon Sep 17 00:00:00 2001 +From: Russell Keith-Magee +Date: Fri, 2 Dec 2022 14:46:04 +0800 +Subject: [PATCH 4/6] Minor readme updates. + +--- + README.rst | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/README.rst b/README.rst +index 4fb002a..ba14f03 100644 +--- a/README.rst ++++ b/README.rst +@@ -36,7 +36,7 @@ If you notice any differences, please report them. + Requirements + ------------ + +-- python 3.6+ ++- python 3.7+ + - pygobject + - glib + - gtk+3 (optional) +@@ -130,7 +130,7 @@ calling GLib's primitives. + Community + --------- + +-gblub is part of the `BeeWare suite`_. You can talk to the community through: ++gbulb is part of the `BeeWare suite`_. You can talk to the community through: + + * `@pybeeware on Twitter `__ + +-- +2.37.3 + diff --git a/0005-Update-tests-to-use-explicit-tasks-not-coroutines-im.patch b/0005-Update-tests-to-use-explicit-tasks-not-coroutines-im.patch new file mode 100644 index 0000000..6c72574 --- /dev/null +++ b/0005-Update-tests-to-use-explicit-tasks-not-coroutines-im.patch @@ -0,0 +1,51 @@ +From f54ab030964e2f1649721ad23e8260b62f5fdaab Mon Sep 17 00:00:00 2001 +From: Russell Keith-Magee +Date: Tue, 6 Dec 2022 09:08:44 +0800 +Subject: [PATCH 5/6] Update tests to use explicit tasks, not coroutines + implied to be tasks. + +--- + tests/test_utils.py | 20 ++++++++++++++++++-- + 1 file changed, 18 insertions(+), 2 deletions(-) + +diff --git a/tests/test_utils.py b/tests/test_utils.py +index 4d31d5b..0982216 100644 +--- a/tests/test_utils.py ++++ b/tests/test_utils.py +@@ -81,7 +81,15 @@ def test_wait_signal(glib_loop): + assert r == (t, "frozen brains tell no tales") + called = True + +- glib_loop.run_until_complete(asyncio.wait([waiter(), emitter()], timeout=1)) ++ glib_loop.run_until_complete( ++ asyncio.wait( ++ [ ++ glib_loop.create_task(waiter()), ++ glib_loop.create_task(emitter()), ++ ], ++ timeout=1, ++ ) ++ ) + + assert called + +@@ -122,7 +130,15 @@ def test_wait_signal_cancel(glib_loop): + assert r.cancelled() + cancelled = True + +- glib_loop.run_until_complete(asyncio.wait([waiter(), emitter()], timeout=1)) ++ glib_loop.run_until_complete( ++ asyncio.wait( ++ [ ++ glib_loop.create_task(waiter()), ++ glib_loop.create_task(emitter()), ++ ], ++ timeout=1, ++ ) ++ ) + + assert cancelled + assert called +-- +2.37.3 + diff --git a/0006-Add-support-for-ssl-timeout-parameters.patch b/0006-Add-support-for-ssl-timeout-parameters.patch new file mode 100644 index 0000000..17a5b90 --- /dev/null +++ b/0006-Add-support-for-ssl-timeout-parameters.patch @@ -0,0 +1,64 @@ +From c9503099d2599daff4cebab8b43ccde666222ba7 Mon Sep 17 00:00:00 2001 +From: Russell Keith-Magee +Date: Tue, 6 Dec 2022 09:09:07 +0800 +Subject: [PATCH 6/6] Add support for ssl timeout parameters. + +--- + src/gbulb/glib_events.py | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/src/gbulb/glib_events.py b/src/gbulb/glib_events.py +index aba5f8e..832f119 100644 +--- a/src/gbulb/glib_events.py ++++ b/src/gbulb/glib_events.py +@@ -317,6 +317,7 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + extra=None, + server=None, + ssl_handshake_timeout=None, ++ ssl_shutdown_timeout=None, + ): + """Create SSL transport.""" + # sslproto._is_sslproto_available was removed from asyncio, starting from Python 3.7. +@@ -329,10 +330,13 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + " or newer (ssl.MemoryBIO) to support " + "SSL" + ) +- # Support for the ssl_handshake_timeout keyword argument was added in Python 3.7. + extra_protocol_kwargs = {} ++ # Support for the ssl_handshake_timeout keyword argument was added in Python 3.7. + if sys.version_info[:2] >= (3, 7): + extra_protocol_kwargs["ssl_handshake_timeout"] = ssl_handshake_timeout ++ # Support for the ssl_shutdown_timeout keyword argument was added in Python 3.11. ++ if sys.version_info[:2] >= (3, 11): ++ extra_protocol_kwargs["ssl_shutdown_timeout"] = ssl_shutdown_timeout + + ssl_protocol = sslproto.SSLProtocol( + self, +@@ -429,6 +433,7 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + server=None, + backlog=100, + ssl_handshake_timeout=getattr(constants, "SSL_HANDSHAKE_TIMEOUT", 60.0), ++ ssl_shutdown_timeout=getattr(constants, "SSL_SHUTDOWN_TIMEOUT", 60.0), + ): + self._transports[sock.fileno()] = server + +@@ -438,7 +443,6 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + (conn, addr) = f.result() + protocol = protocol_factory() + if sslcontext is not None: +- # FIXME: add ssl_handshake_timeout to this call once 3.7 support is merged in. + self._make_ssl_transport( + conn, + protocol, +@@ -446,6 +450,8 @@ class GLibBaseEventLoop(_BaseEventLoop, GLibBaseEventLoopPlatformExt): + server_side=True, + extra={"peername": addr}, + server=server, ++ ssl_handshake_timeout=ssl_handshake_timeout, ++ ssl_shutdown_timeout=ssl_shutdown_timeout, + ) + else: + self._make_socket_transport( +-- +2.37.3 + diff --git a/Makefile.builder b/Makefile.builder index 56676c2..2c123a1 100644 --- a/Makefile.builder +++ b/Makefile.builder @@ -10,5 +10,7 @@ source-debian-copy-in: VERSION = $(shell cat $(ORIG_SRC)/version) source-debian-copy-in: ORIG_FILE = $(CHROOT_DIR)/$(DIST_SRC)/python3-gbulb_$(VERSION).orig.tar.gz source-debian-copy-in: SRC_FILE = $(ORIG_SRC)/gbulb-$(VERSION).tar.gz source-debian-copy-in: + mkdir -p "$(CHROOT_DIR)/$(DIST_SRC)/debian/patches" + $(ORIG_SRC)/debian-quilt $(ORIG_SRC)/series-debian.conf $(CHROOT_DIR)/$(DIST_SRC)/debian/patches cp -p $(SRC_FILE) $(ORIG_FILE) tar xzf $(SRC_FILE) -C $(CHROOT_DIR)/$(DIST_SRC)/debian-pkg --strip-components=1 diff --git a/python3-gbulb.spec.in b/python3-gbulb.spec.in index 545b643..f15c6e4 100644 --- a/python3-gbulb.spec.in +++ b/python3-gbulb.spec.in @@ -9,6 +9,13 @@ Summary: GLib event loop for asyncio (PEP 3156) License: Apache-2.0 Group: Development/Languages/Python Source: https://files.pythonhosted.org/packages/92/cb/d2a0e4899cde5aa797e31d77a0a7422dcd188d880bb38d0e9d1b1196e5c6/%{mod_name}-%{version}.tar.gz +Patch1: 0001-Correct-markup-on-links-in-changelog.patch +Patch2: 0002-Prevent-the-GTK-event-loop-from-reusing-the-default-.patch +Patch3: 0003-Apply-pre-commit-changes.patch +Patch4: 0004-Minor-readme-updates.patch +Patch5: 0005-Update-tests-to-use-explicit-tasks-not-coroutines-im.patch +Patch6: 0006-Add-support-for-ssl-timeout-parameters.patch + BuildRequires: python%{python3_pkgversion}-devel BuildRequires: python%{python3_pkgversion}-setuptools %{?python_provide:%python_provide python%{python3_pkgversion}-%{mod_name}} diff --git a/series-debian.conf b/series-debian.conf new file mode 100644 index 0000000..485887a --- /dev/null +++ b/series-debian.conf @@ -0,0 +1,6 @@ +0001-Correct-markup-on-links-in-changelog.patch +0002-Prevent-the-GTK-event-loop-from-reusing-the-default-.patch +0003-Apply-pre-commit-changes.patch +0004-Minor-readme-updates.patch +0005-Update-tests-to-use-explicit-tasks-not-coroutines-im.patch +0006-Add-support-for-ssl-timeout-parameters.patch