[Core] Support already loaded packages in [p]load (#2116)

This commit is contained in:
El Laggron 2018-10-07 23:18:28 +02:00 committed by Toby Harradine
parent ee7e8aa782
commit 76bbcf2f8c
4 changed files with 124 additions and 97 deletions

View File

@ -11,10 +11,10 @@ import discord
import sys
from discord.ext.commands import when_mentioned_or
from . import Config, i18n, commands, errors
from .cog_manager import CogManager
from . import Config, i18n, commands
from .rpc import RPCMixin
from .help_formatter import Help, help as help_
from .rpc import RPCMixin
from .sentry import SentryManager
from .utils import common_filters
@ -217,7 +217,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
async def load_extension(self, spec: ModuleSpec):
name = spec.name.split(".")[-1]
if name in self.extensions:
raise discord.ClientException(f"there is already a package named {name} loaded")
raise errors.PackageAlreadyLoaded(spec)
lib = spec.loader.load_module()
if not hasattr(lib, "setup"):

View File

@ -13,7 +13,7 @@ from collections import namedtuple
from pathlib import Path
from random import SystemRandom
from string import ascii_letters, digits
from typing import TYPE_CHECKING, Union
from typing import TYPE_CHECKING, Union, Tuple, List, Optional, Iterable, Sequence, Dict
import aiohttp
import discord
@ -25,6 +25,7 @@ from redbot.core import (
VersionInfo,
checks,
commands,
errors,
i18n,
)
from .utils.predicates import MessagePredicate
@ -59,7 +60,9 @@ class CoreLogic:
self.bot.register_rpc_handler(self._version_info)
self.bot.register_rpc_handler(self._invite_url)
async def _load(self, cog_names: list):
async def _load(
self, cog_names: Iterable[str]
) -> Tuple[List[str], List[str], List[str], List[str]]:
"""
Loads cogs by name.
Parameters
@ -69,11 +72,12 @@ class CoreLogic:
Returns
-------
tuple
3 element tuple of loaded, failed, and not found cogs.
4-tuple of loaded, failed, not found and already loaded cogs.
"""
failed_packages = []
loaded_packages = []
notfound_packages = []
alreadyloaded_packages = []
bot = self.bot
@ -98,6 +102,8 @@ class CoreLogic:
try:
self._cleanup_and_refresh_modules(spec.name)
await bot.load_extension(spec)
except errors.PackageAlreadyLoaded:
alreadyloaded_packages.append(name)
except Exception as e:
log.exception("Package loading failed", exc_info=e)
@ -109,9 +115,10 @@ class CoreLogic:
await bot.add_loaded_package(name)
loaded_packages.append(name)
return loaded_packages, failed_packages, notfound_packages
return loaded_packages, failed_packages, notfound_packages, alreadyloaded_packages
def _cleanup_and_refresh_modules(self, module_name: str):
@staticmethod
def _cleanup_and_refresh_modules(module_name: str) -> None:
"""Interally reloads modules so that changes are detected"""
splitted = module_name.split(".")
@ -123,6 +130,7 @@ class CoreLogic:
else:
importlib._bootstrap._exec(lib.__spec__, lib)
# noinspection PyTypeChecker
modules = itertools.accumulate(splitted, "{}.{}".format)
for m in modules:
maybe_reload(m)
@ -131,7 +139,10 @@ class CoreLogic:
for child_name, lib in children.items():
importlib._bootstrap._exec(lib.__spec__, lib)
def _get_package_strings(self, packages: list, fmt: str, other: tuple = None):
@staticmethod
def _get_package_strings(
packages: List[str], fmt: str, other: Optional[Tuple[str, ...]] = None
) -> str:
"""
Gets the strings needed for the load, unload and reload commands
"""
@ -147,7 +158,7 @@ class CoreLogic:
final_string = fmt.format(**form)
return final_string
async def _unload(self, cog_names: list):
async def _unload(self, cog_names: Iterable[str]) -> Tuple[List[str], List[str]]:
"""
Unloads cogs with the given names.
@ -175,14 +186,16 @@ class CoreLogic:
return unloaded_packages, failed_packages
async def _reload(self, cog_names):
async def _reload(
self, cog_names: Sequence[str]
) -> Tuple[List[str], List[str], List[str], List[str]]:
await self._unload(cog_names)
loaded, load_failed, not_found = await self._load(cog_names)
loaded, load_failed, not_found, already_loaded = await self._load(cog_names)
return loaded, load_failed, not_found
return loaded, load_failed, not_found, already_loaded
async def _name(self, name: str = None):
async def _name(self, name: Optional[str] = None) -> str:
"""
Gets or sets the bot's username.
@ -201,7 +214,7 @@ class CoreLogic:
return self.bot.user.name
async def _prefixes(self, prefixes: list = None):
async def _prefixes(self, prefixes: Optional[Sequence[str]] = None) -> List[str]:
"""
Gets or sets the bot's global prefixes.
@ -220,7 +233,8 @@ class CoreLogic:
await self.bot.db.prefix.set(prefixes)
return await self.bot.db.prefix()
async def _version_info(self):
@classmethod
async def _version_info(cls) -> Dict[str, str]:
"""
Version information for Red and discord.py
@ -231,7 +245,7 @@ class CoreLogic:
"""
return {"redbot": __version__, "discordpy": discord.__version__}
async def _invite_url(self):
async def _invite_url(self) -> str:
"""
Generates the invite URL for the bot.
@ -248,11 +262,8 @@ class CoreLogic:
class Core(commands.Cog, CoreLogic):
"""Commands related to core functions"""
def __init__(self, bot):
super().__init__(bot)
@commands.command(hidden=True)
async def ping(self, ctx):
async def ping(self, ctx: commands.Context):
"""Pong."""
await ctx.send("Pong.")
@ -313,7 +324,7 @@ class Core(commands.Cog, CoreLogic):
passed = self.get_bot_uptime()
await ctx.send("Been up for: **{}** (since {} UTC)".format(passed, since))
def get_bot_uptime(self, *, brief=False):
def get_bot_uptime(self, *, brief: bool = False):
# Courtesy of Danny
now = datetime.datetime.utcnow()
delta = now - self.bot.uptime
@ -416,7 +427,7 @@ class Core(commands.Cog, CoreLogic):
@commands.command()
@checks.is_owner()
async def traceback(self, ctx, public: bool = False):
async def traceback(self, ctx: commands.Context, public: bool = False):
"""Sends to the owner the last command exception that has occurred
If public (yes is specified), it will be sent to the chat instead"""
@ -433,14 +444,14 @@ class Core(commands.Cog, CoreLogic):
@commands.command()
@checks.is_owner()
async def invite(self, ctx):
async def invite(self, ctx: commands.Context):
"""Show's Red's invite url"""
await ctx.author.send(await self._invite_url())
@commands.command()
@commands.guild_only()
@checks.is_owner()
async def leave(self, ctx):
async def leave(self, ctx: commands.Context):
"""Leaves server"""
await ctx.send("Are you sure you want me to leave this server? (y/n)")
@ -460,7 +471,7 @@ class Core(commands.Cog, CoreLogic):
@commands.command()
@checks.is_owner()
async def servers(self, ctx):
async def servers(self, ctx: commands.Context):
"""Lists and allows to leave servers"""
guilds = sorted(list(self.bot.guilds), key=lambda s: s.name.lower())
msg = ""
@ -502,18 +513,21 @@ class Core(commands.Cog, CoreLogic):
@commands.command()
@checks.is_owner()
async def load(self, ctx, *, cog_name: str):
async def load(self, ctx: commands.Context, *cogs: str):
"""Loads packages"""
cog_names = [c.strip() for c in cog_name.split(" ")]
async with ctx.typing():
loaded, failed, not_found = await self._load(cog_names)
loaded, failed, not_found, already_loaded = await self._load(cogs)
if loaded:
fmt = "Loaded {packs}."
formed = self._get_package_strings(loaded, fmt)
await ctx.send(formed)
if already_loaded:
fmt = "The package{plural} {packs} {other} already loaded."
formed = self._get_package_strings(already_loaded, fmt, ("is", "are"))
await ctx.send(formed)
if failed:
fmt = (
"Failed to load package{plural} {packs}. Check your console or "
@ -529,12 +543,9 @@ class Core(commands.Cog, CoreLogic):
@commands.command()
@checks.is_owner()
async def unload(self, ctx, *, cog_name: str):
async def unload(self, ctx: commands.Context, *cogs: str):
"""Unloads packages"""
cog_names = [c.strip() for c in cog_name.split(" ")]
unloaded, failed = await self._unload(cog_names)
unloaded, failed = await self._unload(cogs)
if unloaded:
fmt = "Package{plural} {packs} {other} unloaded."
@ -548,10 +559,10 @@ class Core(commands.Cog, CoreLogic):
@commands.command(name="reload")
@checks.is_owner()
async def reload(self, ctx, *cogs: str):
async def reload(self, ctx: commands.Context, *cogs: str):
"""Reloads packages"""
async with ctx.typing():
loaded, failed, not_found = await self._reload(cogs)
loaded, failed, not_found, already_loaded = await self._reload(cogs)
if loaded:
fmt = "Package{plural} {packs} {other} reloaded."
@ -570,34 +581,30 @@ class Core(commands.Cog, CoreLogic):
@commands.command(name="shutdown")
@checks.is_owner()
async def _shutdown(self, ctx, silently: bool = False):
async def _shutdown(self, ctx: commands.Context, silently: bool = False):
"""Shuts down the bot"""
wave = "\N{WAVING HAND SIGN}"
skin = "\N{EMOJI MODIFIER FITZPATRICK TYPE-3}"
try: # We don't want missing perms to stop our shutdown
with contextlib.suppress(discord.HTTPException):
if not silently:
await ctx.send(_("Shutting down... ") + wave + skin)
except:
pass
await ctx.bot.shutdown()
@commands.command(name="restart")
@checks.is_owner()
async def _restart(self, ctx, silently: bool = False):
async def _restart(self, ctx: commands.Context, silently: bool = False):
"""Attempts to restart Red
Makes Red quit with exit code 26
The restart is not guaranteed: it must be dealt
with by the process manager in use"""
try:
with contextlib.suppress(discord.HTTPException):
if not silently:
await ctx.send(_("Restarting..."))
except:
pass
await ctx.bot.shutdown(restart=True)
@commands.group(name="set")
async def _set(self, ctx):
async def _set(self, ctx: commands.Context):
"""Changes Red's settings"""
if ctx.invoked_subcommand is None:
if ctx.guild:
@ -629,7 +636,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command()
@checks.guildowner()
@commands.guild_only()
async def adminrole(self, ctx, *, role: discord.Role):
async def adminrole(self, ctx: commands.Context, *, role: discord.Role):
"""Sets the admin role for this server"""
await ctx.bot.db.guild(ctx.guild).admin_role.set(role.id)
await ctx.send(_("The admin role for this guild has been set."))
@ -637,7 +644,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command()
@checks.guildowner()
@commands.guild_only()
async def modrole(self, ctx, *, role: discord.Role):
async def modrole(self, ctx: commands.Context, *, role: discord.Role):
"""Sets the mod role for this server"""
await ctx.bot.db.guild(ctx.guild).mod_role.set(role.id)
await ctx.send(_("The mod role for this guild has been set."))
@ -645,7 +652,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(aliases=["usebotcolor"])
@checks.guildowner()
@commands.guild_only()
async def usebotcolour(self, ctx):
async def usebotcolour(self, ctx: commands.Context):
"""
Toggle whether to use the bot owner-configured colour for embeds.
@ -663,7 +670,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command()
@checks.guildowner()
@commands.guild_only()
async def serverfuzzy(self, ctx):
async def serverfuzzy(self, ctx: commands.Context):
"""
Toggle whether to enable fuzzy command search for the server.
@ -679,7 +686,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command()
@checks.is_owner()
async def fuzzy(self, ctx):
async def fuzzy(self, ctx: commands.Context):
"""
Toggle whether to enable fuzzy command search in DMs.
@ -695,7 +702,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(aliases=["color"])
@checks.is_owner()
async def colour(self, ctx, *, colour: discord.Colour = None):
async def colour(self, ctx: commands.Context, *, colour: discord.Colour = None):
"""
Sets a default colour to be used for the bot's embeds.
@ -713,7 +720,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command()
@checks.is_owner()
async def avatar(self, ctx, url: str):
async def avatar(self, ctx: commands.Context, url: str):
"""Sets Red's avatar"""
async with aiohttp.ClientSession() as session:
async with session.get(url) as r:
@ -737,7 +744,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(name="game")
@checks.bot_in_a_guild()
@checks.is_owner()
async def _game(self, ctx, *, game: str = None):
async def _game(self, ctx: commands.Context, *, game: str = None):
"""Sets Red's playing status"""
if game:
@ -751,7 +758,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(name="listening")
@checks.bot_in_a_guild()
@checks.is_owner()
async def _listening(self, ctx, *, listening: str = None):
async def _listening(self, ctx: commands.Context, *, listening: str = None):
"""Sets Red's listening status"""
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
@ -765,7 +772,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(name="watching")
@checks.bot_in_a_guild()
@checks.is_owner()
async def _watching(self, ctx, *, watching: str = None):
async def _watching(self, ctx: commands.Context, *, watching: str = None):
"""Sets Red's watching status"""
status = ctx.bot.guilds[0].me.status if len(ctx.bot.guilds) > 0 else discord.Status.online
@ -779,7 +786,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command()
@checks.bot_in_a_guild()
@checks.is_owner()
async def status(self, ctx, *, status: str):
async def status(self, ctx: commands.Context, *, status: str):
"""Sets Red's status
Available statuses:
@ -808,7 +815,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command()
@checks.bot_in_a_guild()
@checks.is_owner()
async def stream(self, ctx, streamer=None, *, stream_title=None):
async def stream(self, ctx: commands.Context, streamer=None, *, stream_title=None):
"""Sets Red's streaming status
Leaving both streamer and stream_title empty will clear it."""
@ -829,7 +836,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(name="username", aliases=["name"])
@checks.is_owner()
async def _username(self, ctx, *, username: str):
async def _username(self, ctx: commands.Context, *, username: str):
"""Sets Red's username"""
try:
await self._name(name=username)
@ -848,7 +855,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(name="nickname")
@checks.admin()
@commands.guild_only()
async def _nickname(self, ctx, *, nickname: str = None):
async def _nickname(self, ctx: commands.Context, *, nickname: str = None):
"""Sets Red's nickname"""
try:
await ctx.guild.me.edit(nick=nickname)
@ -859,7 +866,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(aliases=["prefixes"])
@checks.is_owner()
async def prefix(self, ctx, *prefixes):
async def prefix(self, ctx: commands.Context, *prefixes: str):
"""Sets Red's global prefix(es)"""
if not prefixes:
await ctx.send_help()
@ -870,7 +877,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command(aliases=["serverprefixes"])
@checks.admin()
@commands.guild_only()
async def serverprefix(self, ctx, *prefixes):
async def serverprefix(self, ctx: commands.Context, *prefixes: str):
"""Sets Red's server prefix(es)"""
if not prefixes:
await ctx.bot.db.guild(ctx.guild).prefix.set([])
@ -882,7 +889,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command()
@commands.cooldown(1, 60 * 10, commands.BucketType.default)
async def owner(self, ctx):
async def owner(self, ctx: commands.Context):
"""Sets Red's main owner"""
# According to the Python docs this is suitable for cryptographic use
random = SystemRandom()
@ -926,7 +933,7 @@ class Core(commands.Cog, CoreLogic):
@_set.command()
@checks.is_owner()
async def token(self, ctx, token: str):
async def token(self, ctx: commands.Context, token: str):
"""Change bot token."""
if not isinstance(ctx.channel, discord.DMChannel):
@ -1071,7 +1078,7 @@ class Core(commands.Cog, CoreLogic):
@commands.command()
@checks.is_owner()
async def backup(self, ctx, backup_path: str = None):
async def backup(self, ctx: commands.Context, backup_path: str = None):
"""Creates a backup of all data for the instance."""
from redbot.core.data_manager import basic_config, instance_name
from redbot.core.drivers.red_json import JSON
@ -1134,7 +1141,7 @@ class Core(commands.Cog, CoreLogic):
tar.add(str(f), recursive=False)
print(str(backup_file))
await ctx.send(
_("A backup has been made of this instance. It is at {}.").format((backup_file))
_("A backup has been made of this instance. It is at {}.").format(backup_file)
)
await ctx.send(_("Would you like to receive a copy via DM? (y/n)"))
@ -1157,7 +1164,7 @@ class Core(commands.Cog, CoreLogic):
@commands.command()
@commands.cooldown(1, 60, commands.BucketType.user)
async def contact(self, ctx, *, message: str):
async def contact(self, ctx: commands.Context, *, message: str):
"""Sends a message to the owner"""
guild = ctx.message.guild
owner = discord.utils.get(ctx.bot.get_all_members(), id=ctx.bot.owner_id)
@ -1200,7 +1207,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(
_("I cannot send your message, I'm unable to find my owner... *sigh*")
)
except:
except discord.HTTPException:
await ctx.send(_("I'm unable to deliver your message. Sorry."))
else:
await ctx.send(_("Your message has been sent."))
@ -1212,14 +1219,14 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(
_("I cannot send your message, I'm unable to find my owner... *sigh*")
)
except:
except discord.HTTPException:
await ctx.send(_("I'm unable to deliver your message. Sorry."))
else:
await ctx.send(_("Your message has been sent."))
@commands.command()
@checks.is_owner()
async def dm(self, ctx, user_id: int, *, message: str):
async def dm(self, ctx: commands.Context, user_id: int, *, message: str):
"""Sends a DM to a user
This command needs a user id to work.
@ -1253,7 +1260,7 @@ class Core(commands.Cog, CoreLogic):
try:
await destination.send(embed=e)
except:
except discord.HTTPException:
await ctx.send(
_("Sorry, I couldn't deliver your message to {}").format(destination)
)
@ -1263,7 +1270,7 @@ class Core(commands.Cog, CoreLogic):
response = "{}\nMessage:\n\n{}".format(description, message)
try:
await destination.send("{}\n{}".format(box(response), content))
except:
except discord.HTTPException:
await ctx.send(
_("Sorry, I couldn't deliver your message to {}").format(destination)
)
@ -1272,7 +1279,7 @@ class Core(commands.Cog, CoreLogic):
@commands.group()
@checks.is_owner()
async def whitelist(self, ctx):
async def whitelist(self, ctx: commands.Context):
"""
Whitelist management commands.
"""
@ -1290,7 +1297,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("User added to whitelist."))
@whitelist.command(name="list")
async def whitelist_list(self, ctx):
async def whitelist_list(self, ctx: commands.Context):
"""
Lists whitelisted users.
"""
@ -1304,7 +1311,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(box(page))
@whitelist.command(name="remove")
async def whitelist_remove(self, ctx, user: discord.User):
async def whitelist_remove(self, ctx: commands.Context, user: discord.User):
"""
Removes user from whitelist.
"""
@ -1321,7 +1328,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("User was not in the whitelist."))
@whitelist.command(name="clear")
async def whitelist_clear(self, ctx):
async def whitelist_clear(self, ctx: commands.Context):
"""
Clears the whitelist.
"""
@ -1330,19 +1337,19 @@ class Core(commands.Cog, CoreLogic):
@commands.group()
@checks.is_owner()
async def blacklist(self, ctx):
async def blacklist(self, ctx: commands.Context):
"""
blacklist management commands.
"""
pass
@blacklist.command(name="add")
async def blacklist_add(self, ctx, user: discord.User):
async def blacklist_add(self, ctx: commands.Context, user: discord.User):
"""
Adds a user to the blacklist.
"""
if await ctx.bot.is_owner(user):
ctx.send(_("You cannot blacklist an owner!"))
await ctx.send(_("You cannot blacklist an owner!"))
return
async with ctx.bot.db.blacklist() as curr_list:
@ -1352,7 +1359,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("User added to blacklist."))
@blacklist.command(name="list")
async def blacklist_list(self, ctx):
async def blacklist_list(self, ctx: commands.Context):
"""
Lists blacklisted users.
"""
@ -1366,7 +1373,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(box(page))
@blacklist.command(name="remove")
async def blacklist_remove(self, ctx, user: discord.User):
async def blacklist_remove(self, ctx: commands.Context, user: discord.User):
"""
Removes user from blacklist.
"""
@ -1383,7 +1390,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("User was not in the blacklist."))
@blacklist.command(name="clear")
async def blacklist_clear(self, ctx):
async def blacklist_clear(self, ctx: commands.Context):
"""
Clears the blacklist.
"""
@ -1393,14 +1400,14 @@ class Core(commands.Cog, CoreLogic):
@commands.group()
@commands.guild_only()
@checks.admin_or_permissions(administrator=True)
async def localwhitelist(self, ctx):
async def localwhitelist(self, ctx: commands.Context):
"""
Whitelist management commands.
"""
pass
@localwhitelist.command(name="add")
async def localwhitelist_add(self, ctx, *, user_or_role: str):
async def localwhitelist_add(self, ctx: commands.Context, *, user_or_role: str):
"""
Adds a user or role to the whitelist.
"""
@ -1421,7 +1428,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("Role added to whitelist."))
@localwhitelist.command(name="list")
async def localwhitelist_list(self, ctx):
async def localwhitelist_list(self, ctx: commands.Context):
"""
Lists whitelisted users and roles.
"""
@ -1435,7 +1442,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(box(page))
@localwhitelist.command(name="remove")
async def localwhitelist_remove(self, ctx, *, user_or_role: str):
async def localwhitelist_remove(self, ctx: commands.Context, *, user_or_role: str):
"""
Removes user or role from whitelist.
"""
@ -1465,7 +1472,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("Role was not in the whitelist."))
@localwhitelist.command(name="clear")
async def localwhitelist_clear(self, ctx):
async def localwhitelist_clear(self, ctx: commands.Context):
"""
Clears the whitelist.
"""
@ -1475,14 +1482,14 @@ class Core(commands.Cog, CoreLogic):
@commands.group()
@commands.guild_only()
@checks.admin_or_permissions(administrator=True)
async def localblacklist(self, ctx):
async def localblacklist(self, ctx: commands.Context):
"""
blacklist management commands.
"""
pass
@localblacklist.command(name="add")
async def localblacklist_add(self, ctx, *, user_or_role: str):
async def localblacklist_add(self, ctx: commands.Context, *, user_or_role: str):
"""
Adds a user or role to the blacklist.
"""
@ -1495,7 +1502,7 @@ class Core(commands.Cog, CoreLogic):
user = True
if user and await ctx.bot.is_owner(obj):
ctx.send(_("You cannot blacklist an owner!"))
await ctx.send(_("You cannot blacklist an owner!"))
return
async with ctx.bot.db.guild(ctx.guild).blacklist() as curr_list:
@ -1508,7 +1515,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("Role added to blacklist."))
@localblacklist.command(name="list")
async def localblacklist_list(self, ctx):
async def localblacklist_list(self, ctx: commands.Context):
"""
Lists blacklisted users and roles.
"""
@ -1522,7 +1529,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(box(page))
@localblacklist.command(name="remove")
async def localblacklist_remove(self, ctx, *, user_or_role: str):
async def localblacklist_remove(self, ctx: commands.Context, *, user_or_role: str):
"""
Removes user or role from blacklist.
"""
@ -1552,7 +1559,7 @@ class Core(commands.Cog, CoreLogic):
await ctx.send(_("Role was not in the blacklist."))
@localblacklist.command(name="clear")
async def localblacklist_clear(self, ctx):
async def localblacklist_clear(self, ctx: commands.Context):
"""
Clears the blacklist.
"""

16
redbot/core/errors.py Normal file
View File

@ -0,0 +1,16 @@
import importlib.machinery
class RedError(Exception):
"""Base error class for Red-related errors."""
class PackageAlreadyLoaded(RedError):
"""Raised when trying to load an already-loaded package."""
def __init__(self, spec: importlib.machinery.ModuleSpec, *args, **kwargs):
super().__init__(*args, **kwargs)
self.spec: importlib.machinery.ModuleSpec = spec
def __str__(self) -> str:
return f"There is already a package named {self.spec.name.split('.')[-1]} loaded"

View File

@ -3,8 +3,6 @@ import re
from pathlib import Path
from typing import Callable, Union
from . import commands
__all__ = ["get_locale", "set_locale", "reload_locales", "cog_i18n", "Translator"]
_current_locale = "en_us"
@ -219,6 +217,12 @@ class Translator(Callable[[str], str]):
self.translations.update({untranslated: translated})
# This import to be down here to avoid circular import issues.
# This will be cleaned up at a later date
# noinspection PyPep8
from . import commands
def cog_i18n(translator: Translator):
"""Get a class decorator to link the translator to this cog."""