@@ -120,7 +120,7 @@ def __repr__(self):
120
120
121
121
_default = _DefaultRepr ()
122
122
123
- class BotBase (GroupMixin ):
123
+ class BotBase (GroupMixin , discord . cog . CogMixin ):
124
124
_supports_prefixed_commands = True
125
125
def __init__ (self , command_prefix = when_mentioned , help_command = _default , ** options ):
126
126
super ().__init__ (** options )
@@ -181,340 +181,6 @@ async def can_run(self, ctx: Context, *, call_once: bool = False) -> bool:
181
181
182
182
# type-checker doesn't distinguish between functions and methods
183
183
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
-
518
184
# help command stuff
519
185
520
186
@property
0 commit comments