From cd7f4681a48c18a52bc825b232d2c1335bc384bb Mon Sep 17 00:00:00 2001 From: Michael H Date: Fri, 17 Jan 2020 16:49:25 -0500 Subject: [PATCH] Cache prefixes (#3150) * Cache prefixes - This works towards #3148 - Ends up centralizing some logic - Including that prefixes should be a reverse sorted list * handle global prefix attempts at none * fix prefix set for server * cache using guild id --- changelog.d/3148.misc.1.rst | 1 + redbot/core/bot.py | 36 +++++++++-------------- redbot/core/cli.py | 4 ++- redbot/core/core_commands.py | 15 ++++------ redbot/core/settings_caches.py | 53 ++++++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 32 deletions(-) create mode 100644 changelog.d/3148.misc.1.rst create mode 100644 redbot/core/settings_caches.py diff --git a/changelog.d/3148.misc.1.rst b/changelog.d/3148.misc.1.rst new file mode 100644 index 000000000..6e966e714 --- /dev/null +++ b/changelog.d/3148.misc.1.rst @@ -0,0 +1 @@ +Cache prefixes rather than lookup from config each time diff --git a/redbot/core/bot.py b/redbot/core/bot.py index 32b69f27d..9c7e56408 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -36,6 +36,8 @@ from .dev_commands import Dev from .events import init_events from .global_checks import init_global_checks +from .settings_caches import PrefixManager + from .rpc import RPCMixin from .utils import common_filters @@ -124,23 +126,13 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d self._config.init_custom(SHARED_API_TOKENS, 2) self._config.register_custom(SHARED_API_TOKENS) + self._prefix_cache = PrefixManager(self._config, cli_flags) - async def prefix_manager(bot, message): - if not cli_flags.prefix: - global_prefix = await bot._config.prefix() - else: - global_prefix = cli_flags.prefix - if message.guild is None: - return global_prefix - server_prefix = await bot._config.guild(message.guild).prefix() + async def prefix_manager(bot, message) -> List[str]: + prefixes = await self._prefix_cache.get_prefixes(message.guild) if cli_flags.mentionable: - return ( - when_mentioned_or(*server_prefix)(bot, message) - if server_prefix - else when_mentioned_or(*global_prefix)(bot, message) - ) - else: - return server_prefix if server_prefix else global_prefix + return when_mentioned_or(*prefixes)(bot, message) + return prefixes if "command_prefix" not in kwargs: kwargs["command_prefix"] = prefix_manager @@ -273,15 +265,15 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d """ This checks if a user or member is allowed to run things, as considered by Red's whitelist and blacklist. - + If given a user object, this function will check the global lists - + If given a member, this will additionally check guild lists - + If omiting a user or member, you must provide a value for ``who_id`` - + You may also provide a value for ``guild_id`` in this case - + If providing a member by guild and member ids, you should supply ``role_ids`` as well @@ -289,7 +281,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d ---------- who : Optional[Union[discord.Member, discord.User]] The user or member object to check - + Other Parameters ---------------- who_id : Optional[int] @@ -906,7 +898,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin): # pylint: d This should realistically only be used for responding using user provided input. (unfortunately, including usernames) Manually crafted messages which dont take any user input have no need of this - + Returns ------- discord.Message diff --git a/redbot/core/cli.py b/redbot/core/cli.py index 4778bb642..9c2575795 100644 --- a/redbot/core/cli.py +++ b/redbot/core/cli.py @@ -135,7 +135,9 @@ def parse_cli_flags(args): "security implications if misused. Can be " "multiple.", ) - parser.add_argument("--prefix", "-p", action="append", help="Global prefix. Can be multiple") + parser.add_argument( + "--prefix", "-p", action="append", help="Global prefix. Can be multiple", default=[] + ) parser.add_argument( "--no-prompt", action="store_true", diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index 4a05cea70..02ed1b738 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -257,10 +257,9 @@ class CoreLogic: The current (or new) list of prefixes. """ if prefixes: - prefixes = sorted(prefixes, reverse=True) - await self.bot._config.prefix.set(prefixes) + await self.bot._prefix_cache.set_prefixes(guild=None, prefixes=prefixes) return prefixes - return await self.bot._config.prefix() + return await self.bot._prefix_cache.get_prefixes(guild=None) @classmethod async def _version_info(cls) -> Dict[str, str]: @@ -847,15 +846,13 @@ class Core(commands.Cog, CoreLogic): mod_role_ids = await ctx.bot._config.guild(ctx.guild).mod_role() mod_role_names = [r.name for r in guild.roles if r.id in mod_role_ids] mod_roles_str = humanize_list(mod_role_names) if mod_role_names else "Not Set." - prefixes = await ctx.bot._config.guild(ctx.guild).prefix() guild_settings = _("Admin roles: {admin}\nMod roles: {mod}\n").format( admin=admin_roles_str, mod=mod_roles_str ) else: guild_settings = "" - prefixes = None # This is correct. The below can happen in a guild. - if not prefixes: - prefixes = await ctx.bot._config.prefix() + + prefixes = await ctx.bot._prefix_cache.get_prefixes(ctx.guild) locale = await ctx.bot._config.locale() prefix_string = " ".join(prefixes) @@ -1182,11 +1179,11 @@ class Core(commands.Cog, CoreLogic): async def serverprefix(self, ctx: commands.Context, *prefixes: str): """Sets Red's server prefix(es)""" if not prefixes: - await ctx.bot._config.guild(ctx.guild).prefix.set([]) + await ctx.bot._prefix_cache.set_prefixes(guild=ctx.guild, prefixes=[]) await ctx.send(_("Guild prefixes have been reset.")) return prefixes = sorted(prefixes, reverse=True) - await ctx.bot._config.guild(ctx.guild).prefix.set(prefixes) + await ctx.bot._prefix_cache.set_prefixes(guild=ctx.guild, prefixes=prefixes) await ctx.send(_("Prefix set.")) @_set.command() diff --git a/redbot/core/settings_caches.py b/redbot/core/settings_caches.py new file mode 100644 index 000000000..32c4c5d52 --- /dev/null +++ b/redbot/core/settings_caches.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from typing import Dict, List, Optional +from argparse import Namespace + +import discord + +from .config import Config + + +class PrefixManager: + def __init__(self, config: Config, cli_flags: Namespace): + self._config: Config = config + self._global_prefix_overide: Optional[List[str]] = sorted( + cli_flags.prefix, reverse=True + ) or None + self._cached: Dict[Optional[int], List[str]] = {} + + async def get_prefixes(self, guild: Optional[discord.Guild] = None) -> List[str]: + ret: List[str] + + gid: Optional[int] = guild.id if guild else None + + if gid in self._cached: + ret = self._cached[gid].copy() + else: + if gid is not None: + ret = await self._config.guild_from_id(gid).prefix() + if not ret: + ret = await self.get_prefixes(None) + else: + ret = self._global_prefix_overide or (await self._config.prefix()) + + self._cached[gid] = ret.copy() + + return ret + + async def set_prefixes( + self, guild: Optional[discord.Guild] = None, prefixes: Optional[List[str]] = None + ): + gid: Optional[int] = guild.id if guild else None + prefixes = prefixes or [] + if not isinstance(prefixes, list) and not all(isinstance(pfx, str) for pfx in prefixes): + raise TypeError("Prefixes must be a list of strings") + prefixes = sorted(prefixes, reverse=True) + if gid is None: + if not prefixes: + raise ValueError("You must have at least one prefix.") + self._cached.clear() + await self._config.prefix.set(prefixes) + else: + del self._cached[gid] + await self._config.guild_from_id(gid).prefix.set(prefixes)