Compare commits
2 Commits
35414c3085
...
6d5b92ad69
Author | SHA1 | Date | |
---|---|---|---|
6d5b92ad69 | |||
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
|
||||
|
||||
|
@ -920,12 +920,12 @@ class Client:
|
||||
await view._start(self)
|
||||
components_payload = view.to_components_payload()
|
||||
elif components:
|
||||
from .models import ActionRow as ActionRowModel
|
||||
from .models import Component as ComponentModel
|
||||
|
||||
components_payload = [
|
||||
comp.to_dict()
|
||||
for comp in components
|
||||
if isinstance(comp, ActionRowModel)
|
||||
if isinstance(comp, ComponentModel)
|
||||
]
|
||||
|
||||
message_data = await self._http.send_message(
|
||||
|
@ -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
|
||||
|
||||
# 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.core import Command as PrefixCommand
|
||||
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
|
||||
|
||||
if not TYPE_CHECKING:
|
||||
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.
|
||||
@ -123,7 +128,7 @@ class CommandContext:
|
||||
|
||||
async def reply(
|
||||
self,
|
||||
content: str,
|
||||
content: Optional[str] = None,
|
||||
*,
|
||||
mention_author: Optional[bool] = None,
|
||||
**kwargs: Any,
|
||||
@ -235,6 +240,7 @@ class CommandHandler:
|
||||
return self.commands.get(name.lower())
|
||||
|
||||
def add_cog(self, cog_to_add: "Cog") -> None:
|
||||
from .cog import Cog
|
||||
if not isinstance(cog_to_add, Cog):
|
||||
raise TypeError("Argument must be a subclass of Cog.")
|
||||
|
||||
|
@ -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