Skip to content

feat: add ability to use different arg name than option name #1493

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

Merged
merged 3 commits into from
Jul 20, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion interactions/ext/hybrid_commands/hybrid_slash.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ def slash_to_prefixed(cmd: HybridSlashCommand) -> _HybridToPrefixedCommand: # n
# there isn't much we can do here
raise ValueError("Autocomplete is unsupported in hybrid commands.")

name = str(option.name)
name = option.argument_name or str(option.name)
annotation = inspect.Parameter.empty
default = inspect.Parameter.empty
kind = inspect.Parameter.POSITIONAL_ONLY if cmd._uses_arg else inspect.Parameter.POSITIONAL_OR_KEYWORD
Expand Down
36 changes: 27 additions & 9 deletions interactions/models/internal/annotations/slash.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def slash_str_option(
choices: List[Union["SlashCommandChoice", dict]] | None = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
name: Optional[str] = None,
) -> Type[str]:
"""
Annotates an argument as a string type slash command option.
Expand All @@ -46,10 +47,11 @@ def slash_str_option(
choices: The choices allowed by this command
min_length: The minimum length of text a user can input.
max_length: The maximum length of text a user can input.
name: The name of the option. Defaults to the name of the argument

"""
return SlashCommandOption(
name="placeholder",
name=name,
description=description,
required=required,
autocomplete=autocomplete,
Expand All @@ -67,6 +69,7 @@ def slash_float_option(
choices: List[Union["SlashCommandChoice", dict]] | None = None,
min_value: Optional[float] = None,
max_value: Optional[float] = None,
name: Optional[str] = None,
) -> Type[float]:
"""
Annotates an argument as a float type slash command option.
Expand All @@ -78,10 +81,11 @@ def slash_float_option(
choices: The choices allowed by this command
min_value: The minimum number allowed
max_value: The maximum number allowed
name: The name of the option. Defaults to the name of the argument

"""
return SlashCommandOption(
name="placeholder",
name=name,
description=description,
required=required,
autocomplete=autocomplete,
Expand All @@ -99,6 +103,7 @@ def slash_int_option(
choices: List[Union["SlashCommandChoice", dict]] | None = None,
min_value: Optional[float] = None,
max_value: Optional[float] = None,
name: Optional[str] = None,
) -> Type[int]:
"""
Annotates an argument as a integer type slash command option.
Expand All @@ -110,10 +115,11 @@ def slash_int_option(
choices: The choices allowed by this command
min_value: The minimum number allowed
max_value: The maximum number allowed
name: The name of the option. Defaults to the name of the argument

"""
return SlashCommandOption(
name="placeholder",
name=name,
description=description,
required=required,
autocomplete=autocomplete,
Expand All @@ -127,17 +133,19 @@ def slash_int_option(
def slash_bool_option(
description: str,
required: bool = False,
name: Optional[str] = None,
) -> Type[bool]:
"""
Annotates an argument as a boolean type slash command option.

Args:
description: The description of your option
required: Is this option required?
name: The name of the option. Defaults to the name of the argument

"""
return SlashCommandOption(
name="placeholder",
name=name,
description=description,
required=required,
type=models.OptionType.BOOLEAN,
Expand All @@ -148,6 +156,7 @@ def slash_user_option(
description: str,
required: bool = False,
autocomplete: bool = False,
name: Optional[str] = None,
) -> Type[Union["User", "Member"]]:
"""
Annotates an argument as a user type slash command option.
Expand All @@ -156,10 +165,11 @@ def slash_user_option(
description: The description of your option
required: Is this option required?
autocomplete: Use autocomplete for this option
name: The name of the option. Defaults to the name of the argument

"""
return SlashCommandOption(
name="placeholder",
name=name,
description=description,
required=required,
autocomplete=autocomplete,
Expand All @@ -173,6 +183,7 @@ def slash_channel_option(
autocomplete: bool = False,
choices: List[Union["SlashCommandChoice", dict]] | None = None,
channel_types: Optional[list[Union["ChannelType", int]]] = None,
name: Optional[str] = None,
) -> Type["BaseChannel"]:
"""
Annotates an argument as a channel type slash command option.
Expand All @@ -183,10 +194,11 @@ def slash_channel_option(
autocomplete: Use autocomplete for this option
choices: The choices allowed by this command
channel_types: The types of channel allowed by this option
name: The name of the option. Defaults to the name of the argument

"""
return SlashCommandOption(
name="placeholder",
name=name,
description=description,
required=required,
autocomplete=autocomplete,
Expand All @@ -201,6 +213,7 @@ def slash_role_option(
required: bool = False,
autocomplete: bool = False,
choices: List[Union["SlashCommandChoice", dict]] | None = None,
name: Optional[str] = None,
) -> Type["Role"]:
"""
Annotates an argument as a role type slash command option.
Expand All @@ -210,10 +223,11 @@ def slash_role_option(
required: Is this option required?
autocomplete: Use autocomplete for this option
choices: The choices allowed by this command
name: The name of the option. Defaults to the name of the argument

"""
return SlashCommandOption(
name="placeholder",
name=name,
description=description,
required=required,
autocomplete=autocomplete,
Expand All @@ -227,6 +241,7 @@ def slash_mentionable_option(
required: bool = False,
autocomplete: bool = False,
choices: List[Union["SlashCommandChoice", dict]] | None = None,
name: Optional[str] = None,
) -> Type[Union["Role", "BaseChannel", "User", "Member"]]:
"""
Annotates an argument as a mentionable type slash command option.
Expand All @@ -236,10 +251,11 @@ def slash_mentionable_option(
required: Is this option required?
autocomplete: Use autocomplete for this option
choices: The choices allowed by this command
name: The name of the option. Defaults to the name of the argument

"""
return SlashCommandOption(
name="placeholder",
name=name,
description=description,
required=required,
autocomplete=autocomplete,
Expand All @@ -251,17 +267,19 @@ def slash_mentionable_option(
def slash_attachment_option(
description: str,
required: bool = False,
name: Optional[str] = None,
) -> Type["Attachment"]:
"""
Annotates an argument as an attachment type slash command option.

Args:
description: The description of your option
required: Is this option required?
name: The name of the option. Defaults to the name of the argument

"""
return SlashCommandOption(
name="placeholder",
name=name,
description=description,
required=required,
type=models.OptionType.ATTACHMENT,
Expand Down
39 changes: 34 additions & 5 deletions interactions/models/internal/application_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ class SlashCommandOption(DictSerializationMixin):
max_value: The maximum value permitted. The option needs to be an integer or float
min_length: The minimum length of text a user can input. The option needs to be a string
max_length: The maximum length of text a user can input. The option needs to be a string
argument_name: The name of the argument to be used in the function. If not given, assumed to be the same as the name of the option

"""

Expand All @@ -418,6 +419,7 @@ class SlashCommandOption(DictSerializationMixin):
max_value: Optional[float] = attrs.field(repr=False, default=None)
min_length: Optional[int] = attrs.field(repr=False, default=None)
max_length: Optional[int] = attrs.field(repr=False, default=None)
argument_name: Optional[str] = attrs.field(repr=False, default=None)

@type.validator
def _type_validator(self, attribute: str, value: int) -> None:
Expand Down Expand Up @@ -488,6 +490,7 @@ def _max_length_validator(self, attribute: str, value: Optional[int]) -> None:

def as_dict(self) -> dict:
data = attrs.asdict(self)
data.pop("argument_name", None)
data["name"] = str(self.name)
data["description"] = str(self.description)
data["choices"] = [
Expand All @@ -506,6 +509,11 @@ class SlashCommandParameter:
kind: inspect._ParameterKind = attrs.field()
default: typing.Any = attrs.field(default=MISSING)
converter: typing.Optional[typing.Callable] = attrs.field(default=None)
_option_name: typing.Optional[str] = attrs.field(default=None)

@property
def option_name(self) -> str:
return self._option_name or self.name


def _get_option_from_annotated(annotated: Annotated) -> SlashCommandOption | None:
Expand Down Expand Up @@ -601,10 +609,14 @@ def _add_option_from_anno_method(self, name: str, option: SlashCommandOption) ->
if not self.options:
self.options = []

option.name = name
if option.name is None:
option.name = name
else:
option.argument_name = name

self.options.append(option)

def _parse_parameters(self) -> None:
def _parse_parameters(self) -> None: # noqa: C901
"""
Parses the parameters that this command has into a form i.py can use.

Expand Down Expand Up @@ -665,6 +677,20 @@ def _parse_parameters(self) -> None:

self.parameters[param.name] = our_param

if self.options:
for option in self.options:
maybe_argument_name = (
option.argument_name if isinstance(option, SlashCommandOption) else option.get("argument_name")
)
if maybe_argument_name:
name = option.name if isinstance(option, SlashCommandOption) else option["name"]
try:
self.parameters[maybe_argument_name]._option_name = str(name)
except KeyError:
raise ValueError(
f'Argument name "{maybe_argument_name}" for "{name}" does not match any parameter in {self.resolved_name}\'s function.'
) from None

def to_dict(self) -> dict:
data = super().to_dict()

Expand Down Expand Up @@ -780,8 +806,8 @@ async def call_callback(self, callback: typing.Callable, ctx: "InteractionContex
new_args = []
new_kwargs = {}

for name, param in self.parameters.items():
value = kwargs_copy.pop(name, MISSING)
for param in self.parameters.values():
value = kwargs_copy.pop(param.option_name, MISSING)
if value is MISSING:
continue

Expand All @@ -791,7 +817,7 @@ async def call_callback(self, callback: typing.Callable, ctx: "InteractionContex
if param.kind == inspect.Parameter.POSITIONAL_ONLY:
new_args.append(value)
else:
new_kwargs[name] = value
new_kwargs[param.name] = value

# i do want to address one thing: what happens if you have both *args and **kwargs
# in your argument?
Expand Down Expand Up @@ -1196,6 +1222,7 @@ def slash_option(
max_value: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
argument_name: Optional[str] = None,
) -> Callable[[SlashCommandT], SlashCommandT]:
r"""
A decorator to add an option to a slash command.
Expand All @@ -1212,6 +1239,7 @@ def slash_option(
max_value: The maximum value permitted. The option needs to be an integer or float
min_length: The minimum length of text a user can input. The option needs to be a string
max_length: The maximum length of text a user can input. The option needs to be a string
argument_name: The name of the argument to be used in the function. If not given, assumed to be the same as the name of the option
"""

def wrapper(func: SlashCommandT) -> SlashCommandT:
Expand All @@ -1230,6 +1258,7 @@ def wrapper(func: SlashCommandT) -> SlashCommandT:
max_value=max_value,
min_length=min_length,
max_length=max_length,
argument_name=argument_name,
)
if not hasattr(func, "options"):
func.options = []
Expand Down