[commands][types] Type hint commands-ext

This commit is contained in:
Josh
2021-08-20 09:51:26 +10:00
committed by GitHub
parent d4c683738d
commit f3cb197429
6 changed files with 635 additions and 311 deletions

View File

@ -21,16 +21,52 @@ 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
import re
from typing import Any, Dict, Generic, List, Optional, TYPE_CHECKING, TypeVar, Union
import discord.abc
import discord.utils
import re
from discord.message import Message
if TYPE_CHECKING:
from typing_extensions import ParamSpec
from discord.abc import MessageableChannel
from discord.guild import Guild
from discord.member import Member
from discord.state import ConnectionState
from discord.user import ClientUser, User
from discord.voice_client import VoiceProtocol
from .bot import Bot, AutoShardedBot
from .cog import Cog
from .core import Command
from .help import HelpCommand
from .view import StringView
__all__ = (
'Context',
)
class Context(discord.abc.Messageable):
MISSING: Any = discord.utils.MISSING
T = TypeVar('T')
BotT = TypeVar('BotT', bound="Union[Bot, AutoShardedBot]")
CogT = TypeVar('CogT', bound="Cog")
if TYPE_CHECKING:
P = ParamSpec('P')
else:
P = TypeVar('P')
class Context(discord.abc.Messageable, Generic[BotT]):
r"""Represents the context in which a command is being invoked under.
This class contains a lot of meta data to help you understand more about
@ -58,11 +94,11 @@ class Context(discord.abc.Messageable):
This is only of use for within converters.
.. versionadded:: 2.0
prefix: :class:`str`
prefix: Optional[:class:`str`]
The prefix that was used to invoke the command.
command: :class:`Command`
command: Optional[:class:`Command`]
The command that is being invoked currently.
invoked_with: :class:`str`
invoked_with: Optional[:class:`str`]
The command name that triggered this invocation. Useful for finding out
which alias called the command.
invoked_parents: List[:class:`str`]
@ -73,7 +109,7 @@ class Context(discord.abc.Messageable):
.. versionadded:: 1.7
invoked_subcommand: :class:`Command`
invoked_subcommand: Optional[:class:`Command`]
The subcommand that was invoked.
If no valid subcommand was invoked then this is equal to ``None``.
subcommand_passed: Optional[:class:`str`]
@ -86,23 +122,38 @@ class Context(discord.abc.Messageable):
or invoked.
"""
def __init__(self, **attrs):
self.message = attrs.pop('message', None)
self.bot = attrs.pop('bot', None)
self.args = attrs.pop('args', [])
self.kwargs = attrs.pop('kwargs', {})
self.prefix = attrs.pop('prefix')
self.command = attrs.pop('command', None)
self.view = attrs.pop('view', None)
self.invoked_with = attrs.pop('invoked_with', None)
self.invoked_parents = attrs.pop('invoked_parents', [])
self.invoked_subcommand = attrs.pop('invoked_subcommand', None)
self.subcommand_passed = attrs.pop('subcommand_passed', None)
self.command_failed = attrs.pop('command_failed', False)
self.current_parameter = attrs.pop('current_parameter', None)
self._state = self.message._state
def __init__(self,
*,
message: Message,
bot: BotT,
view: StringView,
args: List[Any] = MISSING,
kwargs: Dict[str, Any] = MISSING,
prefix: Optional[str] = None,
command: Optional[Command] = None,
invoked_with: Optional[str] = None,
invoked_parents: List[str] = MISSING,
invoked_subcommand: Optional[Command] = None,
subcommand_passed: Optional[str] = None,
command_failed: bool = False,
current_parameter: Optional[inspect.Parameter] = None,
):
self.message: Message = message
self.bot: BotT = bot
self.args: List[Any] = args or []
self.kwargs: Dict[str, Any] = kwargs or {}
self.prefix: Optional[str] = prefix
self.command: Optional[Command] = command
self.view: StringView = view
self.invoked_with: Optional[str] = invoked_with
self.invoked_parents: List[str] = invoked_parents or []
self.invoked_subcommand: Optional[Command] = invoked_subcommand
self.subcommand_passed: Optional[str] = subcommand_passed
self.command_failed: bool = command_failed
self.current_parameter: Optional[inspect.Parameter] = current_parameter
self._state: ConnectionState = self.message._state
async def invoke(self, command, /, *args, **kwargs):
async def invoke(self, command: Command[CogT, P, T], /, *args: P.args, **kwargs: P.kwargs) -> T:
r"""|coro|
Calls a command with the arguments given.
@ -133,17 +184,9 @@ class Context(discord.abc.Messageable):
TypeError
The command argument to invoke is missing.
"""
arguments = []
if command.cog is not None:
arguments.append(command.cog)
return await command(self, *args, **kwargs)
arguments.append(self)
arguments.extend(args)
ret = await command.callback(*arguments, **kwargs)
return ret
async def reinvoke(self, *, call_hooks: bool = False, restart: bool = True):
async def reinvoke(self, *, call_hooks: bool = False, restart: bool = True) -> None:
"""|coro|
Calls the command again.
@ -187,7 +230,7 @@ class Context(discord.abc.Messageable):
if restart:
to_call = cmd.root_parent or cmd
view.index = len(self.prefix)
view.index = len(self.prefix or '')
view.previous = 0
self.invoked_parents = []
self.invoked_with = view.get_word() # advance to get the root command
@ -206,20 +249,23 @@ class Context(discord.abc.Messageable):
self.subcommand_passed = subcommand_passed
@property
def valid(self):
def valid(self) -> bool:
""":class:`bool`: Checks if the invocation context is valid to be invoked with."""
return self.prefix is not None and self.command is not None
async def _get_channel(self):
async def _get_channel(self) -> discord.abc.Messageable:
return self.channel
@property
def clean_prefix(self):
def clean_prefix(self) -> str:
""":class:`str`: The cleaned up invoke prefix. i.e. mentions are ``@name`` instead of ``<@id>``.
.. versionadded:: 2.0
"""
user = self.guild.me if self.guild else self.bot.user
if self.prefix is None:
return ''
user = self.me
# this breaks if the prefix mention is not the bot itself but I
# consider this to be an *incredibly* strange use case. I'd rather go
# for this common use case rather than waste performance for the
@ -228,7 +274,7 @@ class Context(discord.abc.Messageable):
return pattern.sub("@%s" % user.display_name.replace('\\', r'\\'), self.prefix)
@property
def cog(self):
def cog(self) -> Optional[Cog]:
"""Optional[:class:`.Cog`]: Returns the cog associated with this context's command. None if it does not exist."""
if self.command is None:
@ -236,38 +282,39 @@ class Context(discord.abc.Messageable):
return self.command.cog
@discord.utils.cached_property
def guild(self):
def guild(self) -> Optional[Guild]:
"""Optional[:class:`.Guild`]: Returns the guild associated with this context's command. None if not available."""
return self.message.guild
@discord.utils.cached_property
def channel(self):
def channel(self) -> MessageableChannel:
"""Union[:class:`.abc.Messageable`]: Returns the channel associated with this context's command.
Shorthand for :attr:`.Message.channel`.
"""
return self.message.channel
@discord.utils.cached_property
def author(self):
def author(self) -> Union[User, Member]:
"""Union[:class:`~discord.User`, :class:`.Member`]:
Returns the author associated with this context's command. Shorthand for :attr:`.Message.author`
"""
return self.message.author
@discord.utils.cached_property
def me(self):
def me(self) -> Union[Member, ClientUser]:
"""Union[:class:`.Member`, :class:`.ClientUser`]:
Similar to :attr:`.Guild.me` except it may return the :class:`.ClientUser` in private message contexts.
"""
return self.guild.me if self.guild is not None else self.bot.user
# bot.user will never be None at this point.
return self.guild.me if self.guild is not None else self.bot.user # type: ignore
@property
def voice_client(self):
def voice_client(self) -> Optional[VoiceProtocol]:
r"""Optional[:class:`.VoiceProtocol`]: A shortcut to :attr:`.Guild.voice_client`\, if applicable."""
g = self.guild
return g.voice_client if g else None
async def send_help(self, *args):
async def send_help(self, *args: Any) -> Any:
"""send_help(entity=<bot>)
|coro|
@ -319,12 +366,12 @@ class Context(discord.abc.Messageable):
return None
entity = args[0]
if entity is None:
return None
if isinstance(entity, str):
entity = bot.get_cog(entity) or bot.get_command(entity)
if entity is None:
return None
try:
entity.qualified_name
except AttributeError:
@ -348,6 +395,6 @@ class Context(discord.abc.Messageable):
except CommandError as e:
await cmd.on_help_command_error(self, e)
@discord.utils.copy_doc(discord.Message.reply)
async def reply(self, content=None, **kwargs):
@discord.utils.copy_doc(Message.reply)
async def reply(self, content: Optional[str] = None, **kwargs: Any) -> Message:
return await self.message.reply(content, **kwargs)