Implement app_commands.rename decorator

This commit is contained in:
Nadir Chowdhury 2022-03-28 10:52:33 +01:00 committed by GitHub
parent def035bf9a
commit c6d0c82d66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 6 deletions

View File

@ -75,6 +75,7 @@ __all__ = (
'command', 'command',
'describe', 'describe',
'check', 'check',
'rename',
'choices', 'choices',
'autocomplete', 'autocomplete',
'guilds', 'guilds',
@ -217,6 +218,29 @@ def _populate_descriptions(params: Dict[str, CommandParameter], descriptions: Di
raise TypeError(f'unknown parameter given: {first}') raise TypeError(f'unknown parameter given: {first}')
def _populate_renames(params: Dict[str, CommandParameter], renames: Dict[str, str]) -> None:
rename_map: Dict[str, str] = {}
# original name to renamed name
for name in params.keys():
new_name = renames.pop(name, MISSING)
if new_name is MISSING:
rename_map[name] = name
continue
if name in rename_map:
raise ValueError(f'{new_name} is already used')
rename_map[name] = new_name
params[name]._rename = new_name
if renames:
first = next(iter(renames))
raise ValueError(f'unknown parameter given: {first}')
def _populate_choices(params: Dict[str, CommandParameter], all_choices: Dict[str, List[Choice]]) -> None: def _populate_choices(params: Dict[str, CommandParameter], all_choices: Dict[str, List[Choice]]) -> None:
for name, param in params.items(): for name, param in params.items():
choices = all_choices.pop(name, MISSING) choices = all_choices.pop(name, MISSING)
@ -298,6 +322,13 @@ def _extract_parameters_from_callback(func: Callable[..., Any], globalns: Dict[s
else: else:
_populate_descriptions(result, descriptions) _populate_descriptions(result, descriptions)
try:
renames = func.__discord_app_commands_param_rename__
except AttributeError:
pass
else:
_populate_renames(result, renames)
try: try:
choices = func.__discord_app_commands_param_choices__ choices = func.__discord_app_commands_param_choices__
except AttributeError: except AttributeError:
@ -475,23 +506,29 @@ class Command(Generic[GroupT, P, T]):
raise CheckFailure(f'The check functions for command {self.name!r} failed.') raise CheckFailure(f'The check functions for command {self.name!r} failed.')
values = namespace.__dict__ values = namespace.__dict__
for name, param in self._params.items(): transformed_values = {}
# get parameters mapped to their renamed names
params = {param.display_name: param for param in self._params.values()}
for name, param in params.items():
try: try:
value = values[name] value = values[name]
except KeyError: except KeyError:
if not param.required: if not param.required:
values[name] = param.default transformed_values[name] = param.default
else: else:
raise CommandSignatureMismatch(self) from None raise CommandSignatureMismatch(self) from None
else: else:
values[name] = await param.transform(interaction, value) if param._rename is not MISSING:
name = param.name
transformed_values[name] = await param.transform(interaction, value)
# These type ignores are because the type checker doesn't quite understand the narrowing here # These type ignores are because the type checker doesn't quite understand the narrowing here
# Likewise, it thinks we're missing positional arguments when there aren't any. # Likewise, it thinks we're missing positional arguments when there aren't any.
try: try:
if self.binding is not None: if self.binding is not None:
return await self._callback(self.binding, interaction, **values) # type: ignore return await self._callback(self.binding, interaction, **transformed_values) # type: ignore
return await self._callback(interaction, **values) # type: ignore return await self._callback(interaction, **transformed_values) # type: ignore
except TypeError as e: except TypeError as e:
# In order to detect mismatch from the provided signature and the Discord data, # In order to detect mismatch from the provided signature and the Discord data,
# there are many ways it can go wrong yet all of them eventually lead to a TypeError # there are many ways it can go wrong yet all of them eventually lead to a TypeError
@ -1280,6 +1317,46 @@ def describe(**parameters: str) -> Callable[[T], T]:
return decorator return decorator
def rename(**parameters: str) -> Callable[[T], T]:
r"""Renames the given parameters by their name using the key of the keyword argument
as the name.
Example:
.. code-block:: python3
@app_commands.command()
@app_commands.rename(the_member_to_ban='member')
async def ban(interaction: discord.Interaction, the_member_to_ban: discord.Member):
await interaction.response.send_message(f'Banned {the_member_to_ban}')
Parameters
-----------
\*\*parameters
The name of the parameters.
Raises
--------
ValueError
The parameter name is already used by another parameter.
TypeError
The parameter name is not found.
"""
def decorator(inner: T) -> T:
if isinstance(inner, Command):
_populate_renames(inner._params, parameters)
else:
try:
inner.__discord_app_commands_param_rename__.update(parameters) # type: ignore - Runtime attribute access
except AttributeError:
inner.__discord_app_commands_param_rename__ = parameters # type: ignore - Runtime attribute assignment
return inner
return decorator
def choices(**parameters: List[Choice[ChoiceT]]) -> Callable[[T], T]: def choices(**parameters: List[Choice[ChoiceT]]) -> Callable[[T], T]:
r"""Instructs the given parameters by their name to use the given choices for their choices. r"""Instructs the given parameters by their name to use the given choices for their choices.

View File

@ -101,12 +101,13 @@ class CommandParameter:
min_value: Optional[Union[int, float]] = None min_value: Optional[Union[int, float]] = None
max_value: Optional[Union[int, float]] = None max_value: Optional[Union[int, float]] = None
autocomplete: Optional[Callable[..., Coroutine[Any, Any, Any]]] = None autocomplete: Optional[Callable[..., Coroutine[Any, Any, Any]]] = None
_rename: str = MISSING
_annotation: Any = MISSING _annotation: Any = MISSING
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
base = { base = {
'type': self.type.value, 'type': self.type.value,
'name': self.name, 'name': self.display_name,
'description': self.description, 'description': self.description,
'required': self.required, 'required': self.required,
} }
@ -146,6 +147,11 @@ class CommandParameter:
return value return value
@property
def display_name(self) -> str:
""":class:`str`: The name of the parameter as it should be displayed to the user."""
return self.name if self._rename is MISSING else self._rename
class Transformer: class Transformer:
"""The base class that allows a type annotation in an application command parameter """The base class that allows a type annotation in an application command parameter

View File

@ -462,6 +462,9 @@ Decorators
.. autofunction:: discord.app_commands.describe .. autofunction:: discord.app_commands.describe
:decorator: :decorator:
.. autofunction:: discord.app_commands.rename
:decorator:
.. autofunction:: discord.app_commands.choices .. autofunction:: discord.app_commands.choices
:decorator: :decorator: