From 8bd3706994843b31900975825e19aec35641e92d Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Wed, 23 Mar 2022 21:39:43 +0000 Subject: Add BotBase that will act as a base for all our bots This commit also modifies the extensions util, since it's now directly used by bot-core. Co-authored-by: Mark <1515135+MarkKoz@users.noreply.github.com> Co-authored-by: Hassan Abouelela --- botcore/utils/_extensions.py | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 botcore/utils/_extensions.py (limited to 'botcore/utils/_extensions.py') diff --git a/botcore/utils/_extensions.py b/botcore/utils/_extensions.py new file mode 100644 index 00000000..6848fae6 --- /dev/null +++ b/botcore/utils/_extensions.py @@ -0,0 +1,49 @@ +"""Utilities for loading Discord extensions.""" + +import importlib +import inspect +import pkgutil +import types +from typing import NoReturn + + +def unqualify(name: str) -> str: + """ + Return an unqualified name given a qualified module/package ``name``. + + Args: + name: The module name to unqualify. + + Returns: + The unqualified module name. + """ + return name.rsplit(".", maxsplit=1)[-1] + + +def walk_extensions(module: types.ModuleType) -> frozenset[str]: + """ + Yield extension names from the given module. + + Args: + module (types.ModuleType): The module to look for extensions in. + + Returns: + An iterator object, that returns a string that can be passed directly to + :obj:`discord.ext.commands.Bot.load_extension` on call to next(). + """ + + def on_error(name: str) -> NoReturn: + raise ImportError(name=name) # pragma: no cover + + for module_info in pkgutil.walk_packages(module.__path__, f"{module.__name__}.", onerror=on_error): + if unqualify(module_info.name).startswith("_"): + # Ignore module/package names starting with an underscore. + continue + + if module_info.ispkg: + imported = importlib.import_module(module_info.name) + if not inspect.isfunction(getattr(imported, "setup", None)): + # If it lacks a setup function, it's not an extension. + continue + + yield module_info.name -- cgit v1.2.3 From b8a245061689c45ebee05f27481837576a079e54 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Thu, 24 Mar 2022 07:44:50 +0000 Subject: Provide a bot.all_extensions instance attribute This allows commands like extensions and source to see all of the available commands, rather than just the currently loaded commands. --- botcore/_bot.py | 8 ++++++-- botcore/utils/_extensions.py | 11 +++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) (limited to 'botcore/utils/_extensions.py') diff --git a/botcore/_bot.py b/botcore/_bot.py index f334631c..d740fe03 100644 --- a/botcore/_bot.py +++ b/botcore/_bot.py @@ -75,6 +75,8 @@ class BotBase(commands.Bot): self.stats: Optional[AsyncStatsClient] = None + self.all_extensions: Optional[frozenset[str]] = None + def _connect_statsd( self, statsd_url: str, @@ -104,8 +106,10 @@ class BotBase(commands.Bot): self.closing_tasks: list[asyncio.Task] = [] async def load_extensions(self, module: types.ModuleType) -> None: - """Load all the extensions within the given module.""" - for extension in walk_extensions(module): + """Load all the extensions within the given module and save them to ``self.all_extensions``.""" + self.all_extensions = walk_extensions(module) + + for extension in self.all_extensions: await self.load_extension(extension) def _add_root_aliases(self, command: commands.Command) -> None: diff --git a/botcore/utils/_extensions.py b/botcore/utils/_extensions.py index 6848fae6..d90a25dd 100644 --- a/botcore/utils/_extensions.py +++ b/botcore/utils/_extensions.py @@ -22,19 +22,20 @@ def unqualify(name: str) -> str: def walk_extensions(module: types.ModuleType) -> frozenset[str]: """ - Yield extension names from the given module. + Return all extension names from the given module. Args: module (types.ModuleType): The module to look for extensions in. Returns: - An iterator object, that returns a string that can be passed directly to - :obj:`discord.ext.commands.Bot.load_extension` on call to next(). + A set of strings that can be passed directly to :obj:`discord.ext.commands.Bot.load_extension`. """ def on_error(name: str) -> NoReturn: raise ImportError(name=name) # pragma: no cover + modules = set() + for module_info in pkgutil.walk_packages(module.__path__, f"{module.__name__}.", onerror=on_error): if unqualify(module_info.name).startswith("_"): # Ignore module/package names starting with an underscore. @@ -46,4 +47,6 @@ def walk_extensions(module: types.ModuleType) -> frozenset[str]: # If it lacks a setup function, it's not an extension. continue - yield module_info.name + modules.add(module_info.name) + + return frozenset(modules) -- cgit v1.2.3