Repository: qtile/qtile (branch: master)
Commit date: 2023-11-08T11:10:02Z

Workflow file: .github/workflows/pre_commit.yml (Run pre-commit)
name: Run pre-commit

on:
    push:
    pull_request:

jobs:
    check:
        runs-on: ubuntu-latest
        services:
          pypi_wayback:
            image: ghcr.io/waleko/pypi-wayback
            ports:
              - 8629:8080
        steps:
            - uses: actions/checkout@v4
            - uses: actions/setup-python@v4
            - name: Install dependencies
              run: |
                sudo apt update
                sudo apt install --no-install-recommends libxkbcommon-dev
            - uses: pre-commit/action@v3.0.0

env:
  PIP_INDEX_URL: http://localhost:8629/2023-11-08
  UV_INDEX_URL: http://localhost:8629/2023-11-08


Failed build logs:
step_name: check/5_Run pre-commitaction@v3.0.0.txt
log: ##[group]Run pre-commit/action@v3.0.0
with:
  extra_args: --all-files
##[endgroup]
##[group]Run python -m pip install pre-commit
[36;1mpython -m pip install pre-commit[0m
shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
##[endgroup]
Defaulting to user installation because normal site-packages is not writeable
Collecting pre-commit
  Downloading pre_commit-3.5.0-py2.py3-none-any.whl (203 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 203.7/203.7 KB 3.9 MB/s eta 0:00:00
Collecting identify>=1.0.0
  Downloading identify-2.5.31-py2.py3-none-any.whl (98 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 98.9/98.9 KB 9.1 MB/s eta 0:00:00
Collecting cfgv>=2.0.0
  Downloading cfgv-3.4.0-py2.py3-none-any.whl (7.2 kB)
Requirement already satisfied: pyyaml>=5.1 in /usr/lib/python3/dist-packages (from pre-commit) (5.4.1)
Collecting virtualenv>=20.10.0
  Downloading virtualenv-20.24.6-py3-none-any.whl (3.8 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.8/3.8 MB 21.1 MB/s eta 0:00:00
Collecting nodeenv>=0.11.1
  Downloading nodeenv-1.8.0-py2.py3-none-any.whl (22 kB)
Requirement already satisfied: setuptools in /usr/lib/python3/dist-packages (from nodeenv>=0.11.1->pre-commit) (59.6.0)
Collecting distlib<1,>=0.3.7
  Downloading distlib-0.3.7-py2.py3-none-any.whl (468 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 468.9/468.9 KB 37.7 MB/s eta 0:00:00
Collecting platformdirs<4,>=3.9.1
  Downloading platformdirs-3.11.0-py3-none-any.whl (17 kB)
Collecting filelock<4,>=3.12.2
  Downloading filelock-3.13.1-py3-none-any.whl (11 kB)
Installing collected packages: distlib, platformdirs, nodeenv, identify, filelock, cfgv, virtualenv, pre-commit
Successfully installed cfgv-3.4.0 distlib-0.3.7 filelock-3.13.1 identify-2.5.31 nodeenv-1.8.0 platformdirs-3.11.0 pre-commit-3.5.0 virtualenv-20.24.6
##[group]Run python -m pip freeze --local
[36;1mpython -m pip freeze --local[0m
shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
##[endgroup]
argcomplete==3.1.2
cfgv==3.4.0
distlib==0.3.7
filelock==3.13.1
identify==2.5.31
nodeenv==1.8.0
packaging==23.2
pipx==1.2.1
platformdirs==3.11.0
pre-commit==3.5.0
userpath==1.9.1
virtualenv==20.24.6
##[group]Run actions/cache@v3
with:
  path: ~/.cache/pre-commit
  key: pre-commit-3||05c1fbbb63b353467651b511e6ac241ffd2d8e71749cfabfe5ee6bc8366d2d02
  enableCrossOsArchive: false
  fail-on-cache-miss: false
  lookup-only: false
##[endgroup]
Cache Size: ~36 MB (37431926 B)
[command]/usr/bin/tar -xf /home/runner/work/_temp/197df8ad-dba5-43ca-8f9c-fc801b310ed7/cache.tzst -P -C /home/runner/work/qtile/qtile --use-compress-program unzstd
Received 37431926 of 37431926 (100.0%), 35.7 MBs/sec
Cache restored successfully
Cache restored from key: pre-commit-3||05c1fbbb63b353467651b511e6ac241ffd2d8e71749cfabfe5ee6bc8366d2d02
##[group]Run pre-commit run --show-diff-on-failure --color=always --all-files
[36;1mpre-commit run --show-diff-on-failure --color=always --all-files[0m
shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
##[endgroup]
black....................................................................[42mPassed[m
isort....................................................................[42mPassed[m
flake8...................................................................[42mPassed[m
mypy.....................................................................[41mFailed[m
[2m- hook id: mypy[m
[2m- exit code: 1[m

libqtile/hook.py:107: error: Need type annotation for "hooks" (hint: "hooks: Set[<type>] = ...")  [var-annotated]
libqtile/hook.py:136: error: Return type "None" of "_subscribe" incompatible with return type "Callable[..., Any]" in supertype "Subscribe"  [override]
libqtile/bar.py:351: error: "Subscribe" has no attribute "setgroup"  [attr-defined]
libqtile/bar.py:352: error: "Subscribe" has no attribute "startup_complete"  [attr-defined]
libqtile/scratchpad.py:403: error: "Subscribe" has no attribute "client_new"  [attr-defined]
libqtile/scratchpad.py:407: error: "Subscribe" has no attribute "client_killed"  [attr-defined]
libqtile/scratchpad.py:408: error: "Subscribe" has no attribute "float_change"  [attr-defined]
libqtile/layout/screensplit.py:177: error: "Subscribe" has no attribute "focus_change"  [attr-defined]
libqtile/layout/screensplit.py:180: error: "Unsubscribe" has no attribute "focus_change"  [attr-defined]
libqtile/core/state.py:91: error: "Subscribe" has no attribute "client_new"  [attr-defined]
libqtile/core/state.py:110: error: "Unsubscribe" has no attribute "client_new"  [attr-defined]
libqtile/core/manager.py:177: error: "Subscribe" has no attribute "setgroup"  [attr-defined]
libqtile/core/manager.py:180: error: "Subscribe" has no attribute "screen_change"  [attr-defined]
libqtile/widget/prompt.py:412: error: "Subscribe" has no attribute "client_focus"  [attr-defined]
libqtile/backend/wayland/core.py:200: error: "Subscribe" has no attribute "startup_complete"  [attr-defined]
Found 15 errors in 8 files (checked 178 source files)

vulture..................................................................[42mPassed[m
##[error]Process completed with exit code 1.


Changed files:
libqtile/hook.py

Fix diff:
diff --git a/libqtile/hook.py b/libqtile/hook.py
index f405a68d..da33d949 100644
--- a/libqtile/hook.py
+++ b/libqtile/hook.py
@@ -42,6 +42,8 @@ from libqtile.resources.sleep import inhibitor
 if TYPE_CHECKING:
     from typing import Callable
 
+    HookHandler = Callable[[Callable], Callable]
+
 subscriptions = {}  # type: dict
 
 
@@ -87,7 +89,7 @@ def _user_hook_func(self):
         def f(func):
             name = f"user_{hook_name}"
             if name not in self.hooks:
-                self.hooks.add(name)
+                self.hooks[name] = None
             return self._subscribe(name, func)
 
         return f
@@ -102,15 +104,28 @@ class Hook:
         self.func = func
 
 
-class Subscribe:
+class HookHandlerCollection:
     def __init__(self, registry_name: str, check_name=True):
-        self.hooks = set([])
+        self.hooks: dict[str, HookHandler] = {}
         if check_name and registry_name in subscriptions:
             raise NameError("A hook registry already exists with that name: {registry_name}")
         elif registry_name not in subscriptions:
             subscriptions[registry_name] = {}
         self.registry_name = registry_name
 
+    def __getattr__(self, name: str) -> HookHandler:
+        if name not in self.hooks:
+            raise AttributeError
+        return self.hooks[name]
+
+    def _register(self, hook: Hook) -> None:
+        def _hook_func(func):
+            return self._subscribe(hook.name, func)
+
+        self.hooks[hook.name] = _hook_func if hook.func is None else hook.func(self)
+
+
+class Subscribe(HookHandlerCollection):
     def _subscribe(self, event: str, func: Callable) -> Callable:
         registry = subscriptions.setdefault(self.registry_name, dict())
         lst = registry.setdefault(event, [])
@@ -118,16 +133,8 @@ class Subscribe:
             lst.append(func)
         return func
 
-    def _register(self, hook: Hook) -> None:
-        def _hook_func(func):
-            return self._subscribe(hook.name, func)
-
-        self.hooks.add(hook.name)
-        setattr(self, hook.name, _hook_func if hook.func is None else hook.func(self))
-        setattr(getattr(self, hook.name), "__doc__", hook.doc)
-
 
-class Unsubscribe(Subscribe):
+class Unsubscribe(HookHandlerCollection):
     """
     This class mirrors subscribe, except the _subscribe member has been
     overridden to remove calls from hooks.
@@ -153,7 +160,7 @@ class Registry:
             self.register_hook(hook)
 
     def register_hook(self, hook: Hook) -> None:
-        if hook.name in dir(self.subscribe):
+        if hook.name in self.subscribe.hooks:
             raise utils.QtileError(
                 f"Unable to register hook. A hook with that name already exists: {hook.name}"
             )