Skip to content

Commit 3db4e11

Browse files
committed
Make commands.Bot inherit from CogMixin
1 parent da8d82c commit 3db4e11

File tree

2 files changed

+2
-336
lines changed

2 files changed

+2
-336
lines changed

discord/cog.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
__all__ = (
4242
'CogMeta',
4343
'Cog',
44-
'CogMixin'
44+
'CogMixin',
4545
)
4646

4747
CogT = TypeVar('CogT', bound='Cog')

discord/ext/commands/bot.py

Lines changed: 1 addition & 335 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def __repr__(self):
120120

121121
_default = _DefaultRepr()
122122

123-
class BotBase(GroupMixin):
123+
class BotBase(GroupMixin, discord.cog.CogMixin):
124124
_supports_prefixed_commands = True
125125
def __init__(self, command_prefix=when_mentioned, help_command=_default, **options):
126126
super().__init__(**options)
@@ -181,340 +181,6 @@ async def can_run(self, ctx: Context, *, call_once: bool = False) -> bool:
181181

182182
# type-checker doesn't distinguish between functions and methods
183183
return await discord.utils.async_all(f(ctx) for f in data) # type: ignore
184-
185-
186-
# cogs
187-
188-
def add_cog(self, cog: Cog, *, override: bool = False) -> None:
189-
"""Adds a "cog" to the bot.
190-
191-
A cog is a class that has its own event listeners and commands.
192-
193-
.. versionchanged:: 2.0
194-
195-
:exc:`.ClientException` is raised when a cog with the same name
196-
is already loaded.
197-
198-
Parameters
199-
-----------
200-
cog: :class:`.Cog`
201-
The cog to register to the bot.
202-
override: :class:`bool`
203-
If a previously loaded cog with the same name should be ejected
204-
instead of raising an error.
205-
206-
.. versionadded:: 2.0
207-
208-
Raises
209-
-------
210-
TypeError
211-
The cog does not inherit from :class:`.Cog`.
212-
CommandError
213-
An error happened during loading.
214-
.ClientException
215-
A cog with the same name is already loaded.
216-
"""
217-
218-
if not isinstance(cog, Cog):
219-
raise TypeError('cogs must derive from Cog')
220-
221-
cog_name = cog.__cog_name__
222-
existing = self.__cogs.get(cog_name)
223-
224-
if existing is not None:
225-
if not override:
226-
raise discord.ClientException(f'Cog named {cog_name!r} already loaded')
227-
self.remove_cog(cog_name)
228-
229-
cog = cog._inject(self)
230-
self.__cogs[cog_name] = cog
231-
232-
def get_cog(self, name: str) -> Optional[Cog]:
233-
"""Gets the cog instance requested.
234-
235-
If the cog is not found, ``None`` is returned instead.
236-
237-
Parameters
238-
-----------
239-
name: :class:`str`
240-
The name of the cog you are requesting.
241-
This is equivalent to the name passed via keyword
242-
argument in class creation or the class name if unspecified.
243-
244-
Returns
245-
--------
246-
Optional[:class:`Cog`]
247-
The cog that was requested. If not found, returns ``None``.
248-
"""
249-
return self.__cogs.get(name)
250-
251-
def remove_cog(self, name: str) -> Optional[Cog]:
252-
"""Removes a cog from the bot and returns it.
253-
254-
All registered commands and event listeners that the
255-
cog has registered will be removed as well.
256-
257-
If no cog is found then this method has no effect.
258-
259-
Parameters
260-
-----------
261-
name: :class:`str`
262-
The name of the cog to remove.
263-
264-
Returns
265-
-------
266-
Optional[:class:`.Cog`]
267-
The cog that was removed. ``None`` if not found.
268-
"""
269-
270-
cog = self.__cogs.pop(name, None)
271-
if cog is None:
272-
return
273-
274-
help_command = self._help_command
275-
if help_command and help_command.cog is cog:
276-
help_command.cog = None
277-
cog._eject(self)
278-
279-
return cog
280-
281-
@property
282-
def cogs(self) -> Mapping[str, Cog]:
283-
"""Mapping[:class:`str`, :class:`Cog`]: A read-only mapping of cog name to cog."""
284-
return types.MappingProxyType(self.__cogs)
285-
286-
# extensions
287-
288-
def _remove_module_references(self, name: str) -> None:
289-
# find all references to the module
290-
# remove the cogs registered from the module
291-
for cogname, cog in self.__cogs.copy().items():
292-
if _is_submodule(name, cog.__module__):
293-
self.remove_cog(cogname)
294-
295-
# remove all the commands from the module
296-
for cmd in self.all_commands.copy().values():
297-
if cmd.module is not None and _is_submodule(name, cmd.module):
298-
if isinstance(cmd, GroupMixin):
299-
cmd.recursively_remove_all_commands()
300-
self.remove_command(cmd.name)
301-
302-
# remove all the listeners from the module
303-
for event_list in self.extra_events.copy().values():
304-
remove = []
305-
for index, event in enumerate(event_list):
306-
if event.__module__ is not None and _is_submodule(name, event.__module__):
307-
remove.append(index)
308-
309-
for index in reversed(remove):
310-
del event_list[index]
311-
312-
def _call_module_finalizers(self, lib: types.ModuleType, key: str) -> None:
313-
try:
314-
func = getattr(lib, 'teardown')
315-
except AttributeError:
316-
pass
317-
else:
318-
try:
319-
func(self)
320-
except Exception:
321-
pass
322-
finally:
323-
self.__extensions.pop(key, None)
324-
sys.modules.pop(key, None)
325-
name = lib.__name__
326-
for module in list(sys.modules.keys()):
327-
if _is_submodule(name, module):
328-
del sys.modules[module]
329-
330-
def _load_from_module_spec(self, spec: importlib.machinery.ModuleSpec, key: str) -> None:
331-
# precondition: key not in self.__extensions
332-
lib = importlib.util.module_from_spec(spec)
333-
sys.modules[key] = lib
334-
try:
335-
spec.loader.exec_module(lib) # type: ignore
336-
except Exception as e:
337-
del sys.modules[key]
338-
raise discord.ExtensionFailed(key, e) from e
339-
340-
try:
341-
setup = getattr(lib, 'setup')
342-
except AttributeError:
343-
del sys.modules[key]
344-
raise discord.NoEntryPointError(key)
345-
346-
try:
347-
setup(self)
348-
except Exception as e:
349-
del sys.modules[key]
350-
self._remove_module_references(lib.__name__)
351-
self._call_module_finalizers(lib, key)
352-
raise discord.ExtensionFailed(key, e) from e
353-
else:
354-
self.__extensions[key] = lib
355-
356-
def _resolve_name(self, name: str, package: Optional[str]) -> str:
357-
try:
358-
return importlib.util.resolve_name(name, package)
359-
except ImportError:
360-
raise discord.ExtensionNotFound(name)
361-
362-
def load_extension(self, name: str, *, package: Optional[str] = None) -> None:
363-
"""Loads an extension.
364-
365-
An extension is a python module that contains commands, cogs, or
366-
listeners.
367-
368-
An extension must have a global function, ``setup`` defined as
369-
the entry point on what to do when the extension is loaded. This entry
370-
point must have a single argument, the ``bot``.
371-
372-
Parameters
373-
------------
374-
name: :class:`str`
375-
The extension name to load. It must be dot separated like
376-
regular Python imports if accessing a sub-module. e.g.
377-
``foo.test`` if you want to import ``foo/test.py``.
378-
package: Optional[:class:`str`]
379-
The package name to resolve relative imports with.
380-
This is required when loading an extension using a relative path, e.g ``.foo.test``.
381-
Defaults to ``None``.
382-
383-
.. versionadded:: 1.7
384-
385-
Raises
386-
--------
387-
ExtensionNotFound
388-
The extension could not be imported.
389-
This is also raised if the name of the extension could not
390-
be resolved using the provided ``package`` parameter.
391-
ExtensionAlreadyLoaded
392-
The extension is already loaded.
393-
NoEntryPointError
394-
The extension does not have a setup function.
395-
ExtensionFailed
396-
The extension or its setup function had an execution error.
397-
"""
398-
399-
name = self._resolve_name(name, package)
400-
if name in self.__extensions:
401-
raise discord.ExtensionAlreadyLoaded(name)
402-
403-
spec = importlib.util.find_spec(name)
404-
if spec is None:
405-
raise discord.ExtensionNotFound(name)
406-
407-
self._load_from_module_spec(spec, name)
408-
409-
def unload_extension(self, name: str, *, package: Optional[str] = None) -> None:
410-
"""Unloads an extension.
411-
412-
When the extension is unloaded, all commands, listeners, and cogs are
413-
removed from the bot and the module is un-imported.
414-
415-
The extension can provide an optional global function, ``teardown``,
416-
to do miscellaneous clean-up if necessary. This function takes a single
417-
parameter, the ``bot``, similar to ``setup`` from
418-
:meth:`~.Bot.load_extension`.
419-
420-
Parameters
421-
------------
422-
name: :class:`str`
423-
The extension name to unload. It must be dot separated like
424-
regular Python imports if accessing a sub-module. e.g.
425-
``foo.test`` if you want to import ``foo/test.py``.
426-
package: Optional[:class:`str`]
427-
The package name to resolve relative imports with.
428-
This is required when unloading an extension using a relative path, e.g ``.foo.test``.
429-
Defaults to ``None``.
430-
431-
.. versionadded:: 1.7
432-
433-
Raises
434-
-------
435-
ExtensionNotFound
436-
The name of the extension could not
437-
be resolved using the provided ``package`` parameter.
438-
ExtensionNotLoaded
439-
The extension was not loaded.
440-
"""
441-
442-
name = self._resolve_name(name, package)
443-
lib = self.__extensions.get(name)
444-
if lib is None:
445-
raise discord.ExtensionNotLoaded(name)
446-
447-
self._remove_module_references(lib.__name__)
448-
self._call_module_finalizers(lib, name)
449-
450-
def reload_extension(self, name: str, *, package: Optional[str] = None) -> None:
451-
"""Atomically reloads an extension.
452-
453-
This replaces the extension with the same extension, only refreshed. This is
454-
equivalent to a :meth:`unload_extension` followed by a :meth:`load_extension`
455-
except done in an atomic way. That is, if an operation fails mid-reload then
456-
the bot will roll-back to the prior working state.
457-
458-
Parameters
459-
------------
460-
name: :class:`str`
461-
The extension name to reload. It must be dot separated like
462-
regular Python imports if accessing a sub-module. e.g.
463-
``foo.test`` if you want to import ``foo/test.py``.
464-
package: Optional[:class:`str`]
465-
The package name to resolve relative imports with.
466-
This is required when reloading an extension using a relative path, e.g ``.foo.test``.
467-
Defaults to ``None``.
468-
469-
.. versionadded:: 1.7
470-
471-
Raises
472-
-------
473-
ExtensionNotLoaded
474-
The extension was not loaded.
475-
ExtensionNotFound
476-
The extension could not be imported.
477-
This is also raised if the name of the extension could not
478-
be resolved using the provided ``package`` parameter.
479-
NoEntryPointError
480-
The extension does not have a setup function.
481-
ExtensionFailed
482-
The extension setup function had an execution error.
483-
"""
484-
485-
name = self._resolve_name(name, package)
486-
lib = self.__extensions.get(name)
487-
if lib is None:
488-
raise discord.ExtensionNotLoaded(name)
489-
490-
# get the previous module states from sys modules
491-
modules = {
492-
name: module
493-
for name, module in sys.modules.items()
494-
if _is_submodule(lib.__name__, name)
495-
}
496-
497-
try:
498-
# Unload and then load the module...
499-
self._remove_module_references(lib.__name__)
500-
self._call_module_finalizers(lib, name)
501-
self.load_extension(name)
502-
except Exception:
503-
# if the load failed, the remnants should have been
504-
# cleaned from the load_extension function call
505-
# so let's load it from our old compiled library.
506-
lib.setup(self) # type: ignore
507-
self.__extensions[name] = lib
508-
509-
# revert sys.modules back to normal and raise back to caller
510-
sys.modules.update(modules)
511-
raise
512-
513-
@property
514-
def extensions(self) -> Mapping[str, types.ModuleType]:
515-
"""Mapping[:class:`str`, :class:`py:types.ModuleType`]: A read-only mapping of extension name to extension."""
516-
return types.MappingProxyType(self.__extensions)
517-
518184
# help command stuff
519185

520186
@property

0 commit comments

Comments
 (0)