Source code for lifesaver.commands.cog
# encoding: utf-8
__all__ = ['Cog']
import asyncio
import inspect
import logging
import os
from typing import Type
import aiohttp
from discord.ext import commands
import lifesaver
from lifesaver.config import Config
[docs]class Cog(commands.Cog):
"""The base class for cogs.
It inherits from :class:`discord.ext.commands.Cog`. It differs in that the
bot instance must be passed when constructing the cog. Some helpful
properties and methods are also provided.
"""
def __init__(self, bot):
super().__init__()
#: The bot instance.
self.bot: lifesaver.bot.Bot = bot
#: The lowercased name of the class.
self.name = type(self).__name__.lower()
#: The logger for this cog. The name of the logger is derived from :attr:`name`.
self.log: logging.Logger = logging.getLogger(f'cog.{self.name}')
#: The :class:`aiohttp.ClientSession` for this cog.
self.session = aiohttp.ClientSession(loop=self.loop)
#: The loaded config file. Only present when :meth:`with_config` is used.
self.config = None
if hasattr(self, '__lifesaver_config_cls__'):
path = os.path.join(self.bot.config.cog_config_path, self.name + '.yml')
self.config = self.__lifesaver_config_cls__.load(path)
self._scheduled_tasks = []
self._setup_schedules()
@property
def loop(self):
"""A shortcut to the :class:`asyncio.AbstractEventLoop` that the bot is running on."""
return self.bot.loop
@property
def pool(self):
"""A shortcut to :attr:`lifesaver.bot.BotBase.pool`."""
return self.bot.pool
[docs] @staticmethod
def with_config(config_cls: Type[Config]):
"""Specifies a :class:`lifesaver.config.Config` to load when
constructing the cog.
The config file is loaded according to :attr:`lifesaver.bot.BotConfig.cog_config_path`
and :attr:`name` when constructed. The parsed config resides in :attr:`config`.
"""
def decorator(cls):
setattr(cls, '__lifesaver_config_cls__', config_cls)
return cls
return decorator
def _schedule_method(self, name: str, method):
schedule = method.__lifesaver_schedule__
async def scheduled_function_wrapper():
if 'wait_until_ready' in schedule:
await self.bot.wait_until_ready()
while True:
if 'initial_sleep' in schedule:
await asyncio.sleep(schedule['interval'])
try:
await method()
except Exception:
self.log.exception(f'Exception thrown from scheduled method {name}')
await asyncio.sleep(schedule['interval'])
task = self.bot.loop.create_task(scheduled_function_wrapper())
self._scheduled_tasks.append(task)
def _setup_schedules(self):
methods = inspect.getmembers(self, predicate=inspect.ismethod)
for (name, method) in methods:
if not hasattr(method, '__lifesaver_schedule__'):
continue
self._schedule_method(name, method)
[docs] def cog_unload(self):
"""The special method called upon this cog being unloaded.
It cancels scheduled tasks created through :meth:`every`, and destroys
:attr:`session`.
If you override this, make sure to call ``super().cog_unload()``.
"""
for scheduled_task in self._scheduled_tasks:
self.log.debug('Cancelling scheduled task: %s', scheduled_task)
scheduled_task.cancel()
self.loop.create_task(self.session.close())
[docs] @classmethod
def every(cls, interval: int, **kwargs):
"""A decorator that designates this function to be executed every ``n`` second(s).
Parameters
----------
interval
The time interval in seconds.
wait_until_ready
Waits until the bot is ready before running.
initial_sleep
The number of seconds to sleep before running.
"""
def outer(func):
if not inspect.iscoroutinefunction(func):
raise TypeError('You must use Cog.every on a coroutine.')
func.__lifesaver_schedule__ = {'interval': interval, **kwargs}
return func
return outer