From abd669fc0da3cf643a626d39e9ecae7e23be598c Mon Sep 17 00:00:00 2001 From: Tycho Andersen Date: Wed, 23 Nov 2022 16:45:38 -0700 Subject: [PATCH 1/4] widget: log who's poll() returned None Previously, we just got: 2022-11-23 16:33:11,669 WARNING libqtile base.py:on_done():L803 poll() returned None, not rescheduling but it is useful to know which widget is failing, so we know where to debug :) Signed-off-by: Tycho Andersen --- libqtile/widget/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libqtile/widget/base.py b/libqtile/widget/base.py index b7dd66a21e..788d8573d1 100644 --- a/libqtile/widget/base.py +++ b/libqtile/widget/base.py @@ -800,7 +800,7 @@ def on_done(future): except Exception: logger.exception("Failed to reschedule.") else: - logger.warning("poll() returned None, not rescheduling") + logger.warning("%s's poll() returned None, not rescheduling", self.name) self.future = self.qtile.run_in_executor(self.poll) self.future.add_done_callback(on_done) From 1939616694bc6409c281f25bb5c6013fcba25aa4 Mon Sep 17 00:00:00 2001 From: Tycho Andersen Date: Wed, 23 Nov 2022 16:46:52 -0700 Subject: [PATCH 2/4] idlerpg: set json/xml flags explicitly ...instead of using the defaults framework. These are really hard coded settings for this widget, users can't really change them. Add to that that this wasn't working (since json was already set, it was masked), so the idlerpg widget always tried to parse the response as json, and always failed: 2022-11-23 16:33:11,667 ERROR libqtile base.py:on_done():L789 poll() raised exceptions, not rescheduling Traceback (most recent call last): File "/home/tycho/.local/lib/python3.10/site-packages/libqtile/widget/base.py", line 786, in on_done result = future.result() File "/usr/lib/python3.10/concurrent/futures/thread.py", line 58, in run result = self.fn(*self.args, **self.kwargs) File "/home/tycho/.local/lib/python3.10/site-packages/libqtile/widget/generic_poll_text.py", line 85, in poll body = self.fetch() File "/home/tycho/.local/lib/python3.10/site-packages/libqtile/widget/generic_poll_text.py", line 74, in fetch body = json.loads(body) File "/usr/lib/python3.10/json/__init__.py", line 346, in loads return _default_decoder.decode(s) File "/usr/lib/python3.10/json/decoder.py", line 337, in decode obj, end = self.raw_decode(s, idx=_w(s, 0).end()) File "/usr/lib/python3.10/json/decoder.py", line 355, in raw_decode raise JSONDecodeError("Expecting value", s, err.value) from None json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) Let's just explicitly set these in __init__ after all the other initialization has completed. I have no idea when this broke, because I swear this used to work. But I haven't had time to debug it until recently :) Signed-off-by: Tycho Andersen --- libqtile/widget/idlerpg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libqtile/widget/idlerpg.py b/libqtile/widget/idlerpg.py index 8be1adb4a5..361da57415 100644 --- a/libqtile/widget/idlerpg.py +++ b/libqtile/widget/idlerpg.py @@ -40,13 +40,13 @@ class IdleRPG(GenPollUrl): defaults = [ ("format", "IdleRPG: {online} TTL: {ttl}", "Display format"), - ("json", False, "Not json :)"), - ("xml", True, "Is XML :)"), ] def __init__(self, **config): GenPollUrl.__init__(self, **config) self.add_defaults(IdleRPG.defaults) + self.json = False + self.xml = True def parse(self, body): formatted = {} From 5ef58905cba3b0a8c04585c31d3bf06ec12c90a5 Mon Sep 17 00:00:00 2001 From: Tycho Andersen Date: Tue, 29 Nov 2022 08:54:42 -0700 Subject: [PATCH 3/4] drop support for python 3.8 pyflakes 3.0 dropped support for python2.x-style comment type annotations, so we get a lot of F401s, which the next patch will fix. We can't use dict[] subscripting until 3.9: https://bugs.python.org/issue45117 So we could either switch to typing.Dict which is more verbose, pin pyflakes, or drop support for 3.8. Let's move to the future and drop 3.8. Signed-off-by: Tycho Andersen --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 6 +++--- CHANGELOG | 2 +- libqtile/scripts/check.py | 7 ------- setup.cfg | 3 +-- tox.ini | 4 +--- 6 files changed, 7 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bb6467644..a34dd29e02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: # - /libqtile/scripts/check.py mypy argument # If adding new python versions, consider also updating # python version in .readthedocs.yaml - python-version: [pypy-3.9, 3.8, 3.9, '3.10', '3.11'] + python-version: [pypy-3.9, 3.9, '3.10', '3.11'] steps: - uses: actions/checkout@v2 - name: Set up python ${{ matrix.python-version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 115e43df8d..8efb8de304 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: container: quay.io/pypa/manylinux_2_24_x86_64 strategy: matrix: - python-version: ["cp3.8", "cp3.9", "cp3.10", "cp3.11", "pp3.9"] + python-version: ["cp3.9", "cp3.10", "cp3.11", "pp3.9"] steps: - name: Install dependencies run: | @@ -45,7 +45,7 @@ jobs: needs: build-wheel strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11"] include: - python-version: "pypy-3.9" manual-version: "pp3.9" @@ -103,7 +103,7 @@ jobs: needs: [test-wheel, build-source] strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11"] include: - python-version: "pypy-3.9" manual-version: "pp3.9" diff --git a/CHANGELOG b/CHANGELOG index 43aa46130f..6a82c506ab 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,7 +31,7 @@ Qtile x.xx.x, released XXXX-XX-XX: - Fix `Notify` bug when apps close notifications. * python version support - We have added support for python 3.11 and pypy 3.9. - - python 3.7 and pypy 3.7 are not longer supported. + - python 3.7, 3.8 and pypy 3.7 are not longer supported. - Fix bug where `StatusNotifier` does not update icons Qtile 0.22.0, released 2022-09-22: diff --git a/libqtile/scripts/check.py b/libqtile/scripts/check.py index 71a9933840..8af15c4c83 100644 --- a/libqtile/scripts/check.py +++ b/libqtile/scripts/check.py @@ -97,13 +97,6 @@ def type_check_config_args(config_file): def check_deps() -> None: ok = True - if sys.version_info.minor < 8: # < 3.8 - print( - "mypy check is not supported for the current version of Python, " - "please update to at least 3.8 and try again." - ) - ok = False - for dep in ["mypy", "stubtest"]: if shutil.which(dep) is None: print(f"{dep} was not found. Please install it and try again.") diff --git a/setup.cfg b/setup.cfg index a412161010..709857d441 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,7 +21,6 @@ classifiers = Operating System :: POSIX :: BSD :: FreeBSD Operating System :: POSIX :: Linux Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 @@ -36,7 +35,7 @@ project_urls = [options] packages = find: -python_requires >= 3.8 +python_requires >= 3.9 setup_requires = cffi >= 1.1.0 cairocffi[xcb] >= 0.9.0 diff --git a/tox.ini b/tox.ini index 750c35f1af..bcb950a570 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,6 @@ skipsdist=True minversion = 1.8 envlist = pypy3, - py38, py39, py310, py311, @@ -49,7 +48,7 @@ commands = # py310 currently fails with -W error, see: https://gitlab.gnome.org/GNOME/pygobject/-/issues/476 # pypy3 is very slow when running coverage reports so we skip it pypy3: python3 -m pytest -W error --backend=x11 --backend=wayland {posargs} - py{38,39}: coverage run -m pytest -W error --backend=x11 --backend=wayland {posargs} + py39: coverage run -m pytest -W error --backend=x11 --backend=wayland {posargs} py310: coverage run -m pytest --backend=x11 --backend=wayland {posargs} # dbus-next 0.2.3 causes a segfault when run in GLib's mainloop in python 3.11 @@ -135,7 +134,6 @@ commands = [gh-actions] python = pypy-3.9: pypy3 - 3.8: py38, mypy 3.9: py39, mypy 3.10: py310, mypy 3.11: py311, mypy, packaging, pep8, codestyle, docstyle, vulture From 2e583eef8df2cf7f72f6a612e9f8e70b98588037 Mon Sep 17 00:00:00 2001 From: Tycho Andersen Date: Tue, 29 Nov 2022 09:01:02 -0700 Subject: [PATCH 4/4] *: fix python2 type annotations pyflakes 3.0.0 drops support for the comment style annotations, in favor of the actual syntactic version of type annotations. This gets everything to pass flake8 again, but there are still many instances of " # type" that we should fix. https://github.com/PyCQA/pyflakes/issues/747#issuecomment-1326939923 Signed-off-by: Tycho Andersen --- libqtile/extension/base.py | 4 ++-- libqtile/layout/base.py | 2 +- libqtile/widget/base.py | 4 ++-- libqtile/widget/battery.py | 10 +++++----- libqtile/widget/generic_poll_text.py | 4 ++-- libqtile/widget/groupbox.py | 4 ++-- libqtile/widget/open_weather.py | 4 ++-- libqtile/widget/textbox.py | 4 ++-- libqtile/widget/widgetbox.py | 4 ++-- libqtile/widget/window_count.py | 4 ++-- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/libqtile/extension/base.py b/libqtile/extension/base.py index 39ce1d443b..00f08b4779 100644 --- a/libqtile/extension/base.py +++ b/libqtile/extension/base.py @@ -95,13 +95,13 @@ class RunCommand(_Extension): `client `_. """ - defaults = [ + defaults: list[tuple[str, Any, str]] = [ # NOTE: Do not use a list as a default value, since it would be shared # among all the objects inheriting this class, and if one of them # modified it, all the other objects would see the modified list; # use a string or a tuple instead, which are immutable ("command", None, "the command to be launched (string or list with arguments)"), - ] # type: list[tuple[str, Any, str]] + ] def __init__(self, **config): _Extension.__init__(self, **config) diff --git a/libqtile/layout/base.py b/libqtile/layout/base.py index 8eabe9899b..5bda866fad 100644 --- a/libqtile/layout/base.py +++ b/libqtile/layout/base.py @@ -37,7 +37,7 @@ class Layout(CommandObject, configurable.Configurable, metaclass=ABCMeta): """This class defines the API that should be exposed by all layouts""" - defaults = [] # type: list[tuple[str, Any, str]] + defaults: list[tuple[str, Any, str]] = [] def __init__(self, **config): # name is a little odd; we can't resolve it until the class is defined diff --git a/libqtile/widget/base.py b/libqtile/widget/base.py index 788d8573d1..1590759beb 100644 --- a/libqtile/widget/base.py +++ b/libqtile/widget/base.py @@ -138,14 +138,14 @@ def open_calendar(): offsetx: int = 0 offsety: int = 0 - defaults = [ + defaults: list[tuple[str, Any, str]] = [ ("background", None, "Widget background color"), ( "mouse_callbacks", {}, "Dict of mouse button press callback functions. Accepts functions and ``lazy`` calls.", ), - ] # type: list[tuple[str, Any, str]] + ] def __init__(self, length, **config): """ diff --git a/libqtile/widget/battery.py b/libqtile/widget/battery.py index 6a0258ccda..e7f316ba98 100644 --- a/libqtile/widget/battery.py +++ b/libqtile/widget/battery.py @@ -192,7 +192,7 @@ class _LinuxBattery(_Battery, configurable.Configurable): ), ] - filenames = {} # type: dict + filenames: dict = {} BAT_DIR = "/sys/class/power_supply" @@ -451,12 +451,12 @@ class BatteryIcon(base._Widget): """Battery life indicator widget.""" orientations = base.ORIENTATION_HORIZONTAL - defaults = [ + defaults: list[tuple[str, Any, str]] = [ ("battery", 0, "Which battery should be monitored"), ("update_interval", 60, "Seconds between status updates"), ("theme_path", default_icon_path(), "Path of the icons"), ("scale", 1, "Scale factor relative to the bar height. " "Defaults to 1"), - ] # type: list[tuple[str, Any, str]] + ] icon_names = ( "battery-missing", @@ -481,12 +481,12 @@ def __init__(self, **config) -> None: base._Widget.__init__(self, length=bar.CALCULATED, **config) self.add_defaults(self.defaults) - self.scale = 1.0 / self.scale # type: float + self.scale: float = 1.0 / self.scale self.length_type = bar.STATIC self.length = 0 self.image_padding = 0 - self.surfaces = {} # type: dict[str, Img] + self.surfaces: dict[str, Img] = {} self.current_icon = "battery-missing" self._battery = self._load_battery(**config) diff --git a/libqtile/widget/generic_poll_text.py b/libqtile/widget/generic_poll_text.py index 97058eb711..3db76120a7 100644 --- a/libqtile/widget/generic_poll_text.py +++ b/libqtile/widget/generic_poll_text.py @@ -40,7 +40,7 @@ def poll(self): class GenPollUrl(base.ThreadPoolText): """A generic text widget that polls an url and parses it using parse function""" - defaults = [ + defaults: list[tuple[str, Any, str]] = [ ("url", None, "Url"), ("data", None, "Post Data"), ("parse", None, "Parse Function"), @@ -48,7 +48,7 @@ class GenPollUrl(base.ThreadPoolText): ("user_agent", "Qtile", "Set the user agent"), ("headers", {}, "Extra Headers"), ("xml", False, "Is XML?"), - ] # type: list[tuple[str, Any, str]] + ] def __init__(self, **config): base.ThreadPoolText.__init__(self, "", **config) diff --git a/libqtile/widget/groupbox.py b/libqtile/widget/groupbox.py index 400c9cd815..71e7040884 100644 --- a/libqtile/widget/groupbox.py +++ b/libqtile/widget/groupbox.py @@ -39,10 +39,10 @@ class _GroupBase(base._TextBox, base.PaddingMixin, base.MarginMixin): - defaults = [ + defaults: list[tuple[str, Any, str]] = [ ("borderwidth", 3, "Current group border width"), ("center_aligned", True, "center-aligned group box"), - ] # type: list[tuple[str, Any, str]] + ] def __init__(self, **config): base._TextBox.__init__(self, **config) diff --git a/libqtile/widget/open_weather.py b/libqtile/widget/open_weather.py index c6e7b63a8e..125bf72335 100644 --- a/libqtile/widget/open_weather.py +++ b/libqtile/widget/open_weather.py @@ -188,7 +188,7 @@ class OpenWeather(GenPollUrl): "50n": "🌫", } - defaults = [ + defaults: list[tuple[str, Any, str]] = [ # One of (cityid, location, zip, coordinates) must be set. ( "app_key", @@ -255,7 +255,7 @@ class OpenWeather(GenPollUrl): dict(), "Dictionary of weather symbols. Can be used to override default symbols.", ), - ] # type: list[tuple[str, Any, str]] + ] def __init__(self, **config): GenPollUrl.__init__(self, **config) diff --git a/libqtile/widget/textbox.py b/libqtile/widget/textbox.py index 7cd54f4fee..f65f1be281 100644 --- a/libqtile/widget/textbox.py +++ b/libqtile/widget/textbox.py @@ -33,13 +33,13 @@ class TextBox(base._TextBox): """A flexible textbox that can be updated from bound keys, scripts, and qshell.""" - defaults = [ + defaults: list[tuple[str, Any, str]] = [ ("font", "sans", "Text font"), ("fontsize", None, "Font pixel size. Calculated if None."), ("fontshadow", None, "font shadow color, default is None(no shadow)"), ("padding", None, "Padding left and right. Calculated if None."), ("foreground", "#ffffff", "Foreground colour."), - ] # type: list[tuple[str, Any, str]] + ] def __init__(self, text=" ", width=bar.CALCULATED, **config): base._TextBox.__init__(self, text=text, width=width, **config) diff --git a/libqtile/widget/widgetbox.py b/libqtile/widget/widgetbox.py index 1b7cac1a89..a5e88e079b 100644 --- a/libqtile/widget/widgetbox.py +++ b/libqtile/widget/widgetbox.py @@ -55,7 +55,7 @@ class WidgetBox(base._Widget): """ orientations = base.ORIENTATION_HORIZONTAL - defaults = [ + defaults: list[tuple[str, Any, str]] = [ ("font", "sans", "Text font"), ("fontsize", None, "Font pixel size. Calculated if None."), ("fontshadow", None, "font shadow color, default is None(no shadow)"), @@ -68,7 +68,7 @@ class WidgetBox(base._Widget): ("text_closed", "[<]", "Text when box is closed"), ("text_open", "[>]", "Text when box is open"), ("widgets", list(), "A list of widgets to include in the box"), - ] # type: list[tuple[str, Any, str]] + ] def __init__(self, _widgets: list[base._Widget] | None = None, **config): base._Widget.__init__(self, bar.CALCULATED, **config) diff --git a/libqtile/widget/window_count.py b/libqtile/widget/window_count.py index 35d542fa94..5db97e4a3d 100644 --- a/libqtile/widget/window_count.py +++ b/libqtile/widget/window_count.py @@ -33,7 +33,7 @@ class WindowCount(base._TextBox): current group of the screen on which the widget is. """ - defaults = [ + defaults: list[tuple[str, Any, str]] = [ ("font", "sans", "Text font"), ("fontsize", None, "Font pixel size. Calculated if None."), ("fontshadow", None, "font shadow color, default is None(no shadow)"), @@ -41,7 +41,7 @@ class WindowCount(base._TextBox): ("foreground", "#ffffff", "Foreground colour."), ("text_format", "{num}", "Format for message"), ("show_zero", False, "Show window count when no windows"), - ] # type: list[tuple[str, Any, str]] + ] def __init__(self, width=bar.CALCULATED, **config): base._TextBox.__init__(self, width=width, **config)