[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 .core import GroupMixin, Command | ||||||
| from .view import StringView | from .view import StringView | ||||||
| from .context import Context | from .context import Context | ||||||
| from .errors import CommandNotFound, CommandError | from . import errors | ||||||
| from .help import HelpCommand, DefaultHelpCommand | from .help import HelpCommand, DefaultHelpCommand | ||||||
| from .cog import Cog | from .cog import Cog | ||||||
|  |  | ||||||
| @@ -571,14 +571,14 @@ class BotBase(GroupMixin): | |||||||
|             setup = getattr(lib, 'setup') |             setup = getattr(lib, 'setup') | ||||||
|         except AttributeError: |         except AttributeError: | ||||||
|             del sys.modules[key] |             del sys.modules[key] | ||||||
|             raise discord.ClientException('extension {!r} ({!r}) does not have a setup function.'.format(key, lib)) |             raise errors.NoEntryPointError(key) | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             setup(self) |             setup(self) | ||||||
|         except Exception: |         except Exception as e: | ||||||
|             self._remove_module_references(lib.__name__) |             self._remove_module_references(lib.__name__) | ||||||
|             self._call_module_finalizers(lib, key) |             self._call_module_finalizers(lib, key) | ||||||
|             raise |             raise errors.ExtensionFailed(key, e) from e | ||||||
|         else: |         else: | ||||||
|             self._extensions[key] = lib |             self._extensions[key] = lib | ||||||
|  |  | ||||||
| @@ -601,20 +601,25 @@ class BotBase(GroupMixin): | |||||||
|  |  | ||||||
|         Raises |         Raises | ||||||
|         -------- |         -------- | ||||||
|         ClientException |         ExtensionNotFound | ||||||
|             The extension does not have a setup function. |  | ||||||
|         ImportError |  | ||||||
|             The extension could not be imported. |             The extension could not be imported. | ||||||
|         Exception |         ExtensionAlreadyLoaded | ||||||
|             Any other exception raised by the extension will be raised back |             The extension is already loaded. | ||||||
|             to the caller. |         NoEntryPointError | ||||||
|  |             The extension does not have a setup function. | ||||||
|  |         ExtensionFailed | ||||||
|  |             The extension setup function had an execution error. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         if name in self._extensions: |         if name in self._extensions: | ||||||
|             return |             raise errors.ExtensionAlreadyLoaded(name) | ||||||
|  |  | ||||||
|         lib = importlib.import_module(name) |         try: | ||||||
|         self._load_from_module_spec(lib, name) |             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): |     def unload_extension(self, name): | ||||||
|         """Unloads an extension. |         """Unloads an extension. | ||||||
| @@ -633,11 +638,16 @@ class BotBase(GroupMixin): | |||||||
|             The extension name to unload. It must be dot separated like |             The extension name to unload. It must be dot separated like | ||||||
|             regular Python imports if accessing a sub-module. e.g. |             regular Python imports if accessing a sub-module. e.g. | ||||||
|             ``foo.test`` if you want to import ``foo/test.py``. |             ``foo.test`` if you want to import ``foo/test.py``. | ||||||
|  |  | ||||||
|  |         Raises | ||||||
|  |         ------- | ||||||
|  |         ExtensionNotLoaded | ||||||
|  |             The extension was not loaded. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         lib = self._extensions.get(name) |         lib = self._extensions.get(name) | ||||||
|         if lib is None: |         if lib is None: | ||||||
|             return |             raise errors.ExtensionNotLoaded(name) | ||||||
|  |  | ||||||
|         self._remove_module_references(lib.__name__) |         self._remove_module_references(lib.__name__) | ||||||
|         self._call_module_finalizers(lib, name) |         self._call_module_finalizers(lib, name) | ||||||
| @@ -659,14 +669,19 @@ class BotBase(GroupMixin): | |||||||
|  |  | ||||||
|         Raises |         Raises | ||||||
|         ------- |         ------- | ||||||
|         Exception |         ExtensionNotLoaded | ||||||
|             Any exception raised by the extension will be raised back |             The extension was not loaded. | ||||||
|             to the caller. |         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) |         lib = self._extensions.get(name) | ||||||
|         if lib is None: |         if lib is None: | ||||||
|             return |             raise errors.ExtensionNotLoaded(name) | ||||||
|  |  | ||||||
|         # get the previous module states from sys modules |         # get the previous module states from sys modules | ||||||
|         modules = { |         modules = { | ||||||
| @@ -843,12 +858,12 @@ class BotBase(GroupMixin): | |||||||
|             try: |             try: | ||||||
|                 if await self.can_run(ctx, call_once=True): |                 if await self.can_run(ctx, call_once=True): | ||||||
|                     await ctx.command.invoke(ctx) |                     await ctx.command.invoke(ctx) | ||||||
|             except CommandError as exc: |             except errors.CommandError as exc: | ||||||
|                 await ctx.command.dispatch_error(ctx, exc) |                 await ctx.command.dispatch_error(ctx, exc) | ||||||
|             else: |             else: | ||||||
|                 self.dispatch('command_completion', ctx) |                 self.dispatch('command_completion', ctx) | ||||||
|         elif ctx.invoked_with: |         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) |             self.dispatch('command_error', ctx, exc) | ||||||
|  |  | ||||||
|     async def process_commands(self, message): |     async def process_commands(self, message): | ||||||
|   | |||||||
| @@ -34,7 +34,9 @@ __all__ = ['CommandError', 'MissingRequiredArgument', 'BadArgument', | |||||||
|            'MissingPermissions', 'BotMissingPermissions', 'ConversionError', |            'MissingPermissions', 'BotMissingPermissions', 'ConversionError', | ||||||
|            'BadUnionArgument', 'ArgumentParsingError', |            'BadUnionArgument', 'ArgumentParsingError', | ||||||
|            'UnexpectedQuoteError', 'InvalidEndOfQuotedStringError', |            'UnexpectedQuoteError', 'InvalidEndOfQuotedStringError', | ||||||
|            'ExpectedClosingQuoteError', ] |            'ExpectedClosingQuoteError', 'ExtensionError', 'ExtensionAlreadyLoaded', | ||||||
|  |            'ExtensionNotLoaded', 'NoEntryPointError', 'ExtensionFailed', | ||||||
|  |            'ExtensionNotFound' ] | ||||||
|  |  | ||||||
| class CommandError(DiscordException): | class CommandError(DiscordException): | ||||||
|     r"""The base exception type for all command related errors. |     r"""The base exception type for all command related errors. | ||||||
| @@ -283,3 +285,80 @@ class ExpectedClosingQuoteError(ArgumentParsingError): | |||||||
|     def __init__(self, close_quote): |     def __init__(self, close_quote): | ||||||
|         self.close_quote = close_quote |         self.close_quote = close_quote | ||||||
|         super().__init__('Expected closing {}.'.format(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) | ||||||
|   | |||||||
| @@ -285,6 +285,25 @@ Exceptions | |||||||
| .. autoexception:: discord.ext.commands.BotMissingPermissions | .. autoexception:: discord.ext.commands.BotMissingPermissions | ||||||
|     :members: |     :members: | ||||||
|  |  | ||||||
|  | .. autoexception:: discord.ext.commands.ExtensionError | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | .. autoexception:: discord.ext.commands.ExtensionAlreadyLoaded | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | .. autoexception:: discord.ext.commands.ExtensionNotLoaded | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | .. autoexception:: discord.ext.commands.NoEntryPointError | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | .. autoexception:: discord.ext.commands.ExtensionFailed | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | .. autoexception:: discord.ext.commands.ExtensionNotFound | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  |  | ||||||
| Exception Hierarchy | Exception Hierarchy | ||||||
| +++++++++++++++++++++ | +++++++++++++++++++++ | ||||||
|  |  | ||||||
| @@ -311,3 +330,9 @@ Exception Hierarchy | |||||||
|             - :exc:`~.commands.DisabledCommand` |             - :exc:`~.commands.DisabledCommand` | ||||||
|             - :exc:`~.commands.CommandInvokeError` |             - :exc:`~.commands.CommandInvokeError` | ||||||
|             - :exc:`~.commands.CommandOnCooldown` |             - :exc:`~.commands.CommandOnCooldown` | ||||||
|  |         - :exc:`~.commands.ExtensionError` | ||||||
|  |             - :exc:`~.commands.ExtensionAlreadyLoaded` | ||||||
|  |             - :exc:`~.commands.ExtensionNotLoaded` | ||||||
|  |             - :exc:`~.commands.NoEntryPointError` | ||||||
|  |             - :exc:`~.commands.ExtensionFailed` | ||||||
|  |             - :exc:`~.commands.ExtensionNotFound` | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user