mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-06-08 04:38:42 +00:00
[commands] Implement commands.parameter
This commit is contained in:
parent
deb7958797
commit
55c5be78cf
@ -9,11 +9,12 @@ An extension module to facilitate creation of bot commands.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from .bot import *
|
from .bot import *
|
||||||
|
from .cog import *
|
||||||
from .context import *
|
from .context import *
|
||||||
from .core import *
|
|
||||||
from .errors import *
|
|
||||||
from .help import *
|
|
||||||
from .converter import *
|
from .converter import *
|
||||||
from .cooldowns import *
|
from .cooldowns import *
|
||||||
from .cog import *
|
from .core import *
|
||||||
|
from .errors import *
|
||||||
from .flags import *
|
from .flags import *
|
||||||
|
from .help import *
|
||||||
|
from .parameters import *
|
||||||
|
@ -23,18 +23,15 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import inspect
|
|
||||||
import re
|
import re
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict, Generic, List, Optional, TypeVar, Union
|
||||||
from typing import Any, Dict, Generic, List, Optional, TYPE_CHECKING, TypeVar, Union
|
|
||||||
|
|
||||||
from ._types import BotT
|
|
||||||
|
|
||||||
import discord.abc
|
import discord.abc
|
||||||
import discord.utils
|
import discord.utils
|
||||||
|
|
||||||
from discord.message import Message
|
from discord.message import Message
|
||||||
|
|
||||||
|
from ._types import BotT
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing_extensions import ParamSpec
|
from typing_extensions import ParamSpec
|
||||||
|
|
||||||
@ -47,6 +44,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
from .cog import Cog
|
from .cog import Cog
|
||||||
from .core import Command
|
from .core import Command
|
||||||
|
from .parameters import Parameter
|
||||||
from .view import StringView
|
from .view import StringView
|
||||||
|
|
||||||
# fmt: off
|
# fmt: off
|
||||||
@ -90,7 +88,7 @@ class Context(discord.abc.Messageable, Generic[BotT]):
|
|||||||
A dictionary of transformed arguments that were passed into the command.
|
A dictionary of transformed arguments that were passed into the command.
|
||||||
Similar to :attr:`args`\, if this is accessed in the
|
Similar to :attr:`args`\, if this is accessed in the
|
||||||
:func:`.on_command_error` event then this dict could be incomplete.
|
:func:`.on_command_error` event then this dict could be incomplete.
|
||||||
current_parameter: Optional[:class:`inspect.Parameter`]
|
current_parameter: Optional[:class:`Parameter`]
|
||||||
The parameter that is currently being inspected and converted.
|
The parameter that is currently being inspected and converted.
|
||||||
This is only of use for within converters.
|
This is only of use for within converters.
|
||||||
|
|
||||||
@ -143,7 +141,7 @@ class Context(discord.abc.Messageable, Generic[BotT]):
|
|||||||
invoked_subcommand: Optional[Command[Any, ..., Any]] = None,
|
invoked_subcommand: Optional[Command[Any, ..., Any]] = None,
|
||||||
subcommand_passed: Optional[str] = None,
|
subcommand_passed: Optional[str] = None,
|
||||||
command_failed: bool = False,
|
command_failed: bool = False,
|
||||||
current_parameter: Optional[inspect.Parameter] = None,
|
current_parameter: Optional[Parameter] = None,
|
||||||
current_argument: Optional[str] = None,
|
current_argument: Optional[str] = None,
|
||||||
):
|
):
|
||||||
self.message: Message = message
|
self.message: Message = message
|
||||||
@ -158,7 +156,7 @@ class Context(discord.abc.Messageable, Generic[BotT]):
|
|||||||
self.invoked_subcommand: Optional[Command[Any, ..., Any]] = invoked_subcommand
|
self.invoked_subcommand: Optional[Command[Any, ..., Any]] = invoked_subcommand
|
||||||
self.subcommand_passed: Optional[str] = subcommand_passed
|
self.subcommand_passed: Optional[str] = subcommand_passed
|
||||||
self.command_failed: bool = command_failed
|
self.command_failed: bool = command_failed
|
||||||
self.current_parameter: Optional[inspect.Parameter] = current_parameter
|
self.current_parameter: Optional[Parameter] = current_parameter
|
||||||
self.current_argument: Optional[str] = current_argument
|
self.current_argument: Optional[str] = current_argument
|
||||||
self._state: ConnectionState = self.message._state
|
self._state: ConnectionState = self.message._state
|
||||||
|
|
||||||
@ -357,7 +355,7 @@ class Context(discord.abc.Messageable, Generic[BotT]):
|
|||||||
Any
|
Any
|
||||||
The result of the help command, if any.
|
The result of the help command, if any.
|
||||||
"""
|
"""
|
||||||
from .core import Group, Command, wrap_callback
|
from .core import Command, Group, wrap_callback
|
||||||
from .errors import CommandError
|
from .errors import CommandError
|
||||||
|
|
||||||
bot = self.bot
|
bot = self.bot
|
||||||
|
@ -24,35 +24,36 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
|
||||||
import inspect
|
import inspect
|
||||||
|
import re
|
||||||
from typing import (
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
Dict,
|
Dict,
|
||||||
Generic,
|
Generic,
|
||||||
Iterable,
|
Iterable,
|
||||||
|
List,
|
||||||
Literal,
|
Literal,
|
||||||
Optional,
|
Optional,
|
||||||
TYPE_CHECKING,
|
|
||||||
List,
|
|
||||||
Protocol,
|
Protocol,
|
||||||
|
Tuple,
|
||||||
Type,
|
Type,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Tuple,
|
|
||||||
Union,
|
Union,
|
||||||
runtime_checkable,
|
runtime_checkable,
|
||||||
)
|
)
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
from .errors import *
|
from .errors import *
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .context import Context
|
|
||||||
from discord.state import Channel
|
from discord.state import Channel
|
||||||
from discord.threads import Thread
|
from discord.threads import Thread
|
||||||
|
|
||||||
|
from .parameters import Parameter
|
||||||
from ._types import BotT, _Bot
|
from ._types import BotT, _Bot
|
||||||
|
from .context import Context
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Converter',
|
'Converter',
|
||||||
@ -1062,16 +1063,6 @@ def _convert_to_bool(argument: str) -> bool:
|
|||||||
raise BadBoolArgument(lowered)
|
raise BadBoolArgument(lowered)
|
||||||
|
|
||||||
|
|
||||||
def get_converter(param: inspect.Parameter) -> Any:
|
|
||||||
converter = param.annotation
|
|
||||||
if converter is param.empty:
|
|
||||||
if param.default is not param.empty:
|
|
||||||
converter = str if param.default is None else type(param.default)
|
|
||||||
else:
|
|
||||||
converter = str
|
|
||||||
return converter
|
|
||||||
|
|
||||||
|
|
||||||
_GenericAlias = type(List[T])
|
_GenericAlias = type(List[T])
|
||||||
|
|
||||||
|
|
||||||
@ -1141,7 +1132,7 @@ async def _actual_conversion(ctx: Context[BotT], converter, argument: str, param
|
|||||||
raise BadArgument(f'Converting to "{name}" failed for parameter "{param.name}".') from exc
|
raise BadArgument(f'Converting to "{name}" failed for parameter "{param.name}".') from exc
|
||||||
|
|
||||||
|
|
||||||
async def run_converters(ctx: Context[BotT], converter: Any, argument: str, param: inspect.Parameter) -> Any:
|
async def run_converters(ctx: Context[BotT], converter: Any, argument: str, param: Parameter) -> Any:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Runs converters for a given converter, argument, and parameter.
|
Runs converters for a given converter, argument, and parameter.
|
||||||
@ -1158,7 +1149,7 @@ async def run_converters(ctx: Context[BotT], converter: Any, argument: str, para
|
|||||||
The converter to run, this corresponds to the annotation in the function.
|
The converter to run, this corresponds to the annotation in the function.
|
||||||
argument: :class:`str`
|
argument: :class:`str`
|
||||||
The argument to convert to.
|
The argument to convert to.
|
||||||
param: :class:`inspect.Parameter`
|
param: :class:`Parameter`
|
||||||
The parameter being converted. This is mainly for error reporting.
|
The parameter being converted. This is mainly for error reporting.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
@ -1183,7 +1174,7 @@ async def run_converters(ctx: Context[BotT], converter: Any, argument: str, para
|
|||||||
# with the other parameters
|
# with the other parameters
|
||||||
if conv is _NoneType and param.kind != param.VAR_POSITIONAL:
|
if conv is _NoneType and param.kind != param.VAR_POSITIONAL:
|
||||||
ctx.view.undo()
|
ctx.view.undo()
|
||||||
return None if param.default is param.empty else param.default
|
return None if param.required else await param.get_default(ctx)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
value = await run_converters(ctx, conv, argument, param)
|
value = await run_converters(ctx, conv, argument, param)
|
||||||
|
@ -23,54 +23,44 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import datetime
|
||||||
|
import functools
|
||||||
|
import inspect
|
||||||
from typing import (
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
Callable,
|
Callable,
|
||||||
Dict,
|
Dict,
|
||||||
Generator,
|
Generator,
|
||||||
Generic,
|
Generic,
|
||||||
Literal,
|
|
||||||
List,
|
List,
|
||||||
|
Literal,
|
||||||
Optional,
|
Optional,
|
||||||
Union,
|
|
||||||
Set,
|
Set,
|
||||||
Tuple,
|
Tuple,
|
||||||
TypeVar,
|
|
||||||
Type,
|
Type,
|
||||||
TYPE_CHECKING,
|
TypeVar,
|
||||||
|
Union,
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
import asyncio
|
|
||||||
import functools
|
|
||||||
import inspect
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
from .errors import *
|
|
||||||
from .cooldowns import Cooldown, BucketType, CooldownMapping, MaxConcurrency, DynamicCooldownMapping
|
|
||||||
from .converter import run_converters, get_converter, Greedy
|
|
||||||
from ._types import _BaseCommand
|
from ._types import _BaseCommand
|
||||||
from .cog import Cog
|
from .cog import Cog
|
||||||
from .context import Context
|
from .context import Context
|
||||||
|
from .converter import Greedy, run_converters
|
||||||
|
from .cooldowns import BucketType, Cooldown, CooldownMapping, DynamicCooldownMapping, MaxConcurrency
|
||||||
|
from .errors import *
|
||||||
|
from .parameters import Parameter, Signature
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing_extensions import Concatenate, ParamSpec, TypeGuard, Self
|
from typing_extensions import Concatenate, ParamSpec, Self, TypeGuard
|
||||||
|
|
||||||
from discord.message import Message
|
from discord.message import Message
|
||||||
|
|
||||||
from ._types import (
|
from ._types import BotT, Check, ContextT, Coro, CoroFunc, Error, ErrorT, Hook, HookT
|
||||||
BotT,
|
|
||||||
ContextT,
|
|
||||||
Coro,
|
|
||||||
CoroFunc,
|
|
||||||
Check,
|
|
||||||
Hook,
|
|
||||||
Error,
|
|
||||||
ErrorT,
|
|
||||||
HookT,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -131,9 +121,9 @@ def get_signature_parameters(
|
|||||||
/,
|
/,
|
||||||
*,
|
*,
|
||||||
skip_parameters: Optional[int] = None,
|
skip_parameters: Optional[int] = None,
|
||||||
) -> Dict[str, inspect.Parameter]:
|
) -> Dict[str, Parameter]:
|
||||||
signature = inspect.signature(function)
|
signature = Signature.from_callable(function)
|
||||||
params = {}
|
params: Dict[str, Parameter] = {}
|
||||||
cache: Dict[str, Any] = {}
|
cache: Dict[str, Any] = {}
|
||||||
eval_annotation = discord.utils.evaluate_annotation
|
eval_annotation = discord.utils.evaluate_annotation
|
||||||
required_params = discord.utils.is_inside_class(function) + 1 if skip_parameters is None else skip_parameters
|
required_params = discord.utils.is_inside_class(function) + 1 if skip_parameters is None else skip_parameters
|
||||||
@ -145,10 +135,14 @@ def get_signature_parameters(
|
|||||||
next(iterator)
|
next(iterator)
|
||||||
|
|
||||||
for name, parameter in iterator:
|
for name, parameter in iterator:
|
||||||
|
default = parameter.default
|
||||||
|
if isinstance(default, Parameter): # update from the default
|
||||||
|
parameter._annotation = default.annotation
|
||||||
|
parameter._default = default.default
|
||||||
|
parameter._displayed_default = default._displayed_default
|
||||||
|
|
||||||
annotation = parameter.annotation
|
annotation = parameter.annotation
|
||||||
if annotation is parameter.empty:
|
|
||||||
params[name] = parameter
|
|
||||||
continue
|
|
||||||
if annotation is None:
|
if annotation is None:
|
||||||
params[name] = parameter.replace(annotation=type(None))
|
params[name] = parameter.replace(annotation=type(None))
|
||||||
continue
|
continue
|
||||||
@ -435,7 +429,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
globalns = {}
|
globalns = {}
|
||||||
|
|
||||||
self.params: Dict[str, inspect.Parameter] = get_signature_parameters(function, globalns)
|
self.params: Dict[str, Parameter] = get_signature_parameters(function, globalns)
|
||||||
|
|
||||||
def add_check(self, func: Check[ContextT], /) -> None:
|
def add_check(self, func: Check[ContextT], /) -> None:
|
||||||
"""Adds a check to the command.
|
"""Adds a check to the command.
|
||||||
@ -571,9 +565,8 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
|||||||
finally:
|
finally:
|
||||||
ctx.bot.dispatch('command_error', ctx, error)
|
ctx.bot.dispatch('command_error', ctx, error)
|
||||||
|
|
||||||
async def transform(self, ctx: Context[BotT], param: inspect.Parameter, /) -> Any:
|
async def transform(self, ctx: Context[BotT], param: Parameter, /) -> Any:
|
||||||
required = param.default is param.empty
|
converter = param.converter
|
||||||
converter = get_converter(param)
|
|
||||||
consume_rest_is_special = param.kind == param.KEYWORD_ONLY and not self.rest_is_raw
|
consume_rest_is_special = param.kind == param.KEYWORD_ONLY and not self.rest_is_raw
|
||||||
view = ctx.view
|
view = ctx.view
|
||||||
view.skip_ws()
|
view.skip_ws()
|
||||||
@ -582,7 +575,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
|||||||
# it undos the view ready for the next parameter to use instead
|
# it undos the view ready for the next parameter to use instead
|
||||||
if isinstance(converter, Greedy):
|
if isinstance(converter, Greedy):
|
||||||
if param.kind in (param.POSITIONAL_OR_KEYWORD, param.POSITIONAL_ONLY):
|
if param.kind in (param.POSITIONAL_OR_KEYWORD, param.POSITIONAL_ONLY):
|
||||||
return await self._transform_greedy_pos(ctx, param, required, converter.converter)
|
return await self._transform_greedy_pos(ctx, param, param.required, converter.converter)
|
||||||
elif param.kind == param.VAR_POSITIONAL:
|
elif param.kind == param.VAR_POSITIONAL:
|
||||||
return await self._transform_greedy_var_pos(ctx, param, converter.converter)
|
return await self._transform_greedy_var_pos(ctx, param, converter.converter)
|
||||||
else:
|
else:
|
||||||
@ -594,13 +587,13 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
|||||||
if view.eof:
|
if view.eof:
|
||||||
if param.kind == param.VAR_POSITIONAL:
|
if param.kind == param.VAR_POSITIONAL:
|
||||||
raise RuntimeError() # break the loop
|
raise RuntimeError() # break the loop
|
||||||
if required:
|
if param.required:
|
||||||
if self._is_typing_optional(param.annotation):
|
if self._is_typing_optional(param.annotation):
|
||||||
return None
|
return None
|
||||||
if hasattr(converter, '__commands_is_flag__') and converter._can_be_constructible():
|
if hasattr(converter, '__commands_is_flag__') and converter._can_be_constructible():
|
||||||
return await converter._construct_default(ctx)
|
return await converter._construct_default(ctx)
|
||||||
raise MissingRequiredArgument(param)
|
raise MissingRequiredArgument(param)
|
||||||
return param.default
|
return await param.get_default(ctx)
|
||||||
|
|
||||||
previous = view.index
|
previous = view.index
|
||||||
if consume_rest_is_special:
|
if consume_rest_is_special:
|
||||||
@ -619,9 +612,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
|||||||
# type-checker fails to narrow argument
|
# type-checker fails to narrow argument
|
||||||
return await run_converters(ctx, converter, argument, param) # type: ignore
|
return await run_converters(ctx, converter, argument, param) # type: ignore
|
||||||
|
|
||||||
async def _transform_greedy_pos(
|
async def _transform_greedy_pos(self, ctx: Context[BotT], param: Parameter, required: bool, converter: Any) -> Any:
|
||||||
self, ctx: Context[BotT], param: inspect.Parameter, required: bool, converter: Any
|
|
||||||
) -> Any:
|
|
||||||
view = ctx.view
|
view = ctx.view
|
||||||
result = []
|
result = []
|
||||||
while not view.eof:
|
while not view.eof:
|
||||||
@ -639,10 +630,10 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
|||||||
result.append(value)
|
result.append(value)
|
||||||
|
|
||||||
if not result and not required:
|
if not result and not required:
|
||||||
return param.default
|
return await param.get_default(ctx)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def _transform_greedy_var_pos(self, ctx: Context[BotT], param: inspect.Parameter, converter: Any) -> Any:
|
async def _transform_greedy_var_pos(self, ctx: Context[BotT], param: Parameter, converter: Any) -> Any:
|
||||||
view = ctx.view
|
view = ctx.view
|
||||||
previous = view.index
|
previous = view.index
|
||||||
try:
|
try:
|
||||||
@ -655,8 +646,8 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def clean_params(self) -> Dict[str, inspect.Parameter]:
|
def clean_params(self) -> Dict[str, Parameter]:
|
||||||
"""Dict[:class:`str`, :class:`inspect.Parameter`]:
|
"""Dict[:class:`str`, :class:`Parameter`]:
|
||||||
Retrieves the parameter dictionary without the context or self parameters.
|
Retrieves the parameter dictionary without the context or self parameters.
|
||||||
|
|
||||||
Useful for inspecting signature.
|
Useful for inspecting signature.
|
||||||
@ -753,9 +744,8 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
|||||||
elif param.kind == param.KEYWORD_ONLY:
|
elif param.kind == param.KEYWORD_ONLY:
|
||||||
# kwarg only param denotes "consume rest" semantics
|
# kwarg only param denotes "consume rest" semantics
|
||||||
if self.rest_is_raw:
|
if self.rest_is_raw:
|
||||||
converter = get_converter(param)
|
|
||||||
ctx.current_argument = argument = view.read_rest()
|
ctx.current_argument = argument = view.read_rest()
|
||||||
kwargs[name] = await run_converters(ctx, converter, argument, param)
|
kwargs[name] = await run_converters(ctx, param.converter, argument, param)
|
||||||
else:
|
else:
|
||||||
kwargs[name] = await self.transform(ctx, param)
|
kwargs[name] = await self.transform(ctx, param)
|
||||||
break
|
break
|
||||||
@ -1078,29 +1068,31 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
|||||||
|
|
||||||
result = []
|
result = []
|
||||||
for name, param in params.items():
|
for name, param in params.items():
|
||||||
greedy = isinstance(param.annotation, Greedy)
|
greedy = isinstance(param.converter, Greedy)
|
||||||
optional = False # postpone evaluation of if it's an optional argument
|
optional = False # postpone evaluation of if it's an optional argument
|
||||||
|
|
||||||
# for typing.Literal[...], typing.Optional[typing.Literal[...]], and Greedy[typing.Literal[...]], the
|
annotation = param.converter.converter if greedy else param.converter # type: ignore # needs conditional types
|
||||||
# parameter signature is a literal list of it's values
|
|
||||||
annotation = param.annotation.converter if greedy else param.annotation
|
|
||||||
origin = getattr(annotation, '__origin__', None)
|
origin = getattr(annotation, '__origin__', None)
|
||||||
if not greedy and origin is Union:
|
if not greedy and origin is Union:
|
||||||
none_cls = type(None)
|
none_cls = type(None)
|
||||||
union_args = annotation.__args__
|
union_args = annotation.__args__ # type: ignore # this is safe
|
||||||
optional = union_args[-1] is none_cls
|
optional = union_args[-1] is none_cls
|
||||||
if len(union_args) == 2 and optional:
|
if len(union_args) == 2 and optional:
|
||||||
annotation = union_args[0]
|
annotation = union_args[0]
|
||||||
origin = getattr(annotation, '__origin__', None)
|
origin = getattr(annotation, '__origin__', None)
|
||||||
|
|
||||||
|
# for typing.Literal[...], typing.Optional[typing.Literal[...]], and Greedy[typing.Literal[...]], the
|
||||||
|
# parameter signature is a literal list of it's values
|
||||||
if origin is Literal:
|
if origin is Literal:
|
||||||
name = '|'.join(f'"{v}"' if isinstance(v, str) else str(v) for v in annotation.__args__)
|
name = '|'.join(f'"{v}"' if isinstance(v, str) else str(v) for v in annotation.__args__) # type: ignore # this is safe
|
||||||
if param.default is not param.empty:
|
if not param.required:
|
||||||
# We don't want None or '' to trigger the [name=value] case and instead it should
|
# We don't want None or '' to trigger the [name=value] case and instead it should
|
||||||
# do [name] since [name=None] or [name=] are not exactly useful for the user.
|
# do [name] since [name=None] or [name=] are not exactly useful for the user.
|
||||||
should_print = param.default if isinstance(param.default, str) else param.default is not None
|
should_print = param.default if isinstance(param.default, str) else param.default is not None
|
||||||
if should_print:
|
if should_print:
|
||||||
result.append(f'[{name}={param.default}]' if not greedy else f'[{name}={param.default}]...')
|
result.append(
|
||||||
|
f'[{name}={param.displayed_default}]' if not greedy else f'[{name}={param.displayed_default}]...'
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
result.append(f'[{name}]')
|
result.append(f'[{name}]')
|
||||||
|
@ -24,22 +24,21 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional, Any, TYPE_CHECKING, List, Callable, Tuple, Union
|
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from discord.errors import ClientException, DiscordException
|
from discord.errors import ClientException, DiscordException
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from inspect import Parameter
|
|
||||||
|
|
||||||
from .converter import Converter
|
|
||||||
from .context import Context
|
|
||||||
from .cooldowns import Cooldown, BucketType
|
|
||||||
from .flags import Flag
|
|
||||||
from discord.abc import GuildChannel
|
from discord.abc import GuildChannel
|
||||||
from discord.threads import Thread
|
from discord.threads import Thread
|
||||||
from discord.types.snowflake import Snowflake, SnowflakeList
|
from discord.types.snowflake import Snowflake, SnowflakeList
|
||||||
|
|
||||||
from ._types import BotT
|
from ._types import BotT
|
||||||
|
from .context import Context
|
||||||
|
from .converter import Converter
|
||||||
|
from .cooldowns import BucketType, Cooldown
|
||||||
|
from .flags import Flag
|
||||||
|
from .parameters import Parameter
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -173,7 +172,7 @@ class MissingRequiredArgument(UserInputError):
|
|||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
param: :class:`inspect.Parameter`
|
param: :class:`Parameter`
|
||||||
The argument that is missing.
|
The argument that is missing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -687,11 +686,11 @@ class MissingAnyRole(CheckFailure):
|
|||||||
missing = [f"'{role}'" for role in missing_roles]
|
missing = [f"'{role}'" for role in missing_roles]
|
||||||
|
|
||||||
if len(missing) > 2:
|
if len(missing) > 2:
|
||||||
fmt = '{}, or {}'.format(", ".join(missing[:-1]), missing[-1])
|
fmt = '{}, or {}'.format(', '.join(missing[:-1]), missing[-1])
|
||||||
else:
|
else:
|
||||||
fmt = ' or '.join(missing)
|
fmt = ' or '.join(missing)
|
||||||
|
|
||||||
message = f"You are missing at least one of the required roles: {fmt}"
|
message = f'You are missing at least one of the required roles: {fmt}'
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
@ -717,11 +716,11 @@ class BotMissingAnyRole(CheckFailure):
|
|||||||
missing = [f"'{role}'" for role in missing_roles]
|
missing = [f"'{role}'" for role in missing_roles]
|
||||||
|
|
||||||
if len(missing) > 2:
|
if len(missing) > 2:
|
||||||
fmt = '{}, or {}'.format(", ".join(missing[:-1]), missing[-1])
|
fmt = '{}, or {}'.format(', '.join(missing[:-1]), missing[-1])
|
||||||
else:
|
else:
|
||||||
fmt = ' or '.join(missing)
|
fmt = ' or '.join(missing)
|
||||||
|
|
||||||
message = f"Bot is missing at least one of the required roles: {fmt}"
|
message = f'Bot is missing at least one of the required roles: {fmt}'
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
@ -761,7 +760,7 @@ class MissingPermissions(CheckFailure):
|
|||||||
missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_permissions]
|
missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_permissions]
|
||||||
|
|
||||||
if len(missing) > 2:
|
if len(missing) > 2:
|
||||||
fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1])
|
fmt = '{}, and {}'.format(', '.join(missing[:-1]), missing[-1])
|
||||||
else:
|
else:
|
||||||
fmt = ' and '.join(missing)
|
fmt = ' and '.join(missing)
|
||||||
message = f'You are missing {fmt} permission(s) to run this command.'
|
message = f'You are missing {fmt} permission(s) to run this command.'
|
||||||
@ -786,7 +785,7 @@ class BotMissingPermissions(CheckFailure):
|
|||||||
missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_permissions]
|
missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_permissions]
|
||||||
|
|
||||||
if len(missing) > 2:
|
if len(missing) > 2:
|
||||||
fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1])
|
fmt = '{}, and {}'.format(', '.join(missing[:-1]), missing[-1])
|
||||||
else:
|
else:
|
||||||
fmt = ' and '.join(missing)
|
fmt = ' and '.join(missing)
|
||||||
message = f'Bot requires {fmt} permission(s) to run this command.'
|
message = f'Bot requires {fmt} permission(s) to run this command.'
|
||||||
|
@ -24,37 +24,17 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from .errors import (
|
|
||||||
BadFlagArgument,
|
|
||||||
CommandError,
|
|
||||||
MissingFlagArgument,
|
|
||||||
TooManyFlags,
|
|
||||||
MissingRequiredFlag,
|
|
||||||
)
|
|
||||||
|
|
||||||
from discord.utils import resolve_annotation
|
|
||||||
from .view import StringView
|
|
||||||
from .converter import run_converters
|
|
||||||
|
|
||||||
from discord.utils import maybe_coroutine, MISSING
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from typing import (
|
|
||||||
Dict,
|
|
||||||
Iterator,
|
|
||||||
Literal,
|
|
||||||
Optional,
|
|
||||||
Pattern,
|
|
||||||
Set,
|
|
||||||
TYPE_CHECKING,
|
|
||||||
Tuple,
|
|
||||||
List,
|
|
||||||
Any,
|
|
||||||
Union,
|
|
||||||
)
|
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
import sys
|
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Literal, Optional, Pattern, Set, Tuple, Union
|
||||||
|
|
||||||
|
from discord.utils import MISSING, maybe_coroutine, resolve_annotation
|
||||||
|
|
||||||
|
from .converter import run_converters
|
||||||
|
from .errors import BadFlagArgument, CommandError, MissingFlagArgument, MissingRequiredFlag, TooManyFlags
|
||||||
|
from .view import StringView
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Flag',
|
'Flag',
|
||||||
@ -66,9 +46,9 @@ __all__ = (
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
from .context import Context
|
|
||||||
|
|
||||||
from ._types import BotT
|
from ._types import BotT
|
||||||
|
from .context import Context
|
||||||
|
from .parameters import Parameter
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -351,7 +331,7 @@ class FlagsMeta(type):
|
|||||||
async def tuple_convert_all(ctx: Context[BotT], argument: str, flag: Flag, converter: Any) -> Tuple[Any, ...]:
|
async def tuple_convert_all(ctx: Context[BotT], argument: str, flag: Flag, converter: Any) -> Tuple[Any, ...]:
|
||||||
view = StringView(argument)
|
view = StringView(argument)
|
||||||
results = []
|
results = []
|
||||||
param: inspect.Parameter = ctx.current_parameter # type: ignore
|
param: Parameter = ctx.current_parameter # type: ignore
|
||||||
while not view.eof:
|
while not view.eof:
|
||||||
view.skip_ws()
|
view.skip_ws()
|
||||||
if view.eof:
|
if view.eof:
|
||||||
@ -376,7 +356,7 @@ async def tuple_convert_all(ctx: Context[BotT], argument: str, flag: Flag, conve
|
|||||||
async def tuple_convert_flag(ctx: Context[BotT], argument: str, flag: Flag, converters: Any) -> Tuple[Any, ...]:
|
async def tuple_convert_flag(ctx: Context[BotT], argument: str, flag: Flag, converters: Any) -> Tuple[Any, ...]:
|
||||||
view = StringView(argument)
|
view = StringView(argument)
|
||||||
results = []
|
results = []
|
||||||
param: inspect.Parameter = ctx.current_parameter # type: ignore
|
param: Parameter = ctx.current_parameter # type: ignore
|
||||||
for converter in converters:
|
for converter in converters:
|
||||||
view.skip_ws()
|
view.skip_ws()
|
||||||
if view.eof:
|
if view.eof:
|
||||||
@ -402,7 +382,7 @@ async def tuple_convert_flag(ctx: Context[BotT], argument: str, flag: Flag, conv
|
|||||||
|
|
||||||
|
|
||||||
async def convert_flag(ctx: Context[BotT], argument: str, flag: Flag, annotation: Any = None) -> Any:
|
async def convert_flag(ctx: Context[BotT], argument: str, flag: Flag, annotation: Any = None) -> Any:
|
||||||
param: inspect.Parameter = ctx.current_parameter # type: ignore
|
param: Parameter = ctx.current_parameter # type: ignore
|
||||||
annotation = annotation or flag.annotation
|
annotation = annotation or flag.annotation
|
||||||
try:
|
try:
|
||||||
origin = annotation.__origin__
|
origin = annotation.__origin__
|
||||||
|
@ -51,13 +51,13 @@ from .errors import CommandError
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
import inspect
|
|
||||||
|
|
||||||
import discord.abc
|
import discord.abc
|
||||||
|
|
||||||
from .bot import BotBase
|
from .bot import BotBase
|
||||||
from .context import Context
|
from .context import Context
|
||||||
from .cog import Cog
|
from .cog import Cog
|
||||||
|
from .parameters import Parameter
|
||||||
|
|
||||||
from ._types import (
|
from ._types import (
|
||||||
Check,
|
Check,
|
||||||
@ -224,9 +224,7 @@ class _HelpCommandImpl(Command):
|
|||||||
super().__init__(inject.command_callback, *args, **kwargs)
|
super().__init__(inject.command_callback, *args, **kwargs)
|
||||||
self._original: HelpCommand = inject
|
self._original: HelpCommand = inject
|
||||||
self._injected: HelpCommand = inject
|
self._injected: HelpCommand = inject
|
||||||
self.params: Dict[str, inspect.Parameter] = get_signature_parameters(
|
self.params: Dict[str, Parameter] = get_signature_parameters(inject.command_callback, globals(), skip_parameters=1)
|
||||||
inject.command_callback, globals(), skip_parameters=1
|
|
||||||
)
|
|
||||||
|
|
||||||
async def prepare(self, ctx: Context[Any]) -> None:
|
async def prepare(self, ctx: Context[Any]) -> None:
|
||||||
self._injected = injected = self._original.copy()
|
self._injected = injected = self._original.copy()
|
||||||
@ -1021,7 +1019,7 @@ class DefaultHelpCommand(HelpCommand):
|
|||||||
self.sort_commands: bool = options.pop('sort_commands', True)
|
self.sort_commands: bool = options.pop('sort_commands', True)
|
||||||
self.dm_help: bool = options.pop('dm_help', False)
|
self.dm_help: bool = options.pop('dm_help', False)
|
||||||
self.dm_help_threshold: int = options.pop('dm_help_threshold', 1000)
|
self.dm_help_threshold: int = options.pop('dm_help_threshold', 1000)
|
||||||
self.commands_heading: str = options.pop('commands_heading', "Commands:")
|
self.commands_heading: str = options.pop('commands_heading', 'Commands:')
|
||||||
self.no_category: str = options.pop('no_category', 'No Category')
|
self.no_category: str = options.pop('no_category', 'No Category')
|
||||||
self.paginator: Paginator = options.pop('paginator', None)
|
self.paginator: Paginator = options.pop('paginator', None)
|
||||||
|
|
||||||
@ -1045,8 +1043,8 @@ class DefaultHelpCommand(HelpCommand):
|
|||||||
""":class:`str`: Returns help command's ending note. This is mainly useful to override for i18n purposes."""
|
""":class:`str`: Returns help command's ending note. This is mainly useful to override for i18n purposes."""
|
||||||
command_name = self.invoked_with
|
command_name = self.invoked_with
|
||||||
return (
|
return (
|
||||||
f"Type {self.context.clean_prefix}{command_name} command for more info on a command.\n"
|
f'Type {self.context.clean_prefix}{command_name} command for more info on a command.\n'
|
||||||
f"You can also type {self.context.clean_prefix}{command_name} category for more info on a category."
|
f'You can also type {self.context.clean_prefix}{command_name} category for more info on a category.'
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_indented_commands(
|
def add_indented_commands(
|
||||||
@ -1235,10 +1233,10 @@ class MinimalHelpCommand(HelpCommand):
|
|||||||
|
|
||||||
def __init__(self, **options: Any) -> None:
|
def __init__(self, **options: Any) -> None:
|
||||||
self.sort_commands: bool = options.pop('sort_commands', True)
|
self.sort_commands: bool = options.pop('sort_commands', True)
|
||||||
self.commands_heading: str = options.pop('commands_heading', "Commands")
|
self.commands_heading: str = options.pop('commands_heading', 'Commands')
|
||||||
self.dm_help: bool = options.pop('dm_help', False)
|
self.dm_help: bool = options.pop('dm_help', False)
|
||||||
self.dm_help_threshold: int = options.pop('dm_help_threshold', 1000)
|
self.dm_help_threshold: int = options.pop('dm_help_threshold', 1000)
|
||||||
self.aliases_heading: str = options.pop('aliases_heading', "Aliases:")
|
self.aliases_heading: str = options.pop('aliases_heading', 'Aliases:')
|
||||||
self.no_category: str = options.pop('no_category', 'No Category')
|
self.no_category: str = options.pop('no_category', 'No Category')
|
||||||
self.paginator: Paginator = options.pop('paginator', None)
|
self.paginator: Paginator = options.pop('paginator', None)
|
||||||
|
|
||||||
@ -1268,8 +1266,8 @@ class MinimalHelpCommand(HelpCommand):
|
|||||||
"""
|
"""
|
||||||
command_name = self.invoked_with
|
command_name = self.invoked_with
|
||||||
return (
|
return (
|
||||||
f"Use `{self.context.clean_prefix}{command_name} [command]` for more info on a command.\n"
|
f'Use `{self.context.clean_prefix}{command_name} [command]` for more info on a command.\n'
|
||||||
f"You can also use `{self.context.clean_prefix}{command_name} [category]` for more info on a category."
|
f'You can also use `{self.context.clean_prefix}{command_name} [category]` for more info on a category.'
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_command_signature(self, command: Command[Any, ..., Any], /) -> str:
|
def get_command_signature(self, command: Command[Any, ..., Any], /) -> str:
|
||||||
|
246
discord/ext/commands/parameters.py
Normal file
246
discord/ext/commands/parameters.py
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
"""
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
from operator import attrgetter
|
||||||
|
from typing import TYPE_CHECKING, Any, Literal, Optional, OrderedDict, Union
|
||||||
|
|
||||||
|
from discord.utils import MISSING, maybe_coroutine
|
||||||
|
|
||||||
|
from . import converter
|
||||||
|
from .errors import MissingRequiredArgument
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
from discord import Guild, Member, TextChannel, User
|
||||||
|
|
||||||
|
from .context import Context
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'Parameter',
|
||||||
|
'parameter',
|
||||||
|
'param',
|
||||||
|
'Author',
|
||||||
|
'CurrentChannel',
|
||||||
|
'CurrentGuild',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ParamKinds = Union[
|
||||||
|
Literal[inspect.Parameter.POSITIONAL_ONLY],
|
||||||
|
Literal[inspect.Parameter.POSITIONAL_OR_KEYWORD],
|
||||||
|
Literal[inspect.Parameter.VAR_POSITIONAL],
|
||||||
|
Literal[inspect.Parameter.KEYWORD_ONLY],
|
||||||
|
Literal[inspect.Parameter.VAR_KEYWORD],
|
||||||
|
]
|
||||||
|
|
||||||
|
empty: Any = inspect.Parameter.empty
|
||||||
|
|
||||||
|
|
||||||
|
def _gen_property(name: str) -> property:
|
||||||
|
attr = f'_{name}'
|
||||||
|
return property(
|
||||||
|
attrgetter(attr),
|
||||||
|
lambda self, value: setattr(self, attr, value),
|
||||||
|
doc="The parameter's {name}.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Parameter(inspect.Parameter):
|
||||||
|
r"""A class that stores information on a :class:`Command`\'s parameter.
|
||||||
|
This is a subclass of :class:`inspect.Parameter`.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('_displayed_default',)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
kind: ParamKinds,
|
||||||
|
default: Any = empty,
|
||||||
|
annotation: Any = empty,
|
||||||
|
displayed_default: str = empty,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(name=name, kind=kind, default=default, annotation=annotation)
|
||||||
|
self._name = name
|
||||||
|
self._kind = kind
|
||||||
|
self._default = default
|
||||||
|
self._annotation = annotation
|
||||||
|
self._displayed_default = displayed_default
|
||||||
|
|
||||||
|
def replace(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
name: str = MISSING, # MISSING here cause empty is valid
|
||||||
|
kind: ParamKinds = MISSING,
|
||||||
|
default: Any = MISSING,
|
||||||
|
annotation: Any = MISSING,
|
||||||
|
displayed_default: Any = MISSING,
|
||||||
|
) -> Self:
|
||||||
|
if name is MISSING:
|
||||||
|
name = self._name
|
||||||
|
if kind is MISSING:
|
||||||
|
kind = self._kind # type: ignore # this assignment is actually safe
|
||||||
|
if default is MISSING:
|
||||||
|
default = self._default
|
||||||
|
if annotation is MISSING:
|
||||||
|
annotation = self._annotation
|
||||||
|
if displayed_default is MISSING:
|
||||||
|
displayed_default = self._displayed_default
|
||||||
|
|
||||||
|
return self.__class__(
|
||||||
|
name=name,
|
||||||
|
kind=kind,
|
||||||
|
default=default,
|
||||||
|
annotation=annotation,
|
||||||
|
displayed_default=displayed_default,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not TYPE_CHECKING: # this is to prevent anything breaking if inspect internals change
|
||||||
|
name = _gen_property('name')
|
||||||
|
kind = _gen_property('kind')
|
||||||
|
default = _gen_property('default')
|
||||||
|
annotation = _gen_property('annotation')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def required(self) -> bool:
|
||||||
|
""":class:`bool`: Whether this parameter is required."""
|
||||||
|
return self.default is empty
|
||||||
|
|
||||||
|
@property
|
||||||
|
def converter(self) -> Any:
|
||||||
|
"""The converter that should be used for this parameter."""
|
||||||
|
if self.annotation is empty:
|
||||||
|
return type(self.default) if self.default not in (empty, None) else str
|
||||||
|
|
||||||
|
return self.annotation
|
||||||
|
|
||||||
|
@property
|
||||||
|
def displayed_default(self) -> Optional[str]:
|
||||||
|
"""Optional[:class:`str`]: The displayed default in :class:`Command.signature`."""
|
||||||
|
if self._displayed_default is not empty:
|
||||||
|
return self._displayed_default
|
||||||
|
|
||||||
|
return None if self.required else str(self.default)
|
||||||
|
|
||||||
|
async def get_default(self, ctx: Context) -> Any:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Gets this parameter's default value.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
ctx: :class:`Context`
|
||||||
|
The invocation context that is used to get the default argument.
|
||||||
|
"""
|
||||||
|
# pre-condition: required is False
|
||||||
|
if callable(self.default):
|
||||||
|
return await maybe_coroutine(self.default, ctx) # type: ignore
|
||||||
|
return self.default
|
||||||
|
|
||||||
|
|
||||||
|
def parameter(
|
||||||
|
*,
|
||||||
|
converter: Any = empty,
|
||||||
|
default: Any = empty,
|
||||||
|
displayed_default: str = empty,
|
||||||
|
) -> Any:
|
||||||
|
r"""parameter(\*, converter=..., default=..., displayed_default=...)
|
||||||
|
|
||||||
|
A way to assign custom metadata for a :class:`Command`\'s parameter.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
A custom default can be used to have late binding behaviour.
|
||||||
|
|
||||||
|
.. code-block:: python3
|
||||||
|
|
||||||
|
@bot.command()
|
||||||
|
async def wave(to: discord.User = commands.parameter(default=lambda ctx: ctx.author)):
|
||||||
|
await ctx.send(f'Hello {to.mention} :wave:')
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
converter: Any
|
||||||
|
The converter to use for this parameter, this replaces the annotation at runtime which is transparent to type checkers.
|
||||||
|
default: Any
|
||||||
|
The default value for the parameter, if this is a :term:`callable` or a |coroutine_link|_ it is called with a
|
||||||
|
positional :class:`Context` argument.
|
||||||
|
displayed_default: :class:`str`
|
||||||
|
The displayed default in :attr:`Command.signature`.
|
||||||
|
"""
|
||||||
|
return Parameter(
|
||||||
|
name='empty',
|
||||||
|
kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
||||||
|
annotation=converter,
|
||||||
|
default=default,
|
||||||
|
displayed_default=displayed_default,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
param = parameter
|
||||||
|
r"""param(\*, converter=..., default=..., displayed_default=...)
|
||||||
|
|
||||||
|
An alias for :func:`parameter`.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
# some handy defaults
|
||||||
|
Author: Union[Member, User] = parameter(
|
||||||
|
default=attrgetter('author'),
|
||||||
|
displayed_default='<you>',
|
||||||
|
converter=Union[converter.MemberConverter, converter.UserConverter],
|
||||||
|
)
|
||||||
|
|
||||||
|
CurrentChannel: TextChannel = parameter(
|
||||||
|
default=attrgetter('channel'),
|
||||||
|
displayed_default='<this channel>',
|
||||||
|
converter=converter.TextChannelConverter,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def default_guild(ctx: Context) -> Guild:
|
||||||
|
if ctx.guild is not None:
|
||||||
|
return ctx.guild
|
||||||
|
raise MissingRequiredArgument(ctx.current_parameter) # type: ignore # this is never going to be None
|
||||||
|
|
||||||
|
|
||||||
|
CurrentGuild: Guild = parameter(
|
||||||
|
default=default_guild,
|
||||||
|
displayed_default='<this server>',
|
||||||
|
converter=converter.GuildConverter,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Signature(inspect.Signature):
|
||||||
|
_parameter_cls = Parameter
|
||||||
|
parameters: OrderedDict[str, Parameter]
|
@ -429,6 +429,35 @@ Flag Converter
|
|||||||
|
|
||||||
.. autofunction:: discord.ext.commands.flag
|
.. autofunction:: discord.ext.commands.flag
|
||||||
|
|
||||||
|
|
||||||
|
Defaults
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. autoclass:: discord.ext.commands.Parameter()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autofunction:: discord.ext.commands.parameter
|
||||||
|
|
||||||
|
.. autofunction:: discord.ext.commands.param
|
||||||
|
|
||||||
|
.. data:: discord.ext.commands.Author
|
||||||
|
|
||||||
|
A default :class:`.Parameter` which returns the :attr:`~.Context.author` for this context.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. data:: discord.ext.commands.CurrentChannel
|
||||||
|
|
||||||
|
A default :class:`.Parameter` which returns the :attr:`~.Context.channel` for this context.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. data:: discord.ext.commands.CurrentGuild
|
||||||
|
|
||||||
|
A default :class:`.Parameter` which returns the :attr:`~.Context.guild` for this context. This will never be ``None``.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
.. _ext_commands_api_errors:
|
.. _ext_commands_api_errors:
|
||||||
|
|
||||||
Exceptions
|
Exceptions
|
||||||
|
@ -768,6 +768,58 @@ A :class:`dict` annotation is functionally equivalent to ``List[Tuple[K, V]]`` e
|
|||||||
given as a :class:`dict` rather than a :class:`list`.
|
given as a :class:`dict` rather than a :class:`list`.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ext_commands_parameter:
|
||||||
|
|
||||||
|
Parameter Metadata
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
:func:`~ext.commands.parameter` assigns custom metadata to a :class:`~ext.commands.Command`'s parameter.
|
||||||
|
|
||||||
|
This is useful for:
|
||||||
|
|
||||||
|
- Custom converters as annotating a parameter with a custom converter works at runtime, type checkers don't like it
|
||||||
|
because they can't understand what's going on.
|
||||||
|
|
||||||
|
.. code-block:: python3
|
||||||
|
|
||||||
|
class SomeType:
|
||||||
|
foo: int
|
||||||
|
|
||||||
|
class MyVeryCoolConverter(commands.Converter[SomeType]):
|
||||||
|
... # implementation left as an exercise for the reader
|
||||||
|
|
||||||
|
@bot.command()
|
||||||
|
async def bar(ctx, cool_value: MyVeryCoolConverter):
|
||||||
|
cool_value.foo # type checker warns MyVeryCoolConverter has no value foo (uh-oh)
|
||||||
|
|
||||||
|
However, fear not we can use :func:`~ext.commands.parameter` to tell type checkers what's going on.
|
||||||
|
|
||||||
|
.. code-block:: python3
|
||||||
|
|
||||||
|
@bot.command()
|
||||||
|
async def bar(ctx, cool_value: SomeType = commands.parameter(converter=MyVeryCoolConverter)):
|
||||||
|
cool_value.foo # no error (hurray)
|
||||||
|
|
||||||
|
- Late binding behaviour
|
||||||
|
|
||||||
|
.. code-block:: python3
|
||||||
|
|
||||||
|
@bot.command()
|
||||||
|
async def wave(to: discord.User = commands.parameter(default=lambda ctx: ctx.author)):
|
||||||
|
await ctx.send(f'Hello {to.mention} :wave:')
|
||||||
|
|
||||||
|
Because this is such a common use-case, the library provides :obj:`~.ext.commands.Author`, :obj:`~.ext.commands.CurrentChannel` and
|
||||||
|
:obj:`~.ext.commands.CurrentGuild`, armed with this we can simplify ``wave`` to:
|
||||||
|
|
||||||
|
.. code-block:: python3
|
||||||
|
|
||||||
|
@bot.command()
|
||||||
|
async def wave(to: discord.User = commands.Author):
|
||||||
|
await ctx.send(f'Hello {to.mention} :wave:')
|
||||||
|
|
||||||
|
:obj:`~.ext.commands.Author` and co also have other benefits like having the displayed default being filled.
|
||||||
|
|
||||||
|
|
||||||
.. _ext_commands_error_handler:
|
.. _ext_commands_error_handler:
|
||||||
|
|
||||||
Error Handling
|
Error Handling
|
||||||
|
@ -1422,6 +1422,7 @@ Miscellaneous Changes
|
|||||||
- ``BotMissingPermissions.missing_perms`` has been renamed to :attr:`ext.commands.BotMissingPermissions.missing_permissions`.
|
- ``BotMissingPermissions.missing_perms`` has been renamed to :attr:`ext.commands.BotMissingPermissions.missing_permissions`.
|
||||||
- :meth:`ext.commands.Cog.cog_load` has been added as part of the :ref:`migrating_2_0_commands_extension_cog_async` changes.
|
- :meth:`ext.commands.Cog.cog_load` has been added as part of the :ref:`migrating_2_0_commands_extension_cog_async` changes.
|
||||||
- :meth:`ext.commands.Cog.cog_unload` may now be a :term:`coroutine` due to the :ref:`migrating_2_0_commands_extension_cog_async` changes.
|
- :meth:`ext.commands.Cog.cog_unload` may now be a :term:`coroutine` due to the :ref:`migrating_2_0_commands_extension_cog_async` changes.
|
||||||
|
- :attr:`ext.commands.Command.clean_params` type now uses a custom :class:`inspect.Parameter` to handle defaults.
|
||||||
|
|
||||||
.. _migrating_2_0_tasks:
|
.. _migrating_2_0_tasks:
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user