Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: smarter option decorator #950

Merged
merged 2 commits into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/migration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,9 @@ your option name! Example:
bot = interactions.Client("TOKEN", default_scope=1234567890)

@bot.command(default_scope=False)
@interactions.option(str, name="opt1", required=True) # description is optional.
@interactions.option(4, name="opt2", description="This is an option.", converter="hi", required=True)
@interactions.option(interactions.Channel, name="opt3")
@interactions.option() # type and name default to ones in the parameter.
@interactions.option(name="opt2", description="This is an option.", converter="hi")
@interactions.option() # same kwargs as Option
async def command_with_options(
ctx, opt1: str, hi: int, opt3: interactions.Channel = None
):
Expand Down
17 changes: 9 additions & 8 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -200,15 +200,16 @@ The :ref:`@option() <models.command:Application Command Models>` decorator creat
bot = interactions.Client(token="your_secret_bot_token")

@bot.command(scope=the_id_of_your_guild)
@interactions.option(str, name="text", description="What you want to say", required=True)
@interactions.option()
async def say_something(ctx: interactions.CommandContext, text: str):
"""say something!"""
await ctx.send(f"You said '{text}'!")

* The first field in the ``@option()`` decorator is the type of the option. This is positional only and required. You can use integers, the default Python types, the ``OptionType`` enum, or supported objects such as ``interactions.Channel``.
* All other arguments in the decorator are keyword arguments only.
* The ``name`` field is required.
* All arguments in the decorator are keyword arguments only.
* The ``type`` and ``name`` fields default to the typehint and the name of the parameter.
* The ``description`` field is optional and defaults to ``"No description set``.
* The ``required`` field defaults to whether the default for the parameter is empty.
* For typehinting or inputting the ``type`` field, you can use integers, the default Python types, the ``OptionType`` enum, or supported objects such as ``interactions.Channel``.
* Any parameters from ``Option`` can be passed into the ``@option()`` decorator.

.. note::
Expand Down Expand Up @@ -258,9 +259,9 @@ Here is the structure of a subcommand:
)
async def cmd(ctx: interactions.CommandContext, sub_command: str, second_option: str = "", option: int = None):
if sub_command == "command_name":
await ctx.send(f"You selected the command_name sub command and put in {option}")
await ctx.send(f"You selected the command_name sub command and put in {option}")
elif sub_command == "second_command":
await ctx.send(f"You selected the second_command sub command and put in {second_option}")
await ctx.send(f"You selected the second_command sub command and put in {second_option}")

You can also create subcommands using the command system:

Expand All @@ -276,13 +277,13 @@ You can also create subcommands using the command system:
pass

@base_command.subcommand()
@interactions.option(str, name="option", description="A descriptive description", required=False)
@interactions.option(description="A descriptive description")
async def command_name(ctx: interactions.CommandContext, option: int = None):
"""A descriptive description"""
await ctx.send(f"You selected the command_name sub command and put in {option}")

@base_command.subcommand()
@interactions.option(str, name="second_option", description="A descriptive description", required=True)
@interactions.option(description="A descriptive description")
async def second_command(ctx: interactions.CommandContext, second_option: str):
"""A descriptive description"""
await ctx.send(f"You selected the second_command sub command and put in {second_option}")
Expand Down
89 changes: 48 additions & 41 deletions interactions/client/models/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from ...api.models.guild import Guild
from ...api.models.member import Member
from ...api.models.message import Attachment
from ...api.models.misc import File, Image, Snowflake
from ...api.models.misc import Snowflake
from ...api.models.role import Role
from ...api.models.user import User
from ..enums import ApplicationCommandType, Locale, OptionType, PermissionType
Expand Down Expand Up @@ -210,66 +210,73 @@ class ApplicationCommand(DictSerializerMixin):


def option(
option_type: OptionType,
description: str = "No description set",
/,
name: str,
description: Optional[str] = "No description set",
**kwargs,
) -> Callable[[Callable[..., Awaitable]], Callable[..., Awaitable]]:
r"""
A decorator for adding options to a command.

The ``type`` and ``name`` of the option are defaulted to the parameter's typehint and name.

When the ``name`` of the option differs from the parameter name,
the ``converter`` field will default to the name of the parameter.

The structure of an option:

.. code-block:: python

@client.command()
@interactions.option(str, name="opt", ...)
@interactions.option("description (optional)") # kwargs are optional, same as Option
async def my_command(ctx, opt: str):
...

:param option_type: The type of the option.
:type option_type: OptionType
:param name: The name of the option.
:type name: str
:param description?: The description of the option. Defaults to ``"No description set"``.
:param description?: The description of the option. Defaults to "No description set".
:type description?: str
:param \**kwargs: The keyword arguments of the option, same as :class:`Option`.
:type \**kwargs: dict
:param \**kwargs?: The keyword arguments of the option, same as :class:`Option`.
:type \**kwargs?: dict
"""
if option_type in (str, int, float, bool):
if option_type is str:
option_type = OptionType.STRING
elif option_type is int:
option_type = OptionType.INTEGER
elif option_type is float:
option_type = OptionType.NUMBER
elif option_type is bool:
option_type = OptionType.BOOLEAN
elif option_type in (Member, User):
option_type = OptionType.USER
elif option_type is Channel:
option_type = OptionType.CHANNEL
elif option_type is Role:
option_type = OptionType.ROLE
elif option_type in (Attachment, File, Image):
option_type = OptionType.ATTACHMENT

option: Option = Option(
type=option_type,
name=name,
description=description,
**kwargs,
)

def decorator(coro: Callable[..., Awaitable]) -> Callable[..., Awaitable]:
nonlocal option
parameters = list(signature(coro).parameters.values())

if not hasattr(coro, "_options") or not isinstance(coro._options, list):
coro._options = []

param = parameters[-1 - len(coro._options)]

if hasattr(coro, "_options") and isinstance(coro._options, list):
coro._options.insert(0, option)
else:
coro._options = [option]
option_type = kwargs.get("type", param.annotation)
name = kwargs.pop("name", param.name)
if name != param.name:
kwargs["converter"] = param.name

if option_type is param.empty:
raise LibraryException(
code=12,
message=f"No type specified for option '{name}'.",
)

option_types = {
str: OptionType.STRING,
int: OptionType.INTEGER,
bool: OptionType.BOOLEAN,
User: OptionType.USER,
Member: OptionType.USER,
Channel: OptionType.CHANNEL,
Role: OptionType.ROLE,
float: OptionType.NUMBER,
Attachment: OptionType.ATTACHMENT,
}
option_type = option_types.get(option_type, option_type)

_option = Option(
type=option_type,
name=name,
description=kwargs.pop("description", description),
required=kwargs.pop("required", param.default is param.empty),
**kwargs,
)
coro._options.insert(0, _option)
return coro

return decorator
Expand Down