refactor(api): Re-export common symbols from top-level package
Some checks failed
Deploy MkDocs / deploy (push) Has been cancelled

Makes commonly used classes, functions, and decorators from `disagreement.ext` and `disagreement.ui` submodules directly accessible under the `disagreement` namespace.

This change simplifies import statements for users, leading to cleaner and more concise code. Documentation and examples have been updated to reflect these new, simplified import paths.
This commit is contained in:
Slipstreamm 2025-06-14 18:57:12 -06:00
parent 9237d12a24
commit e965a675c1
13 changed files with 162 additions and 66 deletions

View File

@ -67,6 +67,47 @@ from .hybrid_context import HybridContext
from .interactions import Interaction
from .logging_config import setup_logging
from . import ui, ext
from .ext.app_commands import (
AppCommand,
AppCommandContext,
AppCommandGroup,
MessageCommand,
OptionMetadata,
SlashCommand,
UserCommand,
group,
hybrid_command,
message_command,
slash_command,
subcommand,
subcommand_group,
)
from .ext.commands import (
BadArgument,
CheckAnyFailure,
CheckFailure,
Cog,
Command,
CommandContext,
CommandError,
CommandInvokeError,
CommandNotFound,
CommandOnCooldown,
MaxConcurrencyReached,
MissingRequiredArgument,
ArgumentParsingError,
check,
check_any,
command,
cooldown,
has_any_role,
has_role,
listener,
max_concurrency,
requires_permissions,
)
from .ext.tasks import Task, loop
from .ui import Item, Modal, Select, TextInput, View, button, select, text_input
import logging
@ -123,6 +164,51 @@ __all__ = [
"setup_logging",
"ui",
"ext",
"AppCommand",
"AppCommandContext",
"AppCommandGroup",
"MessageCommand",
"OptionMetadata",
"SlashCommand",
"UserCommand",
"group",
"hybrid_command",
"message_command",
"slash_command",
"subcommand",
"subcommand_group",
"BadArgument",
"CheckAnyFailure",
"CheckFailure",
"Cog",
"Command",
"CommandContext",
"CommandError",
"CommandInvokeError",
"CommandNotFound",
"CommandOnCooldown",
"MaxConcurrencyReached",
"MissingRequiredArgument",
"ArgumentParsingError",
"check",
"check_any",
"command",
"cooldown",
"has_any_role",
"has_role",
"listener",
"max_concurrency",
"requires_permissions",
"Task",
"loop",
"Item",
"Modal",
"Select",
"TextInput",
"View",
"button",
"select",
"text_input",
]

View File

@ -20,7 +20,7 @@ returns ``True``. Checks may be regular or async callables that accept a
`CommandContext`.
```python
from disagreement.ext.commands import command, check, CheckFailure
from disagreement import command, check, CheckFailure
def is_owner(ctx):
return ctx.author.id == "1"
@ -40,7 +40,7 @@ Commands can be rate limited using the ``cooldown`` decorator. The example
below restricts usage to once every three seconds per user:
```python
from disagreement.ext.commands import command, cooldown
from disagreement import command, cooldown
@command()
@cooldown(1, 3.0)
@ -56,7 +56,7 @@ Use `commands.requires_permissions` to ensure the invoking member has the
required permissions in the channel.
```python
from disagreement.ext.commands import command, requires_permissions
from disagreement import command, requires_permissions
from disagreement import Permissions
@command()

View File

@ -1,12 +1,11 @@
# Context Menu Commands
`disagreement` supports Discord's user and message context menu commands. Use the
`user_command` and `message_command` decorators from `ext.app_commands` to
define them.
`user_command` and `message_command` decorators to define them.
```python
from disagreement import User, Message
from disagreement.ext.app_commands import user_command, message_command, AppCommandContext
from disagreement import User, Message, user_command, message_command, AppCommandContext
@user_command(name="User Info")
async def user_info(ctx: AppCommandContext, user: User) -> None:

View File

