async def _remove_module_references(self, name: str) -> None: # find all references to the module # remove the cogs registered from the module for cogname, cog in self.__cogs.copy().items(): if _is_submodule(name, cog.__module__): await self.remove_cog(cogname) # remove all the commands from the module for cmd in self.all_commands.copy().values(): if cmd.module is not None and _is_submodule(name, cmd.module): if isinstance(cmd, GroupMixin): cmd.recursively_remove_all_commands() self.remove_command(cmd.name) # remove all the listeners from the module for event_list in self.extra_events.copy().values(): remove = [] for index, event in enumerate(event_list): if event.__module__ is not None and _is_submodule( name, event.__module__): remove.append(index) for index in reversed(remove): del event_list[index] # remove all relevant application commands from the tree self.__tree._remove_with_module(name)
async def _call_module_finalizers(self, lib: types.ModuleType, key: str) -> None: try: func = getattr(lib, 'teardown') except AttributeError: pass else: try: await func(self) except Exception: pass finally: self.__extensions.pop(key, None) sys.modules.pop(key, None) name = lib.__name__ for module in list(sys.modules.keys()): if _is_submodule(name, module): del sys.modules[module]
async def reload_extension(self, name: str, *, package: Optional[str] = None) -> None: """Atomically reloads an extension. This replaces the extension with the same extension, only refreshed. This is equivalent to a :meth:`unload_extension` followed by a :meth:`load_extension` except done in an atomic way. That is, if an operation fails mid-reload then the bot will roll-back to the prior working state. Parameters ------------ name: :class:`str` The extension name to reload. It must be dot separated like regular Python imports if accessing a sub-module. e.g. ``foo.test`` if you want to import ``foo/test.py``. package: Optional[:class:`str`] The package name to resolve relative imports with. This is required when reloading an extension using a relative path, e.g ``.foo.test``. Defaults to ``None``. .. versionadded:: 1.7 Raises ------- ExtensionNotLoaded The extension was not loaded. ExtensionNotFound The extension could not be imported. This is also raised if the name of the extension could not be resolved using the provided ``package`` parameter. NoEntryPointError The extension does not have a setup function. ExtensionFailed The extension setup function had an execution error. """ name = self._resolve_name(name, package) lib = self.__extensions.get(name) if lib is None: raise errors.ExtensionNotLoaded(name) # get the previous module states from sys modules # fmt: off modules = { name: module for name, module in sys.modules.items() if _is_submodule(lib.__name__, name) } # fmt: on try: # Unload and then load the module... await self._remove_module_references(lib.__name__) await self._call_module_finalizers(lib, name) await self.load_extension(name) except Exception: # if the load failed, the remnants should have been # cleaned from the load_extension function call # so let's load it from our old compiled library. await lib.setup(self) self.__extensions[name] = lib # revert sys.modules back to normal and raise back to caller sys.modules.update(modules) raise