@@ -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