@ -1,9 +1,9 @@
# Using Slash Commands
The library provides a slash command framework via the `ext.app_commands` package. Define commands with decorators and register them with Discord.
The library provides a slash command framework to define commands with decorators and register them with Discord.
```python
from disagreement.ext.app_commands import AppCommandGroup
from disagreement import AppCommandGroup
bot_commands = AppCommandGroup("bot", "Bot commands")

View File

@ -1,11 +1,11 @@
# Task Loops
The tasks extension allows you to run functions periodically. Decorate an async function with `@tasks.loop` and start it using `.start()`.
The tasks extension allows you to run functions periodically. Decorate an async function with `@loop` and start it using `.start()`.
```python
from disagreement.ext import tasks
from disagreement import loop
@tasks.loop(minutes=1.0)
@loop(minutes=1.0)
async def announce():
print("Hello from a loop")
@ -19,7 +19,7 @@ You can provide the interval in seconds, minutes, hours or as a `datetime.timede
```python
import datetime
@tasks.loop(delta=datetime.timedelta(seconds=30))
@loop(delta=datetime.timedelta(seconds=30))
async def ping():
...
```
@ -30,7 +30,7 @@ Handle exceptions raised by the looped coroutine using `on_error`:
async def log_error(exc: Exception) -> None:
print("Loop failed:", exc)
@tasks.loop(seconds=5.0, on_error=log_error)
@loop(seconds=5.0, on_error=log_error)
async def worker():
...
```
@ -38,7 +38,7 @@ async def worker():
Run setup and teardown code using `before_loop` and `after_loop`:
```python
@tasks.loop(seconds=5.0)
@loop(seconds=5.0)
async def worker():
...
@ -58,7 +58,7 @@ from datetime import datetime, timedelta
time_to_run = (datetime.now() + timedelta(seconds=5)).time()
@tasks.loop(time_of_day=time_to_run)
@loop(time_of_day=time_to_run)
async def daily_task():
...
```

View File

