From e5d65b94006810b18c2e7509b3160e48ba0d8e8f Mon Sep 17 00:00:00 2001 From: Zomatree Date: Sat, 13 Nov 2021 23:53:21 +0000 Subject: [PATCH] add Literal converter and rejig command client --- .gitignore | 1 + docs/ext/commands/api.rst | 69 +++++++++++++++++++-- examples/commands.py | 2 +- revolt/ext/commands/__init__.py | 2 +- revolt/ext/commands/{mixin.py => client.py} | 12 ++-- revolt/ext/commands/command.py | 12 +++- revolt/ext/commands/context.py | 8 ++- revolt/ext/commands/converters.py | 7 +-- revolt/ext/commands/errors.py | 39 ++++++++++-- 9 files changed, 126 insertions(+), 26 deletions(-) rename revolt/ext/commands/{mixin.py => client.py} (97%) diff --git a/.gitignore b/.gitignore index 59c4f29..3f7ca08 100755 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ dist docs/_build .vscode .env +.mypy_cache diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst index 1a6151f..efcea31 100755 --- a/docs/ext/commands/api.rst +++ b/docs/ext/commands/api.rst @@ -4,9 +4,9 @@ API Reference =============== -CommandsMixin -~~~~~~~~~~~~~ -.. autoclass:: revolt.ext.commands.CommandsMixin +CommandsClient +~~~~~~~~~~~~~~~ +.. autoclass:: revolt.ext.commands.CommandsClient :members: Context @@ -19,11 +19,72 @@ Command .. autoclass:: revolt.ext.commands.Command :members: +command +~~~~~~~~ +.. autodecorator:: revolt.ext.commands.command + +check +~~~~~~ +.. autodecorator:: revolt.ext.commands.check + +is_bot_owner +~~~~~~~~~~~~~ +.. autodecorator:: revolt.ext.commands.is_bot_owner + +is_server_owner +~~~~~~~~~~~~~~~~ +.. autodecorator:: revolt.ext.commands.is_server_owner + Exceptions =========== +CommandError +~~~~~~~~~~~~~ +.. autoexception:: revolt.ext.commands.CommandError + :members: + CommandNotFound ~~~~~~~~~~~~~~~~ -.. autoclass:: revolt.ext.commands.CommandNotFound +.. autoexception:: revolt.ext.commands.CommandNotFound + :members: + +NoClosingQuote +~~~~~~~~~~~~~~~ +.. autoexception:: revolt.ext.commands.NoClosingQuote + :members: + +CheckError +~~~~~~~~~~~ +.. autoexception:: revolt.ext.commands.CheckError + :members: + +NotBotOwner +~~~~~~~~~~~~ +.. autoexception:: revolt.ext.commands.NotBotOwner + :members: + +NotServerOwner +~~~~~~~~~~~~~~~ +.. autoexception:: revolt.ext.commands.NotServerOwner + :members: + +ServerOnly +~~~~~~~~~~~ +.. autoexception:: revolt.ext.commands.ServerOnly + :members: + +ConverterError +~~~~~~~~~~~~~~~ +.. autoexception:: revolt.ext.commands.ConverterError + :members: + +InvalidLiteralArgument +~~~~~~~~~~~~~~~~~~~~~~~ +.. autoexception:: revolt.ext.commands.InvalidLiteralArgument + :members: + +BadBoolArgument +~~~~~~~~~~~~~~~~ +.. autoexception:: revolt.ext.commands.BadBoolArgument :members: diff --git a/examples/commands.py b/examples/commands.py index d521c11..b3f29dd 100755 --- a/examples/commands.py +++ b/examples/commands.py @@ -6,7 +6,7 @@ import revolt from revolt.ext import commands -class Client(commands.CommandsMixin, revolt.Client): +class Client(commands.CommandsClient): async def get_prefix(self, message: revolt.Message): return "!" diff --git a/revolt/ext/commands/__init__.py b/revolt/ext/commands/__init__.py index 34969ac..8d81849 100755 --- a/revolt/ext/commands/__init__.py +++ b/revolt/ext/commands/__init__.py @@ -1,5 +1,5 @@ from .command import * from .context import * from .errors import * -from .mixin import * +from .client import * from .checks import * diff --git a/revolt/ext/commands/mixin.py b/revolt/ext/commands/client.py similarity index 97% rename from revolt/ext/commands/mixin.py rename to revolt/ext/commands/client.py index cb63779..432d680 100755 --- a/revolt/ext/commands/mixin.py +++ b/revolt/ext/commands/client.py @@ -14,7 +14,7 @@ from .errors import CommandNotFound, CheckError __all__ = ( "CommandsMeta", - "CommandsMixin" + "CommandsClient" ) quote_regex = re.compile(r"[\"']") @@ -40,11 +40,10 @@ class CommandsMeta(type): return self -class CommandsMixin(metaclass=CommandsMeta): +class CommandsClient(revolt.Client, metaclass=CommandsMeta): """Main class that adds commands, this class should be subclassed along with `revolt.Client`.""" _commands: list[Command] - dispatch: Callable[..., None] def __init__(self, *args, **kwargs): self.all_commands: dict[str, Command] = {} @@ -142,7 +141,7 @@ class CommandsMixin(metaclass=CommandsMeta): if not content: return - view = StringView(content) + view = self.get_view(message)(content) try: command_name = view.get_next_word() @@ -154,10 +153,10 @@ class CommandsMixin(metaclass=CommandsMeta): try: command = self.get_command(command_name) except KeyError: - context = context_cls(None, command_name, view, message) + context = context_cls(None, command_name, view, message, self) return self.dispatch("command_error", context, CommandNotFound(command_name)) - context = context_cls(command, command_name, view, message) + context = context_cls(command, command_name, view, message, self) try: self.dispatch("command", context) @@ -175,7 +174,6 @@ class CommandsMixin(metaclass=CommandsMeta): except Exception as e: self.dispatch("command_error", context, e) - @staticmethod async def on_command_error(ctx: Context, error: Exception): traceback.print_exception(type(error), error, error.__traceback__) diff --git a/revolt/ext/commands/command.py b/revolt/ext/commands/command.py index 214727c..2c31375 100755 --- a/revolt/ext/commands/command.py +++ b/revolt/ext/commands/command.py @@ -1,12 +1,13 @@ from __future__ import annotations import traceback -from typing import TYPE_CHECKING, Annotated, Any, Callable, Coroutine, Optional, Union, get_args, get_origin +from typing import TYPE_CHECKING, Annotated, Any, Callable, Coroutine, Literal, Optional, Union, get_args, get_origin import revolt import inspect from contextlib import suppress from revolt.utils import copy_doc, maybe_coroutine +from .errors import InvalidLiteralArgument if TYPE_CHECKING: from .context import Context @@ -111,6 +112,15 @@ class Command: elif origin is Annotated: converter: Callable[[str, Context], Any] = get_args(annot)[1] # the typehint affects the other if statement somehow return await maybe_coroutine(converter, arg, context) + + elif origin is Literal: + if arg in get_args(annot): + return arg + else: + raise InvalidLiteralArgument(arg) + else: + annot: Callable[..., Any] + return await maybe_coroutine(annot, arg, context) else: return arg diff --git a/revolt/ext/commands/context.py b/revolt/ext/commands/context.py index b16d8c5..f5945af 100755 --- a/revolt/ext/commands/context.py +++ b/revolt/ext/commands/context.py @@ -9,6 +9,7 @@ from .command import Command if TYPE_CHECKING: from .view import StringView + from .client import CommandsClient __all__ = ( "Context", @@ -35,17 +36,20 @@ class Context(revolt.Messageable): The positional arguments being passed to the command kwargs: dict[:class:`str`, Any] The keyword arguments being passed to the command + client: :class:`CommandsClient` + The revolt client """ - __slots__ = ("command", "invoked_with", "args", "message", "server", "channel", "author", "view", "kwargs", "state") + __slots__ = ("command", "invoked_with", "args", "message", "server", "channel", "author", "view", "kwargs", "state", "client") def _get_channel_id(self) -> str: return self.channel.id - def __init__(self, command: Optional[Command], invoked_with: str, view: StringView, message: revolt.Message): + def __init__(self, command: Optional[Command], invoked_with: str, view: StringView, message: revolt.Message, client: CommandsClient): self.command = command self.invoked_with = invoked_with self.view = view self.message = message + self.client = client self.args = [] self.kwargs = {} self.server = message.server diff --git a/revolt/ext/commands/converters.py b/revolt/ext/commands/converters.py index 10ec929..2318bbc 100755 --- a/revolt/ext/commands/converters.py +++ b/revolt/ext/commands/converters.py @@ -1,10 +1,5 @@ from typing import Annotated - -class ConverterError(Exception): - """Raised when a converter fails""" - -class BadBoolArgument(ConverterError): - """Raised when the bool converter fails""" +from .errors import BadBoolArgument IntConverter = Annotated[int, lambda arg, _: int(arg)] diff --git a/revolt/ext/commands/errors.py b/revolt/ext/commands/errors.py index fc4f74b..dd5415a 100755 --- a/revolt/ext/commands/errors.py +++ b/revolt/ext/commands/errors.py @@ -1,9 +1,22 @@ +from revolt import RevoltError + __all__ = ( + "CommandError", "CommandNotFound", - "NoClosingQuote" + "NoClosingQuote", + "CheckError", + "NotBotOwner", + "NotServerOwner", + "ServerOnly", + "ConverterError", + "InvalidLiteralArgument", + "BadBoolArgument" ) -class CommandNotFound(Exception): +class CommandError(RevoltError): + """base error for all command's related errors""" + +class CommandNotFound(CommandError): """Raised when a command isnt found. Parameters @@ -16,8 +29,26 @@ class CommandNotFound(Exception): def __init__(self, command_name: str): self.command_name = command_name -class NoClosingQuote(Exception): +class NoClosingQuote(CommandError): """Raised when there is no closing quote for a command argument""" -class CheckError(Exception): +class CheckError(CommandError): """Raised when a check fails for a command""" + +class NotBotOwner(CheckError): + """Raised when the `is_bot_owner` check fails""" + +class NotServerOwner(CheckError): + """Raised when the `is_server_owner` check fails""" + +class ServerOnly(CheckError): + """Raised when a check requires the command to be ran in a server""" + +class ConverterError(CommandError): + """Base class for all converter errors""" + +class InvalidLiteralArgument(ConverterError): + """Raised when the argument is not a valid literal argument""" + +class BadBoolArgument(ConverterError): + """Raised when the bool converter fails"""