[commands] Redesign extension exception flow.
Instead of raising a whole variety of exceptions, they are now wrapped into ExtensionError derived classes. * ExtensionAlreadyLoaded * Raised when an extension is already loaded in Bot.load_extension * ExtensionNotLoaded * Raised when an extension is not loaded, e.g. Bot.unload_extension * NoEntryPointError * Raised when an extension does not have a `setup` function. * ExtensionFailed * Raised when an extension's `setup` function fails. * ExtensionNotFound * Raised when an extension's module import fails.
This commit is contained in:
		| @@ -38,7 +38,7 @@ import discord | ||||
| from .core import GroupMixin, Command | ||||
| from .view import StringView | ||||
| from .context import Context | ||||
| from .errors import CommandNotFound, CommandError | ||||
| from . import errors | ||||
| from .help import HelpCommand, DefaultHelpCommand | ||||
| from .cog import Cog | ||||
|  | ||||
| @@ -571,14 +571,14 @@ class BotBase(GroupMixin): | ||||
|             setup = getattr(lib, 'setup') | ||||
|         except AttributeError: | ||||
|             del sys.modules[key] | ||||
|             raise discord.ClientException('extension {!r} ({!r}) does not have a setup function.'.format(key, lib)) | ||||
|             raise errors.NoEntryPointError(key) | ||||
|  | ||||
|         try: | ||||
|             setup(self) | ||||
|         except Exception: | ||||
|         except Exception as e: | ||||
|             self._remove_module_references(lib.__name__) | ||||
|             self._call_module_finalizers(lib, key) | ||||
|             raise | ||||
|             raise errors.ExtensionFailed(key, e) from e | ||||
|         else: | ||||
|             self._extensions[key] = lib | ||||
|  | ||||
| @@ -601,20 +601,25 @@ class BotBase(GroupMixin): | ||||
|  | ||||
|         Raises | ||||
|         -------- | ||||
|         ClientException | ||||
|             The extension does not have a setup function. | ||||
|         ImportError | ||||
|         ExtensionNotFound | ||||
|             The extension could not be imported. | ||||
|         Exception | ||||
|             Any other exception raised by the extension will be raised back | ||||
|             to the caller. | ||||
|         ExtensionAlreadyLoaded | ||||
|             The extension is already loaded. | ||||
|         NoEntryPointError | ||||
|             The extension does not have a setup function. | ||||
|         ExtensionFailed | ||||
|             The extension setup function had an execution error. | ||||
|         """ | ||||
|  | ||||
|         if name in self._extensions: | ||||
|             return | ||||
|             raise errors.ExtensionAlreadyLoaded(name) | ||||
|  | ||||
|         lib = importlib.import_module(name) | ||||
|         self._load_from_module_spec(lib, name) | ||||
|         try: | ||||
|             lib = importlib.import_module(name) | ||||
|         except ImportError as e: | ||||
|             raise errors.ExtensionNotFound(name, e) from e | ||||
|         else: | ||||
|             self._load_from_module_spec(lib, name) | ||||
|  | ||||
|     def unload_extension(self, name): | ||||
|         """Unloads an extension. | ||||
| @@ -633,11 +638,16 @@ class BotBase(GroupMixin): | ||||
|             The extension name to unload. It must be dot separated like | ||||
|             regular Python imports if accessing a sub-module. e.g. | ||||
|             ``foo.test`` if you want to import ``foo/test.py``. | ||||
|  | ||||
|         Raises | ||||
|         ------- | ||||
|         ExtensionNotLoaded | ||||
|             The extension was not loaded. | ||||
|         """ | ||||
|  | ||||
|         lib = self._extensions.get(name) | ||||
|         if lib is None: | ||||
|             return | ||||
|             raise errors.ExtensionNotLoaded(name) | ||||
|  | ||||
|         self._remove_module_references(lib.__name__) | ||||
|         self._call_module_finalizers(lib, name) | ||||
| @@ -659,14 +669,19 @@ class BotBase(GroupMixin): | ||||
|  | ||||
|         Raises | ||||
|         ------- | ||||
|         Exception | ||||
|             Any exception raised by the extension will be raised back | ||||
|             to the caller. | ||||
|         ExtensionNotLoaded | ||||
|             The extension was not loaded. | ||||
|         ExtensionNotFound | ||||
|             The extension could not be imported. | ||||
|         NoEntryPointError | ||||
|             The extension does not have a setup function. | ||||
|         ExtensionFailed | ||||
|             The extension setup function had an execution error. | ||||
|         """ | ||||
|  | ||||
|         lib = self._extensions.get(name) | ||||
|         if lib is None: | ||||
|             return | ||||
|             raise errors.ExtensionNotLoaded(name) | ||||
|  | ||||
|         # get the previous module states from sys modules | ||||
|         modules = { | ||||
| @@ -843,12 +858,12 @@ class BotBase(GroupMixin): | ||||
|             try: | ||||
|                 if await self.can_run(ctx, call_once=True): | ||||
|                     await ctx.command.invoke(ctx) | ||||
|             except CommandError as exc: | ||||
|             except errors.CommandError as exc: | ||||
|                 await ctx.command.dispatch_error(ctx, exc) | ||||
|             else: | ||||
|                 self.dispatch('command_completion', ctx) | ||||
|         elif ctx.invoked_with: | ||||
|             exc = CommandNotFound('Command "{}" is not found'.format(ctx.invoked_with)) | ||||
|             exc = errors.CommandNotFound('Command "{}" is not found'.format(ctx.invoked_with)) | ||||
|             self.dispatch('command_error', ctx, exc) | ||||
|  | ||||
|     async def process_commands(self, message): | ||||
|   | ||||
| @@ -34,7 +34,9 @@ __all__ = ['CommandError', 'MissingRequiredArgument', 'BadArgument', | ||||
|            'MissingPermissions', 'BotMissingPermissions', 'ConversionError', | ||||
|            'BadUnionArgument', 'ArgumentParsingError', | ||||
|            'UnexpectedQuoteError', 'InvalidEndOfQuotedStringError', | ||||
|            'ExpectedClosingQuoteError', ] | ||||
|            'ExpectedClosingQuoteError', 'ExtensionError', 'ExtensionAlreadyLoaded', | ||||
|            'ExtensionNotLoaded', 'NoEntryPointError', 'ExtensionFailed', | ||||
|            'ExtensionNotFound' ] | ||||
|  | ||||
| class CommandError(DiscordException): | ||||
|     r"""The base exception type for all command related errors. | ||||
| @@ -283,3 +285,80 @@ class ExpectedClosingQuoteError(ArgumentParsingError): | ||||
|     def __init__(self, close_quote): | ||||
|         self.close_quote = close_quote | ||||
|         super().__init__('Expected closing {}.'.format(close_quote)) | ||||
|  | ||||
| class ExtensionError(DiscordException): | ||||
|     """Base exception for extension related errors. | ||||
|  | ||||
|     This inherits from :exc:`~discord.DiscordException`. | ||||
|  | ||||
|     Parameter | ||||
|     ----------- | ||||
|     name: :class:`str` | ||||
|         The extension that had an error. | ||||
|     """ | ||||
|     def __init__(self, message=None, *args, name): | ||||
|         self.name = name | ||||
|         message = message or 'Extension {!r} had an error.'.format(name) | ||||
|         # clean-up @everyone and @here mentions | ||||
|         m = message.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere') | ||||
|         super().__init__(m, *args) | ||||
|  | ||||
| class ExtensionAlreadyLoaded(ExtensionError): | ||||
|     """An exception raised when an extension has already been loaded. | ||||
|  | ||||
|     This inherits from :exc:`ExtensionError` | ||||
|     """ | ||||
|     def __init__(self, name): | ||||
|         super().__init__('Extension {!r} is already loaded.'.format(name), name=name) | ||||
|  | ||||
| class ExtensionNotLoaded(ExtensionError): | ||||
|     """An exception raised when an extension was not loaded. | ||||
|  | ||||
|     This inherits from :exc:`ExtensionError` | ||||
|     """ | ||||
|     def __init__(self, name): | ||||
|         super().__init__('Extension {!r} has not been loaded.'.format(name), name=name) | ||||
|  | ||||
| class NoEntryPointError(ExtensionError): | ||||
|     """An exception raised when an extension does not have a ``setup`` entry point function. | ||||
|  | ||||
|     This inherits from :exc:`ExtensionError` | ||||
|     """ | ||||
|     def __init__(self, name): | ||||
|         super().__init__("Extension {!r} has no 'setup' function.".format(name), name=name) | ||||
|  | ||||
| class ExtensionFailed(ExtensionError): | ||||
|     """An exception raised when an extension failed to load during execution of the ``setup`` entry point. | ||||
|  | ||||
|     This inherits from :exc:`ExtensionError` | ||||
|  | ||||
|     Attributes | ||||
|     ----------- | ||||
|     name: :class:`str` | ||||
|         The extension that had the error. | ||||
|     original: :exc:`Exception` | ||||
|         The original exception that was raised. You can also get this via | ||||
|         the ``__cause__`` attribute. | ||||
|     """ | ||||
|     def __init__(self, name, original): | ||||
|         self.original = original | ||||
|         fmt = 'Extension {0!r} raised an error: {1.__class__.__name__}: {1}' | ||||
|         super().__init__(fmt.format(name, original), name=name) | ||||
|  | ||||
| class ExtensionNotFound(ExtensionError): | ||||
|     """An exception raised when an extension failed to be imported. | ||||
|  | ||||
|     This inherits from :exc:`ExtensionError` | ||||
|  | ||||
|     Attributes | ||||
|     ----------- | ||||
|     name: :class:`str` | ||||
|         The extension that had the error. | ||||
|     original: :exc:`ImportError` | ||||
|         The original exception that was raised. You can also get this via | ||||
|         the ``__cause__`` attribute. | ||||
|     """ | ||||
|     def __init__(self, name, original): | ||||
|         self.original = original | ||||
|         fmt = 'Extension {0!r} could not be loaded.' | ||||
|         super().__init__(fmt.format(name), name=name) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user