@ -32,11 +32,12 @@ try:
GatewayIntent,
Message,
Guild,
ext,
AuthenticationError,
DisagreementException,
Cog,
command,
CommandContext,
)
from disagreement.ext import commands
except ImportError:
print(
"Failed to import disagreement. Make sure it's installed or PYTHONPATH is set correctly."
@ -87,26 +88,26 @@ client = Client(token=BOT_TOKEN, intents=intents, command_prefix="!")
# --- Define a Cog for example commands ---
class ExampleCog(commands.Cog): # Ensuring this uses commands.Cog
class ExampleCog(Cog): # Ensuring this uses commands.Cog
def __init__(
self, bot_client
): # Renamed client to bot_client to avoid conflict with self.client
super().__init__(bot_client) # Pass the client instance to the base Cog
@commands.command(name="hello", aliases=["hi"])
async def hello_command(self, ctx: commands.CommandContext, *, who: str = "world"):
@command(name="hello", aliases=["hi"])
async def hello_command(self, ctx: CommandContext, *, who: str = "world"):
"""Greets someone."""
await ctx.reply(f"Hello {ctx.author.mention} and {who}!")
print(f"Executed 'hello' command for {ctx.author.username}, greeting {who}")
@commands.command()
async def ping(self, ctx: commands.CommandContext):
@command()
async def ping(self, ctx: CommandContext):
"""Responds with Pong!"""
await ctx.reply("Pong!")
print(f"Executed 'ping' command for {ctx.author.username}")
@commands.command()
async def me(self, ctx: commands.CommandContext):
@command()
async def me(self, ctx: CommandContext):
"""Shows information about the invoking user."""
reply_content = (
f"Hello {ctx.author.mention}!\n"
@ -117,8 +118,8 @@ class ExampleCog(commands.Cog): # Ensuring this uses commands.Cog
await ctx.reply(reply_content)
print(f"Executed 'me' command for {ctx.author.username}")
@commands.command(name="add")
async def add_numbers(self, ctx: commands.CommandContext, num1: int, num2: int):
@command(name="add")
async def add_numbers(self, ctx: CommandContext, num1: int, num2: int):
"""Adds two numbers."""
result = num1 + num2
await ctx.reply(f"The sum of {num1} and {num2} is {result}.")
@ -126,16 +127,16 @@ class ExampleCog(commands.Cog): # Ensuring this uses commands.Cog
f"Executed 'add' command for {ctx.author.username}: {num1} + {num2} = {result}"
)
@commands.command(name="say")
async def say_something(self, ctx: commands.CommandContext, *, text_to_say: str):
@command(name="say")
async def say_something(self, ctx: CommandContext, *, text_to_say: str):
"""Repeats the text you provide."""
await ctx.reply(f"You said: {text_to_say}")
print(
f"Executed 'say' command for {ctx.author.username}, saying: {text_to_say}"
)
@commands.command(name="whois")
async def whois(self, ctx: commands.CommandContext, *, name: str):
@command(name="whois")
async def whois(self, ctx: CommandContext, *, name: str):
"""Looks up a member by username or nickname using the guild cache."""
if not ctx.guild:
await ctx.reply("This command can only be used in a guild.")
@ -149,8 +150,8 @@ class ExampleCog(commands.Cog): # Ensuring this uses commands.Cog
else:
await ctx.reply("Member not found in cache.")
@commands.command(name="quit")
async def quit_command(self, ctx: commands.CommandContext):
@command(name="quit")
async def quit_command(self, ctx: CommandContext):
"""Shuts down the bot (requires YOUR_USER_ID to be set)."""
# Replace YOUR_USER_ID with your actual Discord User ID for a safe shutdown command
your_user_id = "YOUR_USER_ID_REPLACE_ME" # IMPORTANT: Replace this

View File

@ -3,8 +3,6 @@ import asyncio
from typing import Union
from disagreement import (
Client,
ui,
ext,
HybridContext,
Message,
SelectOption,
@ -27,9 +25,14 @@ from disagreement import (
ChannelType,
MessageFlags,
Interaction,
Cog,
CommandContext,
AppCommandContext,
hybrid_command,
View,
button,
select,
)
from disagreement.ext.commands import Cog, CommandContext
from disagreement.ext.app_commands import AppCommandContext, hybrid_command
try:
from dotenv import load_dotenv
@ -69,12 +72,12 @@ STOCKS = [
# Define a View class that contains our components
class MyView(ui.View):
class MyView(View):
def __init__(self):
super().__init__(timeout=180) # 180-second timeout
self.click_count = 0
@ui.button(label="Click Me!", style=ButtonStyle.SUCCESS, emoji="🖱️")
@button(label="Click Me!", style=ButtonStyle.SUCCESS, emoji="🖱️")
async def click_me(self, interaction: Interaction):
self.click_count += 1
await interaction.respond(
@ -82,7 +85,7 @@ class MyView(ui.View):
ephemeral=True,
)
@ui.select(
@select(
custom_id="string_select",
placeholder="Choose an option",
options=[
@ -108,12 +111,12 @@ class MyView(ui.View):
# View for cycling through available stocks
class StockView(ui.View):
class StockView(View):
def __init__(self):
super().__init__(timeout=180)
self.index = 0
@ui.button(label="Next Stock", style=ButtonStyle.PRIMARY)
@button(label="Next Stock", style=ButtonStyle.PRIMARY)
async def next_stock(self, interaction: Interaction):
self.index = (self.index + 1) % len(STOCKS)
stock = STOCKS[self.index]

View File

@ -4,19 +4,18 @@
import asyncio
import os
from disagreement import Client, GatewayIntent
from disagreement.ext import commands
from disagreement import Client, GatewayIntent, Cog, command, CommandContext
from dotenv import load_dotenv
load_dotenv()
class Basics(commands.Cog):
class Basics(Cog):
def __init__(self, client: Client) -> None:
super().__init__(client)
@commands.command()
async def ping(self, ctx: commands.CommandContext) -> None:
@command()
async def ping(self, ctx: CommandContext) -> None:
await ctx.reply(f"Pong! Gateway Latency: {self.client.latency_ms} ms.") # type: ignore (latency is None during static analysis)

View File

@ -9,8 +9,7 @@ from typing import Set
if os.path.join(os.getcwd(), "examples") == os.path.dirname(os.path.abspath(__file__)):
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from disagreement import Client, GatewayIntent, Member, Message
from disagreement.ext import commands
from disagreement import Client, GatewayIntent, Member, Message, Cog, command, CommandContext
try:
from dotenv import load_dotenv
@ -37,21 +36,21 @@ client = Client(token=BOT_TOKEN, command_prefix="!", intents=intents)
BANNED_WORDS: Set[str] = {"badword1", "badword2"}
class ModerationCog(commands.Cog):
class ModerationCog(Cog):
def __init__(self, bot: Client) -> None:
super().__init__(bot)
@commands.command()
@command()
async def kick(
self, ctx: commands.CommandContext, member: Member, *, reason: str = ""
self, ctx: CommandContext, member: Member, *, reason: str = ""
) -> None:
"""Kick a member from the guild."""
await member.kick(reason=reason or None)
await ctx.reply(f"Kicked {member.display_name}")
@commands.command()
@command()
async def ban(
self, ctx: commands.CommandContext, member: Member, *, reason: str = ""
self, ctx: CommandContext, member: Member, *, reason: str = ""
) -> None:
"""Ban a member from the guild."""
await member.ban(reason=reason or None)

View File

@ -33,8 +33,10 @@ try:
Member,
HTTPException,
AuthenticationError,
Cog,
command,
CommandContext,
)
from disagreement.ext import commands
except ImportError:
print(
"Failed to import disagreement. Make sure it's installed or PYTHONPATH is set correctly."
@ -72,12 +74,12 @@ client = Client(token=BOT_TOKEN, intents=intents, command_prefix="!")
# --- Define a Cog for reaction-related commands ---
class ReactionCog(commands.Cog):
class ReactionCog(Cog):
def __init__(self, bot_client):
super().__init__(bot_client)
@commands.command(name="react")
async def react_command(self, ctx: commands.CommandContext):
@command(name="react")
async def react_command(self, ctx: CommandContext):
"""Reacts to the command message with a thumbs up."""
try:
# The emoji can be a standard Unicode emoji or a custom one in the format '<:name:id>'

View File

@ -1,7 +1,7 @@
from disagreement.ext import tasks
from disagreement import loop
@tasks.loop(seconds=2.0)
@loop(seconds=2.0)
async def ticker() -> None:
print("Extension tick")

View File

@ -8,12 +8,12 @@ import sys
if os.path.join(os.getcwd(), "examples") == os.path.dirname(os.path.abspath(__file__)):
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from disagreement.ext import tasks
from disagreement import loop
counter = 0
@tasks.loop(seconds=1.0)
@loop(seconds=1.0)
async def ticker() -> None:
global counter
counter += 1

View File

@ -24,8 +24,15 @@ if os.path.join(os.getcwd(), "examples") == os.path.dirname(os.path.abspath(__fi
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
try:
from disagreement import Client, GatewayIntent, HTTPException, AuthenticationError
from disagreement.ext import commands
from disagreement import (
Client,
GatewayIntent,
HTTPException,
AuthenticationError,
Cog,
command,
CommandContext,
)
except ImportError:
print(
"Failed to import disagreement. Make sure it's installed or PYTHONPATH is set correctly."
@ -60,12 +67,12 @@ client = Client(token=BOT_TOKEN, intents=intents, command_prefix="!")
# --- Define a Cog for the typing indicator command ---
class TypingCog(commands.Cog):
class TypingCog(Cog):
def __init__(self, bot_client):
super().__init__(bot_client)
@commands.command(name="typing")
async def typing_test_command(self, ctx: commands.CommandContext):
@command(name="typing")
async def typing_test_command(self, ctx: CommandContext):
"""Shows a typing indicator for 5 seconds."""
await ctx.reply("Showing typing indicator for 5 seconds...")
try: