mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-01 07:01:41 +00:00
fix(discord): define view classes after lazy discord.py install
When discord.py is not installed at import time, DISCORD_AVAILABLE=False and the view class definitions at module bottom are skipped. check_discord_requirements() performs a lazy install and sets DISCORD_AVAILABLE=True but never re-ran the class definitions, causing NameError on the first button interaction (exec approval, slash confirm, etc.). Extract the five ui.View subclasses into _define_discord_view_classes() and call it both at module load (when discord.py is pre-installed) and inside check_discord_requirements() after a successful lazy install.
This commit is contained in:
parent
7552e0f3c0
commit
5a3317693c
2 changed files with 97 additions and 1 deletions
|
|
@ -111,6 +111,7 @@ def check_discord_requirements() -> bool:
|
|||
Intents = _Intents
|
||||
commands = _commands
|
||||
DISCORD_AVAILABLE = True
|
||||
_define_discord_view_classes()
|
||||
return True
|
||||
|
||||
|
||||
|
|
@ -4949,7 +4950,17 @@ def _component_check_auth(
|
|||
return False
|
||||
|
||||
|
||||
if DISCORD_AVAILABLE:
|
||||
def _define_discord_view_classes() -> None:
|
||||
"""Register Discord UI view classes as module globals.
|
||||
|
||||
Called at module load (when discord.py is pre-installed) and also from
|
||||
check_discord_requirements() after a lazy install, so view classes are
|
||||
always defined whenever DISCORD_AVAILABLE is True. Without this,
|
||||
ExecApprovalView and siblings are only defined at import time; a later
|
||||
lazy install sets DISCORD_AVAILABLE=True but leaves the classes
|
||||
undefined, causing NameError on the first button interaction.
|
||||
"""
|
||||
global ExecApprovalView, SlashConfirmView, UpdatePromptView, ModelPickerView, ClarifyChoiceView
|
||||
|
||||
class ExecApprovalView(discord.ui.View):
|
||||
"""
|
||||
|
|
@ -5649,3 +5660,7 @@ if DISCORD_AVAILABLE:
|
|||
self.resolved = True
|
||||
for child in self.children:
|
||||
child.disabled = True
|
||||
|
||||
|
||||
if DISCORD_AVAILABLE:
|
||||
_define_discord_view_classes()
|
||||
|
|
|
|||
81
tests/gateway/test_discord_lazy_install_views.py
Normal file
81
tests/gateway/test_discord_lazy_install_views.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
"""Regression: Discord UI view classes must be defined after lazy-install.
|
||||
|
||||
When discord.py is NOT installed at module load time, the
|
||||
``if DISCORD_AVAILABLE:`` guard at the bottom of gateway/platforms/discord.py
|
||||
evaluates to False and is skipped — leaving ExecApprovalView and its four
|
||||
siblings undefined in the module globals.
|
||||
|
||||
check_discord_requirements() must call _define_discord_view_classes() after
|
||||
a successful lazy install so that all view classes are available the moment
|
||||
DISCORD_AVAILABLE flips to True. Without this, the first button interaction
|
||||
(exec approval, slash confirm, etc.) raises NameError even though
|
||||
DISCORD_AVAILABLE=True.
|
||||
|
||||
Fixes: lazy-install path NameError for ExecApprovalView, SlashConfirmView,
|
||||
UpdatePromptView, ModelPickerView, ClarifyChoiceView.
|
||||
"""
|
||||
import importlib
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
_VIEW_NAMES = [
|
||||
"ExecApprovalView",
|
||||
"SlashConfirmView",
|
||||
"UpdatePromptView",
|
||||
"ModelPickerView",
|
||||
"ClarifyChoiceView",
|
||||
]
|
||||
|
||||
|
||||
class TestDefineDiscordViewClasses:
|
||||
"""_define_discord_view_classes() registers all UI view classes in module globals."""
|
||||
|
||||
def test_registers_all_five_view_classes(self, monkeypatch):
|
||||
"""Calling _define_discord_view_classes() must (re)define all 5 view classes."""
|
||||
dp = importlib.import_module("gateway.platforms.discord")
|
||||
|
||||
# Remove the classes to simulate the state where the module was loaded
|
||||
# with DISCORD_AVAILABLE=False (the lazy-install scenario).
|
||||
for name in _VIEW_NAMES:
|
||||
monkeypatch.delattr(dp, name)
|
||||
|
||||
# Pre-condition: classes are gone
|
||||
for name in _VIEW_NAMES:
|
||||
assert not hasattr(dp, name), f"{name} should be absent before the call"
|
||||
|
||||
dp._define_discord_view_classes()
|
||||
|
||||
for name in _VIEW_NAMES:
|
||||
assert hasattr(dp, name), f"{name} must be defined after _define_discord_view_classes()"
|
||||
assert isinstance(getattr(dp, name), type), f"{name} must be a class"
|
||||
|
||||
def test_check_discord_requirements_calls_define_on_lazy_install(self, monkeypatch):
|
||||
"""check_discord_requirements() must call _define_discord_view_classes() on
|
||||
a successful lazy install so view classes exist when DISCORD_AVAILABLE=True."""
|
||||
dp = importlib.import_module("gateway.platforms.discord")
|
||||
|
||||
# Simulate discord not yet available at module load.
|
||||
monkeypatch.setattr(dp, "DISCORD_AVAILABLE", False)
|
||||
|
||||
define_called = [False]
|
||||
orig_define = dp._define_discord_view_classes
|
||||
|
||||
def _spy_define():
|
||||
define_called[0] = True
|
||||
orig_define()
|
||||
|
||||
monkeypatch.setattr(dp, "_define_discord_view_classes", _spy_define)
|
||||
|
||||
# Patch lazy_deps.ensure to be a no-op (pretend install succeeds).
|
||||
# The discord imports inside check_discord_requirements() succeed because
|
||||
# _ensure_discord_mock() in conftest.py already registered the mock.
|
||||
with patch("tools.lazy_deps.ensure"):
|
||||
result = dp.check_discord_requirements()
|
||||
|
||||
assert result is True, "check_discord_requirements() should return True after lazy install"
|
||||
assert define_called[0], (
|
||||
"check_discord_requirements() must call _define_discord_view_classes() "
|
||||
"after a successful lazy install so view classes are not undefined"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue