Updates Python version requirements and refactors hybrid commands
Lowers minimum Python requirement from 3.11 to 3.10 to increase compatibility while updating CI to use Python 3.13 for testing. Extracts hybrid command functionality into a separate module to improve code organization and reduce complexity in the main commands module. Updates test timeouts and dependency versions to ensure reliable test execution and modern package compatibility.
This commit is contained in:
parent
35414c3085
commit
60a183742a
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
python-version: '3.13'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
@ -21,7 +21,7 @@ pip install disagreement
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
Requires Python 3.11 or newer.
|
||||
Requires Python 3.10 or newer.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
|
@ -44,3 +44,5 @@ __all__ = [
|
||||
"OptionMetadata",
|
||||
"AppCommandContext", # To be defined
|
||||
]
|
||||
|
||||
from .hybrid import *
|
||||
|
@ -1,58 +1,25 @@
|
||||
# disagreement/ext/app_commands/commands.py
|
||||
|
||||
import inspect
|
||||
from typing import Callable, Optional, List, Dict, Any, Union, TYPE_CHECKING
|
||||
from typing import Any, Callable, Dict, List, Optional, Union, TYPE_CHECKING
|
||||
|
||||
from disagreement.enums import (
|
||||
ApplicationCommandType,
|
||||
ApplicationCommandOptionType,
|
||||
IntegrationType,
|
||||
InteractionContextType,
|
||||
)
|
||||
from disagreement.interactions import ApplicationCommandOption, Snowflake
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from disagreement.ext.commands.core import (
|
||||
Command as PrefixCommand,
|
||||
) # Alias to avoid name clash
|
||||
from disagreement.interactions import ApplicationCommandOption, Snowflake
|
||||
from disagreement.enums import (
|
||||
ApplicationCommandType,
|
||||
IntegrationType,
|
||||
InteractionContextType,
|
||||
ApplicationCommandOptionType, # Added
|
||||
)
|
||||
from disagreement.ext.commands.cog import Cog # Corrected import path
|
||||
from disagreement.ext.commands.core import Command as PrefixCommand
|
||||
from disagreement.ext.commands.cog import Cog
|
||||
|
||||
# Placeholder for Cog if not using the existing one or if it needs adaptation
|
||||
if not TYPE_CHECKING:
|
||||
# This dynamic Cog = Any might not be ideal if Cog is used in runtime type checks.
|
||||
# However, for type hinting purposes when TYPE_CHECKING is false, it avoids import.
|
||||
# If Cog is needed at runtime by this module (it is, for AppCommand.cog type hint),
|
||||
# it should be imported directly.
|
||||
# For now, the TYPE_CHECKING block handles the proper import for static analysis.
|
||||
# Let's ensure Cog is available at runtime if AppCommand.cog is accessed.
|
||||
# A simple way is to import it outside TYPE_CHECKING too, if it doesn't cause circularity.
|
||||
# Given its usage, a forward reference string 'Cog' might be better in AppCommand.cog type hint.
|
||||
# Let's try importing it directly for runtime, assuming no circularity with this specific module.
|
||||
try:
|
||||
from disagreement.ext.commands.cog import Cog
|
||||
except ImportError:
|
||||
Cog = Any # Fallback if direct import fails (e.g. during partial builds/tests)
|
||||
# Import PrefixCommand at runtime for HybridCommand
|
||||
try:
|
||||
from disagreement.ext.commands.core import Command as PrefixCommand
|
||||
except Exception: # pragma: no cover - safeguard against unusual import issues
|
||||
PrefixCommand = Any # type: ignore
|
||||
# Import enums used at runtime
|
||||
try:
|
||||
from disagreement.enums import (
|
||||
ApplicationCommandType,
|
||||
IntegrationType,
|
||||
InteractionContextType,
|
||||
ApplicationCommandOptionType,
|
||||
)
|
||||
from disagreement.interactions import ApplicationCommandOption, Snowflake
|
||||
except Exception: # pragma: no cover
|
||||
ApplicationCommandType = ApplicationCommandOptionType = IntegrationType = (
|
||||
InteractionContextType
|
||||
) = Any # type: ignore
|
||||
ApplicationCommandOption = Snowflake = Any # type: ignore
|
||||
else: # When TYPE_CHECKING is true, Cog and PrefixCommand are already imported above.
|
||||
pass
|
||||
except ImportError:
|
||||
PrefixCommand = Any
|
||||
|
||||
|
||||
class AppCommand:
|
||||
@ -235,59 +202,6 @@ class MessageCommand(AppCommand):
|
||||
super().__init__(callback, type=ApplicationCommandType.MESSAGE, **kwargs)
|
||||
|
||||
|
||||
class HybridCommand(SlashCommand, PrefixCommand): # Inherit from both
|
||||
"""
|
||||
Represents a command that can be invoked as both a slash command
|
||||
and a traditional prefix-based command.
|
||||
"""
|
||||
|
||||
def __init__(self, callback: Callable[..., Any], **kwargs: Any):
|
||||
# Initialize SlashCommand part (which calls AppCommand.__init__)
|
||||
# We need to ensure 'type' is correctly passed for AppCommand
|
||||
# kwargs for SlashCommand: name, description, guild_ids, default_member_permissions, nsfw, parent, cog, etc.
|
||||
# kwargs for PrefixCommand: name, aliases, brief, description, cog
|
||||
|
||||
# Pop prefix-specific args before passing to SlashCommand constructor
|
||||
prefix_aliases = kwargs.pop("aliases", [])
|
||||
prefix_brief = kwargs.pop("brief", None)
|
||||
# Description is used by both, AppCommand's constructor will handle it.
|
||||
# Name is used by both. Cog is used by both.
|
||||
|
||||
# Call SlashCommand's __init__
|
||||
# This will set up name, description, callback, type=CHAT_INPUT, options, etc.
|
||||
super().__init__(callback, **kwargs) # This is SlashCommand.__init__
|
||||
|
||||
# Now, explicitly initialize the PrefixCommand parts that SlashCommand didn't cover
|
||||
# or that need specific values for the prefix version.
|
||||
# PrefixCommand.__init__(self, callback, name=self.name, aliases=prefix_aliases, brief=prefix_brief, description=self.description, cog=self.cog)
|
||||
# However, PrefixCommand.__init__ also sets self.params, which AppCommand already did.
|
||||
# We need to be careful not to re-initialize things unnecessarily or incorrectly.
|
||||
# Let's manually set the distinct attributes for the PrefixCommand aspect.
|
||||
|
||||
# Attributes from PrefixCommand:
|
||||
# self.callback is already set by AppCommand
|
||||
# self.name is already set by AppCommand
|
||||
self.aliases: List[str] = (
|
||||
prefix_aliases # This was specific to HybridCommand before, now aligns with PrefixCommand
|
||||
)
|
||||
self.brief: Optional[str] = prefix_brief
|
||||
# self.description is already set by AppCommand (SlashCommand ensures it exists)
|
||||
# self.cog is already set by AppCommand
|
||||
# self.params is already set by AppCommand
|
||||
|
||||
# Ensure the MRO is handled correctly. Python's MRO (C3 linearization)
|
||||
# should call SlashCommand's __init__ then AppCommand's __init__.
|
||||
# PrefixCommand.__init__ won't be called automatically unless we explicitly call it.
|
||||
# By setting attributes directly, we avoid potential issues with multiple __init__ calls
|
||||
# if their logic overlaps too much (e.g., both trying to set self.params).
|
||||
|
||||
# We might need to override invoke if the context or argument passing differs significantly
|
||||
# between app command invocation and prefix command invocation.
|
||||
# For now, SlashCommand.invoke and PrefixCommand.invoke are separate.
|
||||
# The correct one will be called depending on how the command is dispatched.
|
||||
# The AppCommandHandler will use AppCommand.invoke (via SlashCommand).
|
||||
# The prefix CommandHandler will use PrefixCommand.invoke.
|
||||
# This seems acceptable.
|
||||
|
||||
|
||||
class AppCommandGroup:
|
||||
|
@ -26,8 +26,8 @@ from .commands import (
|
||||
MessageCommand,
|
||||
AppCommand,
|
||||
AppCommandGroup,
|
||||
HybridCommand,
|
||||
)
|
||||
from .hybrid import HybridCommand
|
||||
from disagreement.interactions import (
|
||||
ApplicationCommandOption,
|
||||
ApplicationCommandOptionChoice,
|
||||
|
61
disagreement/ext/app_commands/hybrid.py
Normal file
61
disagreement/ext/app_commands/hybrid.py
Normal file
@ -0,0 +1,61 @@
|
||||
# disagreement/ext/app_commands/hybrid.py
|
||||
|
||||
from typing import Any, Callable, List, Optional
|
||||
|
||||
from .commands import SlashCommand
|
||||
from disagreement.ext.commands.core import PrefixCommand
|
||||
|
||||
|
||||
class HybridCommand(SlashCommand, PrefixCommand): # Inherit from both
|
||||
"""
|
||||
Represents a command that can be invoked as both a slash command
|
||||
and a traditional prefix-based command.
|
||||
"""
|
||||
|
||||
def __init__(self, callback: Callable[..., Any], **kwargs: Any):
|
||||
# Initialize SlashCommand part (which calls AppCommand.__init__)
|
||||
# We need to ensure 'type' is correctly passed for AppCommand
|
||||
# kwargs for SlashCommand: name, description, guild_ids, default_member_permissions, nsfw, parent, cog, etc.
|
||||
# kwargs for PrefixCommand: name, aliases, brief, description, cog
|
||||
|
||||
# Pop prefix-specific args before passing to SlashCommand constructor
|
||||
prefix_aliases = kwargs.pop("aliases", [])
|
||||
prefix_brief = kwargs.pop("brief", None)
|
||||
# Description is used by both, AppCommand's constructor will handle it.
|
||||
# Name is used by both. Cog is used by both.
|
||||
|
||||
# Call SlashCommand's __init__
|
||||
# This will set up name, description, callback, type=CHAT_INPUT, options, etc.
|
||||
super().__init__(callback, **kwargs) # This is SlashCommand.__init__
|
||||
|
||||
# Now, explicitly initialize the PrefixCommand parts that SlashCommand didn't cover
|
||||
# or that need specific values for the prefix version.
|
||||
# PrefixCommand.__init__(self, callback, name=self.name, aliases=prefix_aliases, brief=prefix_brief, description=self.description, cog=self.cog)
|
||||
# However, PrefixCommand.__init__ also sets self.params, which AppCommand already did.
|
||||
# We need to be careful not to re-initialize things unnecessarily or incorrectly.
|
||||
# Let's manually set the distinct attributes for the PrefixCommand aspect.
|
||||
|
||||
# Attributes from PrefixCommand:
|
||||
# self.callback is already set by AppCommand
|
||||
# self.name is already set by AppCommand
|
||||
self.aliases: List[str] = (
|
||||
prefix_aliases # This was specific to HybridCommand before, now aligns with PrefixCommand
|
||||
)
|
||||
self.brief: Optional[str] = prefix_brief
|
||||
# self.description is already set by AppCommand (SlashCommand ensures it exists)
|
||||
# self.cog is already set by AppCommand
|
||||
# self.params is already set by AppCommand
|
||||
|
||||
# Ensure the MRO is handled correctly. Python's MRO (C3 linearization)
|
||||
# should call SlashCommand's __init__ then AppCommand's __init__.
|
||||
# PrefixCommand.__init__ won't be called automatically unless we explicitly call it.
|
||||
# By setting attributes directly, we avoid potential issues with multiple __init__ calls
|
||||
# if their logic overlaps too much (e.g., both trying to set self.params).
|
||||
|
||||
# We might need to override invoke if the context or argument passing differs significantly
|
||||
# between app command invocation and prefix command invocation.
|
||||
# For now, SlashCommand.invoke and PrefixCommand.invoke are separate.
|
||||
# The correct one will be called depending on how the command is dispatched.
|
||||
# The AppCommandHandler will use AppCommand.invoke (via SlashCommand).
|
||||
# The prefix CommandHandler will use PrefixCommand.invoke.
|
||||
# This seems acceptable.
|
@ -1,5 +1,7 @@
|
||||
# disagreement/ext/commands/core.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import inspect
|
||||
from typing import (
|
||||
@ -27,10 +29,10 @@ from .errors import (
|
||||
CommandInvokeError,
|
||||
)
|
||||
from .converters import run_converters, DEFAULT_CONVERTERS, Converter
|
||||
from .cog import Cog
|
||||
from disagreement.typing import Typing
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .cog import Cog
|
||||
from disagreement.client import Client
|
||||
from disagreement.models import Message, User
|
||||
|
||||
@ -86,6 +88,9 @@ class Command:
|
||||
await self.callback(ctx, *args, **kwargs)
|
||||
|
||||
|
||||
PrefixCommand = Command # Alias for clarity in hybrid commands
|
||||
|
||||
|
||||
class CommandContext:
|
||||
"""
|
||||
Represents the context in which a command is being invoked.
|
||||
|
@ -3,7 +3,7 @@ name = "disagreement"
|
||||
version = "0.1.0rc1"
|
||||
description = "A Python library for the Discord API."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
requires-python = ">=3.10"
|
||||
license = {text = "BSD 3-Clause"}
|
||||
authors = [
|
||||
{name = "Slipstream", email = "me@slipstreamm.dev"}
|
||||
@ -15,6 +15,7 @@ classifiers = [
|
||||
"License :: OSI Approved :: BSD License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
@ -31,10 +32,10 @@ dependencies = [
|
||||
test = [
|
||||
"pytest>=8.0.0",
|
||||
"pytest-asyncio>=1.0.0",
|
||||
"hypothesis>=6.89.0",
|
||||
"hypothesis>=6.132.0",
|
||||
]
|
||||
dev = [
|
||||
"dotenv>=0.0.5",
|
||||
"python-dotenv>=1.0.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
@ -33,7 +33,7 @@ async def test_check_decorator_blocks(message):
|
||||
async def test_cooldown_per_user(message):
|
||||
uses = []
|
||||
|
||||
@cooldown(1, 0.05)
|
||||
@cooldown(1, 0.1)
|
||||
async def cb(ctx):
|
||||
uses.append(1)
|
||||
|
||||
@ -51,7 +51,7 @@ async def test_cooldown_per_user(message):
|
||||
with pytest.raises(CommandOnCooldown):
|
||||
await cmd.invoke(ctx)
|
||||
|
||||
await asyncio.sleep(0.05)
|
||||
await asyncio.sleep(0.1)
|
||||
await cmd.invoke(ctx)
|
||||
assert len(uses) == 2
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user