[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:
parent
26e9b5bfac
commit
d9e54d7dd3
@ -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`
|
||||||
|
Loading…
x
Reference in New Issue
Block a user