prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  All hooks in config will be subscribed and fired twice.
  ### The issue:
  
  ~~This is probably due to configuration testing syntax step.~~
  
  ```python
  'startup_complete': [<function xstartup_complete at 0x7f2005fc49d0>,
                        <function xstartup_complete at 0x7f2005fc5510>]}
  ```
  
  Code to reproduce is simple:
  
  in config:
  
  ```python
  @hook.subscribe.startup_complete
  def xstartup_complete():
      ...
  
  logger.warn(pprint.pformat(hook.subscriptions))
  ```
  
  All hooks are actually being fired twice, not only startup but all for each event.
  
  ### Required:
  
  - [X] I have searched past issues to see if this bug has already been reported.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `libqtile/confreader.py`
  Content:
  ```
  1 # Copyright (c) 2008, Aldo Cortesi <aldo@corte.si>
  2 # Copyright (c) 2011, Andrew Grigorev <andrew@ei-grad.ru>
  3 #
  4 # All rights reserved.
  5 #
  6 # Permission is hereby granted, free of charge, to any person obtaining a copy
  7 # of this software and associated documentation files (the "Software"), to deal
  8 # in the Software without restriction, including without limitation the rights
  9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10 # copies of the Software, and to permit persons to whom the Software is
  11 # furnished to do so, subject to the following conditions:
  12 #
  13 # The above copyright notice and this permission notice shall be included in
  14 # all copies or substantial portions of the Software.
  15 #
  16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22 # SOFTWARE.
  23 
  24 from __future__ import annotations
  25 
  26 import importlib
  27 import sys
  28 from pathlib import Path
  29 from typing import TYPE_CHECKING
  30 
  31 from libqtile.backend.x11 import core
  32 
  33 if TYPE_CHECKING:
  34     from typing import Any
  35 
  36     from typing_extensions import Literal
  37 
  38     from libqtile.config import Group, Key, Mouse, Rule, Screen
  39     from libqtile.layout.base import Layout
  40 
  41 
  42 class ConfigError(Exception):
  43     pass
  44 
  45 
  46 config_pyi_header = """
  47 from typing import Any
  48 from typing_extensions import Literal
  49 from libqtile.config import Group, Key, Mouse, Rule, Screen
  50 from libqtile.layout.base import Layout
  51 
  52 """
  53 
  54 
  55 class Config:
  56     # All configuration options
  57     keys: list[Key]
  58     mouse: list[Mouse]
  59     groups: list[Group]
  60     dgroups_key_binder: Any
  61     dgroups_app_rules: list[Rule]
  62     follow_mouse_focus: bool
  63     focus_on_window_activation: Literal["focus", "smart", "urgent", "never"]
  64     cursor_warp: bool
  65     layouts: list[Layout]
  66     floating_layout: Layout
  67     screens: list[Screen]
  68     auto_fullscreen: bool
  69     widget_defaults: dict[str, Any]
  70     extension_defaults: dict[str, Any]
  71     bring_front_click: bool | Literal["floating_only"]
  72     reconfigure_screens: bool
  73     wmname: str
  74     auto_minimize: bool
  75     # Really we'd want to check this Any is libqtile.backend.wayland.ImportConfig, but
  76     # doing so forces the import, creating a hard dependency for wlroots.
  77     wl_input_rules: dict[str, Any] | None
  78 
  79     def __init__(self, file_path=None, **settings):
  80         """Create a Config() object from settings
  81 
  82         Only attributes found in Config.__annotations__ will be added to object.
  83         config attribute precedence is 1.) **settings 2.) self 3.) default_config
  84         """
  85         self.file_path = file_path
  86         self.update(**settings)
  87 
  88     def update(self, *, fake_screens=None, **settings):
  89         from libqtile.resources import default_config
  90 
  91         if fake_screens:
  92             self.fake_screens = fake_screens
  93 
  94         default = vars(default_config)
  95         for key in self.__annotations__.keys():
  96             try:
  97                 value = settings[key]
  98             except KeyError:
  99                 value = getattr(self, key, default[key])
  100             setattr(self, key, value)
  101 
  102     def _reload_config_submodules(self, path: Path) -> None:
  103         """Reloads python files from same folder as config file."""
  104         folder = path.parent
  105         for module in sys.modules.copy().values():
  106 
  107             # Skip built-ins and anything with no filepath.
  108             if hasattr(module, "__file__") and module.__file__ is not None:
  109                 subpath = Path(module.__file__)
  110 
  111                 # Check if the module is in the config folder or subfolder
  112                 # if so, reload it
  113                 if folder in subpath.parents:
  114                     importlib.reload(module)
  115 
  116     def load(self):
  117         if not self.file_path:
  118             return
  119 
  120         path = Path(self.file_path)
  121         name = path.stem
  122         sys.path.insert(0, path.parent.as_posix())
  123 
  124         if name in sys.modules:
  125             self._reload_config_submodules(path)
  126             config = importlib.reload(sys.modules[name])
  127         else:
  128             config = importlib.import_module(name)
  129 
  130         self.update(**vars(config))
  131 
  132     def validate(self) -> None:
  133         """
  134         Validate the configuration against the core.
  135         """
  136         valid_keys = core.get_keys()
  137         valid_mods = core.get_modifiers()
  138         # we explicitly do not want to set self.keys and self.mouse above,
  139         # because they are dynamically resolved from the default_config. so we
  140         # need to ignore the errors here about missing attributes.
  141         for k in self.keys:
  142             if k.key.lower() not in valid_keys:
  143                 raise ConfigError("No such key: %s" % k.key)
  144             for m in k.modifiers:
  145                 if m.lower() not in valid_mods:
  146                     raise ConfigError("No such modifier: %s" % m)
  147         for ms in self.mouse:
  148             for m in ms.modifiers:
  149                 if m.lower() not in valid_mods:
  150                     raise ConfigError("No such modifier: %s" % m)
  151
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/libqtile/confreader.py b/libqtile/confreader.py
  --- a/libqtile/confreader.py
  +++ b/libqtile/confreader.py
  @@ -108,6 +108,14 @@
               if hasattr(module, "__file__") and module.__file__ is not None:
                   subpath = Path(module.__file__)
   
  +                if subpath == path:
  +                    # do not reevaluate config itself here, we want only
  +                    # reload all submodules. Also we cant reevaluate config
  +                    # here, because it will cache all current modules before they
  +                    # are reloaded. Thus, config file should be reloaded after
  +                    # this routine.
  +                    continue
  +
                   # Check if the module is in the config folder or subfolder
                   # if so, reload it
                   if folder in subpath.parents:
  
verification_info: {"golden_diff": "diff --git a/libqtile/confreader.py b/libqtile/confreader.py\n--- a/libqtile/confreader.py\n+++ b/libqtile/confreader.py\n@@ -108,6 +108,14 @@\n             if hasattr(module, \"__file__\") and module.__file__ is not None:\n                 subpath = Path(module.__file__)\n \n+                if subpath == path:\n+                    # do not reevaluate config itself here, we want only\n+                    # reload all submodules. Also we cant reevaluate config\n+                    # here, because it will cache all current modules before they\n+                    # are reloaded. Thus, config file should be reloaded after\n+                    # this routine.\n+                    continue\n+\n                 # Check if the module is in the config folder or subfolder\n                 # if so, reload it\n                 if folder in subpath.parents:\n", "issue": "All hooks in config will be subscribed and fired twice.\n### The issue:\r\n\r\n~~This is probably due to configuration testing syntax step.~~\r\n\r\n```python\r\n'startup_complete': [<function xstartup_complete at 0x7f2005fc49d0>,\r\n                      <function xstartup_complete at 0x7f2005fc5510>]}\r\n```\r\n\r\nCode to reproduce is simple:\r\n\r\nin config:\r\n\r\n```python\r\n@hook.subscribe.startup_complete\r\ndef xstartup_complete():\r\n    ...\r\n\r\nlogger.warn(pprint.pformat(hook.subscriptions))\r\n```\r\n\r\nAll hooks are actually being fired twice, not only startup but all for each event.\r\n\r\n### Required:\r\n\r\n- [X] I have searched past issues to see if this bug has already been reported.\n", "before_files": [{"content": "# Copyright (c) 2008, Aldo Cortesi <aldo@corte.si>\n# Copyright (c) 2011, Andrew Grigorev <andrew@ei-grad.ru>\n#\n# All rights reserved.\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom __future__ import annotations\n\nimport importlib\nimport sys\nfrom pathlib import Path\nfrom typing import TYPE_CHECKING\n\nfrom libqtile.backend.x11 import core\n\nif TYPE_CHECKING:\n    from typing import Any\n\n    from typing_extensions import Literal\n\n    from libqtile.config import Group, Key, Mouse, Rule, Screen\n    from libqtile.layout.base import Layout\n\n\nclass ConfigError(Exception):\n    pass\n\n\nconfig_pyi_header = \"\"\"\nfrom typing import Any\nfrom typing_extensions import Literal\nfrom libqtile.config import Group, Key, Mouse, Rule, Screen\nfrom libqtile.layout.base import Layout\n\n\"\"\"\n\n\nclass Config:\n    # All configuration options\n    keys: list[Key]\n    mouse: list[Mouse]\n    groups: list[Group]\n    dgroups_key_binder: Any\n    dgroups_app_rules: list[Rule]\n    follow_mouse_focus: bool\n    focus_on_window_activation: Literal[\"focus\", \"smart\", \"urgent\", \"never\"]\n    cursor_warp: bool\n    layouts: list[Layout]\n    floating_layout: Layout\n    screens: list[Screen]\n    auto_fullscreen: bool\n    widget_defaults: dict[str, Any]\n    extension_defaults: dict[str, Any]\n    bring_front_click: bool | Literal[\"floating_only\"]\n    reconfigure_screens: bool\n    wmname: str\n    auto_minimize: bool\n    # Really we'd want to check this Any is libqtile.backend.wayland.ImportConfig, but\n    # doing so forces the import, creating a hard dependency for wlroots.\n    wl_input_rules: dict[str, Any] | None\n\n    def __init__(self, file_path=None, **settings):\n        \"\"\"Create a Config() object from settings\n\n        Only attributes found in Config.__annotations__ will be added to object.\n        config attribute precedence is 1.) **settings 2.) self 3.) default_config\n        \"\"\"\n        self.file_path = file_path\n        self.update(**settings)\n\n    def update(self, *, fake_screens=None, **settings):\n        from libqtile.resources import default_config\n\n        if fake_screens:\n            self.fake_screens = fake_screens\n\n        default = vars(default_config)\n        for key in self.__annotations__.keys():\n            try:\n                value = settings[key]\n            except KeyError:\n                value = getattr(self, key, default[key])\n            setattr(self, key, value)\n\n    def _reload_config_submodules(self, path: Path) -> None:\n        \"\"\"Reloads python files from same folder as config file.\"\"\"\n        folder = path.parent\n        for module in sys.modules.copy().values():\n\n            # Skip built-ins and anything with no filepath.\n            if hasattr(module, \"__file__\") and module.__file__ is not None:\n                subpath = Path(module.__file__)\n\n                # Check if the module is in the config folder or subfolder\n                # if so, reload it\n                if folder in subpath.parents:\n                    importlib.reload(module)\n\n    def load(self):\n        if not self.file_path:\n            return\n\n        path = Path(self.file_path)\n        name = path.stem\n        sys.path.insert(0, path.parent.as_posix())\n\n        if name in sys.modules:\n            self._reload_config_submodules(path)\n            config = importlib.reload(sys.modules[name])\n        else:\n            config = importlib.import_module(name)\n\n        self.update(**vars(config))\n\n    def validate(self) -> None:\n        \"\"\"\n        Validate the configuration against the core.\n        \"\"\"\n        valid_keys = core.get_keys()\n        valid_mods = core.get_modifiers()\n        # we explicitly do not want to set self.keys and self.mouse above,\n        # because they are dynamically resolved from the default_config. so we\n        # need to ignore the errors here about missing attributes.\n        for k in self.keys:\n            if k.key.lower() not in valid_keys:\n                raise ConfigError(\"No such key: %s\" % k.key)\n            for m in k.modifiers:\n                if m.lower() not in valid_mods:\n                    raise ConfigError(\"No such modifier: %s\" % m)\n        for ms in self.mouse:\n            for m in ms.modifiers:\n                if m.lower() not in valid_mods:\n                    raise ConfigError(\"No such modifier: %s\" % m)\n", "path": "libqtile/confreader.py"}], "after_files": [{"content": "# Copyright (c) 2008, Aldo Cortesi <aldo@corte.si>\n# Copyright (c) 2011, Andrew Grigorev <andrew@ei-grad.ru>\n#\n# All rights reserved.\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom __future__ import annotations\n\nimport importlib\nimport sys\nfrom pathlib import Path\nfrom typing import TYPE_CHECKING\n\nfrom libqtile.backend.x11 import core\n\nif TYPE_CHECKING:\n    from typing import Any\n\n    from typing_extensions import Literal\n\n    from libqtile.config import Group, Key, Mouse, Rule, Screen\n    from libqtile.layout.base import Layout\n\n\nclass ConfigError(Exception):\n    pass\n\n\nconfig_pyi_header = \"\"\"\nfrom typing import Any\nfrom typing_extensions import Literal\nfrom libqtile.config import Group, Key, Mouse, Rule, Screen\nfrom libqtile.layout.base import Layout\n\n\"\"\"\n\n\nclass Config:\n    # All configuration options\n    keys: list[Key]\n    mouse: list[Mouse]\n    groups: list[Group]\n    dgroups_key_binder: Any\n    dgroups_app_rules: list[Rule]\n    follow_mouse_focus: bool\n    focus_on_window_activation: Literal[\"focus\", \"smart\", \"urgent\", \"never\"]\n    cursor_warp: bool\n    layouts: list[Layout]\n    floating_layout: Layout\n    screens: list[Screen]\n    auto_fullscreen: bool\n    widget_defaults: dict[str, Any]\n    extension_defaults: dict[str, Any]\n    bring_front_click: bool | Literal[\"floating_only\"]\n    reconfigure_screens: bool\n    wmname: str\n    auto_minimize: bool\n    # Really we'd want to check this Any is libqtile.backend.wayland.ImportConfig, but\n    # doing so forces the import, creating a hard dependency for wlroots.\n    wl_input_rules: dict[str, Any] | None\n\n    def __init__(self, file_path=None, **settings):\n        \"\"\"Create a Config() object from settings\n\n        Only attributes found in Config.__annotations__ will be added to object.\n        config attribute precedence is 1.) **settings 2.) self 3.) default_config\n        \"\"\"\n        self.file_path = file_path\n        self.update(**settings)\n\n    def update(self, *, fake_screens=None, **settings):\n        from libqtile.resources import default_config\n\n        if fake_screens:\n            self.fake_screens = fake_screens\n\n        default = vars(default_config)\n        for key in self.__annotations__.keys():\n            try:\n                value = settings[key]\n            except KeyError:\n                value = getattr(self, key, default[key])\n            setattr(self, key, value)\n\n    def _reload_config_submodules(self, path: Path) -> None:\n        \"\"\"Reloads python files from same folder as config file.\"\"\"\n        folder = path.parent\n        for module in sys.modules.copy().values():\n\n            # Skip built-ins and anything with no filepath.\n            if hasattr(module, \"__file__\") and module.__file__ is not None:\n                subpath = Path(module.__file__)\n\n                if subpath == path:\n                    # do not reevaluate config itself here, we want only\n                    # reload all submodules. Also we cant reevaluate config\n                    # here, because it will cache all current modules before they\n                    # are reloaded. Thus, config file should be reloaded after\n                    # this routine.\n                    continue\n\n                # Check if the module is in the config folder or subfolder\n                # if so, reload it\n                if folder in subpath.parents:\n                    importlib.reload(module)\n\n    def load(self):\n        if not self.file_path:\n            return\n\n        path = Path(self.file_path)\n        name = path.stem\n        sys.path.insert(0, path.parent.as_posix())\n\n        if name in sys.modules:\n            self._reload_config_submodules(path)\n            config = importlib.reload(sys.modules[name])\n        else:\n            config = importlib.import_module(name)\n\n        self.update(**vars(config))\n\n    def validate(self) -> None:\n        \"\"\"\n        Validate the configuration against the core.\n        \"\"\"\n        valid_keys = core.get_keys()\n        valid_mods = core.get_modifiers()\n        # we explicitly do not want to set self.keys and self.mouse above,\n        # because they are dynamically resolved from the default_config. so we\n        # need to ignore the errors here about missing attributes.\n        for k in self.keys:\n            if k.key.lower() not in valid_keys:\n                raise ConfigError(\"No such key: %s\" % k.key)\n            for m in k.modifiers:\n                if m.lower() not in valid_mods:\n                    raise ConfigError(\"No such modifier: %s\" % m)\n        for ms in self.mouse:\n            for m in ms.modifiers:\n                if m.lower() not in valid_mods:\n                    raise ConfigError(\"No such modifier: %s\" % m)\n", "path": "libqtile/confreader.py"}]}