|
| 1 | +--- |
| 2 | +title: Tasks |
| 3 | +--- |
| 4 | + |
| 5 | +## Concept |
| 6 | + |
| 7 | +So you want to run a background task running at some specified interval? Apparently that's a really common thing to do, whether you're working with some api or handling some background logic don't worry, the Pycord tasks extension is here to make life easier for you! |
| 8 | + |
| 9 | +With the tasks extension you can make background tasks without worrying about the asyncio hell and stuff like "what if my internet dies" and "how do I handle reconnecting" with the added benifit of alot of useful additions that are one line of code away! |
| 10 | + |
| 11 | +### Example Usage |
| 12 | + |
| 13 | +```py |
| 14 | +import discord |
| 15 | +from discord.ext import tasks |
| 16 | + |
| 17 | +bot = discord.Bot() |
| 18 | + |
| 19 | +@bot.event |
| 20 | +async def on_ready(): |
| 21 | + very_useful_task.start() |
| 22 | + |
| 23 | +@tasks.loop(seconds=5) |
| 24 | +async def very_useful_task(): |
| 25 | + print('doing very useful stuff.') |
| 26 | + |
| 27 | +bot.run("TOKEN") |
| 28 | +``` |
| 29 | + |
| 30 | +If you do run this code in your terminal you should see something like this after about 15 seconds: |
| 31 | + |
| 32 | +``` |
| 33 | +doing very useful stuff. |
| 34 | +doing very useful stuff. |
| 35 | +doing very useful stuff. |
| 36 | +``` |
| 37 | + |
| 38 | +For a more usful example here's a task in a cog context ripped straight from the [docs](https://docs.pycord.dev/en/master/ext/tasks/index.html#recepies): |
| 39 | + |
| 40 | +```py |
| 41 | +from discord.ext import tasks, commands |
| 42 | + |
| 43 | +class MyCog(commands.Cog): |
| 44 | + def __init__(self): |
| 45 | + self.index = 0 |
| 46 | + self.printer.start() |
| 47 | + |
| 48 | + def cog_unload(self): |
| 49 | + self.printer.cancel() |
| 50 | + |
| 51 | + @tasks.loop(seconds=5) |
| 52 | + async def printer(self): |
| 53 | + print(self.index) |
| 54 | + self.index += 1 |
| 55 | +``` |
| 56 | + |
| 57 | +As you'd expect this will increnent a number and print it out every 5 seconds like so: |
| 58 | +```sh |
| 59 | +0 |
| 60 | +# 5 seconds later |
| 61 | +1 |
| 62 | +# 5 more seconds later |
| 63 | +2 |
| 64 | +# ... |
| 65 | +``` |
| 66 | + |
| 67 | +## [Tasks](https://docs.pycord.dev/en/master/ext/tasks/index.html) |
| 68 | + |
| 69 | +Now let's get into the nitty-gritty of tasks. |
| 70 | + |
| 71 | +As you've seen tasks can work in both outer scope and class contexts and the handle is roughly the same, you define a task using the `tasks.loop` decorator and use the `start` method to start it, difference is you add the `self` argument when in a class context so since most people have all their bot's code in Cogs all the following code blocks will be in a Cog context to make it easy to copy it then apply whatever modifications you might need! |
| 72 | + |
| 73 | +### [Creating a task](https://docs.pycord.dev/en/master/ext/tasks/index.html#discord.ext.tasks.loop) |
| 74 | + |
| 75 | +A look at `discord.ext.tasks.loop`'s defenition in the doccumentation reveals that: |
| 76 | +1. As you've seen before it expects the time between each execution, that however doesn't have to be in seconds as there are `seconds`, `minutes` and `hours` parameters to make it easier for us, they can also be floats in case you want ultra percision. |
| 77 | +2. You can also pass in a `datetime.time` object or a sequence of them in the `time` parameter which will make it run at the specified time(s). |
| 78 | +3. You can pass in how many times a loop runs before it stops by passing in the `count` parameter, you can use `None` to make it run forever which is also the default. |
| 79 | +4. The loop function ***must*** be a coroutine. |
| 80 | + |
| 81 | +These are all really useful and they aren't the only parameters so if you wanna know more about them check out the [docs](https://docs.pycord.dev/en/master/ext/tasks/index.html#discord.ext.tasks.loop)! |
| 82 | + |
| 83 | +### Attributes |
| 84 | + |
| 85 | +A task has the following attributes: |
| 86 | +- `current_loop`: The current loop the task is on. |
| 87 | +- `hours`, `minutes`, `seconds`: attributes that represent the time between each execution. |
| 88 | +- `time`: A list of datetime.time objects that represent the times the task will run, returns `None` if no datetime objects were passed. |
| 89 | +- `next_iteration`: A `datetime.datetime` object that represents the next time the next iteration of the task will run, can return `None` if the task stopped running. |
| 90 | + |
| 91 | +These attributes serve as a really powerful asset to get info about your loop. |
| 92 | + |
| 93 | +### Example |
| 94 | + |
| 95 | +Now let's create a cog that handles a leaderboard we have in our bot using Tasks then explain what we did after that and also provide a refresher of how slash commands groups work in cogs. |
| 96 | + |
| 97 | +For the sake of this example let's pretend that we have a leaderboard module that does all the leaderboard handling for us and that computing the leaderboard is very expensive computationally wise so we want to store one version of it that gets regularly updated insetad of generating it every time someone calls the `/leaderboard view` command. |
| 98 | +```py |
| 99 | +from discord.ext import tasks, commands |
| 100 | +from discord.commands import SlashCommandGroup, CommandPermissions |
| 101 | + |
| 102 | +from leaderboard import * # Mock leaderboard module. |
| 103 | + |
| 104 | +class LeaderboardCog(commands.Cog): |
| 105 | + def __init__(self, bot): |
| 106 | + self.bot = bot |
| 107 | + self.update_leaderboard.start() |
| 108 | + self.leaderboard_embed = generate_leaderboard_embed() |
| 109 | + |
| 110 | + leaderboard = SlashCommandGroup(name='leaderboard', description='Leaderboard commands.') |
| 111 | + |
| 112 | + @tasks.loop(minutes=10) |
| 113 | + async def update_leaderboard(self): |
| 114 | + print('Updating leaderboard...') |
| 115 | + await update_leaderboard() |
| 116 | + self.leaderboard_embed = generate_leaderboard_embed() |
| 117 | + |
| 118 | + @update_leaderboard.before_loop |
| 119 | + async def before_update_leaderboard(self): |
| 120 | + await self.bot.wait_until_ready() |
| 121 | + print("About to start updating leaderboard.") |
| 122 | + |
| 123 | + @update_leaderboard.after_loop |
| 124 | + async def after_update_leaderboard(self): |
| 125 | + leaderboard_cleanup() |
| 126 | + print("Stopped updating leaderboard.") |
| 127 | + |
| 128 | + @update_leaderboard.error |
| 129 | + async def update_leaderboard_error(self, error): |
| 130 | + print(f"Oh no, an error occured while updating the leaderboard. Error: {error}") |
| 131 | + |
| 132 | + @leaderboard.command() |
| 133 | + async def view(self, ctx): |
| 134 | + await ctx.respond(emebed=self.leaderboard_embed) |
| 135 | + |
| 136 | + @leaderboard.command() |
| 137 | + async def update_info(self, ctx): |
| 138 | + await ctx.respond(f"""***Leaderboard Info***\n |
| 139 | + Last update: {(600 - self.update_leaderboard.next_iteration.timestamp())//60} minutes ago.\n |
| 140 | + Next update: in {self.update_leaderboard.next_iteration.timestamp() // 60} minutes.\n |
| 141 | + Time between loops: {self.update_leaderboard.minutes} minutes.\n |
| 142 | + Times updated this session: {self.update_leaderboard.current_loop}. |
| 143 | + Running? {self.update_leaderboard.is_running()} |
| 144 | + Failed? {self.update_leaderboard.failed()}""", ephemeral=True) |
| 145 | + |
| 146 | + # Now for the owner only commands: |
| 147 | + |
| 148 | + leaderboard_config = SlashCommandGroup(name="leaderboard_config", description="Leaderboard configuration commands", permission=[CommandPermissions("owner", 2, True)]) |
| 149 | + |
| 150 | + @leaderboard_config.command() |
| 151 | + async def set_time_between_updates(self, ctx, minutes: int): |
| 152 | + self.update_leaderboard.change_interval(minutes=minutes) |
| 153 | + await ctx.respond(f"Set time between updates to {minutes} minutes.") |
| 154 | + |
| 155 | + @leaderboard_config.command() |
| 156 | + async def stop(self, ctx): |
| 157 | + self.update_leaderboard.stop() |
| 158 | + await ctx.respond("Stopped the leaderboard update.") |
| 159 | + |
| 160 | + @leaderboard_config.command() |
| 161 | + async def restart(self, ctx): |
| 162 | + self.update_leaderboard.restart() |
| 163 | + await ctx.respond("Restarted the leaderboard update.") |
| 164 | + |
| 165 | + @leaderboard_config.command() |
| 166 | + |
| 167 | +def setup(bot): |
| 168 | + bot.add_cog(LeaderboardCog(bot)) |
| 169 | +``` |
| 170 | + |
| 171 | +Few, that was quite a bit of code. |
| 172 | + |
| 173 | +Now to explain what's going on: |
| 174 | + |
| 175 | +First things first we create a cog and in its `__init__` function we start the `update_leaderboard` task and generate the first instance of our leaderboard's embed. |
| 176 | + |
| 177 | +After that we define the `update_leaderboard` task using the `loop` decorator and we make it run every ten minutes by passing 10 to the `minutes` parameter. |
| 178 | + |
| 179 | +Then that we define the `before_update_leaderboard` task using the `before_loop` decorator and we make it wait until the bot is ready before starting the task. |
| 180 | + |
| 181 | +Next up we define the `after_update_leaderboard` task using the `after_loop` decorator and we make it clean up the leaderboard when the loop finally stops running. |
| 182 | + |
| 183 | +Then we define the `update_leaderboard_error` task using the `error` decorator and we make it print any errors that may occur while we update our leaderboard to the console. |
| 184 | + |
| 185 | +Then we get into the fun stuff, we create a slash command group using the `SlashCommandGroup` class and name it `leaderboard`, we then create the `view` sub-command which sends the embed generated when the leaderboard is updates and `update_info` which shows alot of data about the task using some of the attributes and functions avaiable to us. |
| 186 | + |
| 187 | +We also create a slash command group called `leaderboard_config` which is only available to the owner of the bot. |
| 188 | + |
| 189 | +Then we create the `set_time_between_updates` sub-command which takes in the time in minutes and changes the time between updates of the leaderboard using the `change_interval` method, the `stop` sub-command which stops the task using the `stop` method and `restart` which restarts the task using the `restart` method. |
| 190 | + |
| 191 | +Finally we add the `LeaderboardCog` cog to the bot and we're done! |
| 192 | + |
| 193 | +I'd highly reccomend you check the tasks extension [doccumentation](https://docs.pycord.dev/en/master/ext/tasks/index.html) for more info on how to use them and what other functions they have. |
0 commit comments