Extensions¶
Introduction¶
Your code's getting pretty big and messy being in a single file, huh? Wouldn't it be nice if you could organise your commands and listeners into separate files?
Well let me introduce you to Extensions
!
Extensions allow you to split your commands and listeners into separate files to allow you to better organise your project. They also come with the additional benefit of being able to reload parts of your bot without shutting down your bot.
For example, you can see the difference of a bot with and without extensions:
Examples:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
Sounds pretty good right? Well, let's go over how you can use them:
Basic Usage¶
Setup¶
Extensions are effectively just another Python file that contains a class that inherits from an object called Extension
, inside this extension.
For example, this is a valid extension file:
1 2 3 4 |
|
Differences from Other Python Discord Libraries
If you come from another Python Discord library, you might have seen that there's no __init__
and setup
function in this example. They still do exist as functions you can use (as discussed later), but interactions.py will do the appropriate logic to handle extensions without either of the two.
For example, the following does the exact same thing as the above extension file:
1 2 3 4 5 6 7 8 9 |
|
Events and Commands¶
You probably want extensions to do a little bit more than just exist though. Most likely, you want some events and commands in here. Thankfully, they're relatively simple to do. Expanding on the example a bit, a slash command looks like this:
1 2 3 4 5 6 |
|
As you can see, they're almost identical to how you declare slash commands in your main bot file, even using the same decorator. The only difference is the self
variable - this is the instance of the extension that the command is being called in, and is standard for functions inside of classes. Events follow a similar principal.
interactions.py will automatically add all commands and events to the bot when you load the extension (discussed later), so you don't need to worry about that.
Accessing the Bot¶
When an extension is loaded, the library automatically sets the bot
for you. With this in mind, you can access your client using self.bot
. Using self.client
also works - they are just aliases to each other.
1 2 3 4 |
|
This also allows you to share data between extensions and the main bot itself. Client
allows storing data in unused attributes, so you can do something like this:
1 2 3 4 5 6 7 8 9 10 11 |
|
Loading the Extension¶
Now that you've got your extension, you need to load it.
Let's pretend the extension is in a file called extension.py
, and it looks like the command example:
1 2 3 4 5 6 |
|
Now, let's say you have a file called main.py
in the same directory that actually has the bot in it:
1 2 |
|
To load the extension, you just need to use bot.load_extension("filename.in_import_style")
before bot.start
. So, in this case, it would look like this:
1 2 3 |
|
And that's it! Your extension is now loaded and ready to go.
"Import Style"¶
In the example above, the filename is passed to load_extension
without the .py
extension. This is because interactions.py actually does an import when loading the extension, so whatever string you give it needs to be a valid Python import path. This means that if you have a file structure like this:
1 2 3 |
|
You would need to pass exts.extension
to load_extension
, as that's the import path to the extension file.
Reloading and Unloading Extensions¶
You can also reload and unload extensions. To do this, you use bot.reload_extension
and bot.unload_extension
respectively.
1 2 |
|
Reloading and unloading extensions allows you to edit your code without restarting the bot, and to remove extensions you no longer need. For example, if you organize your extensions so that moderation commands are in one extension, you can reload that extension (and so only moderation-related commands) as you edit them.
Initialization¶
You may want to do some logic to do when loading a specific extension. For that, you can add the __init__
method, which takes a Client
instance, in your extension:
1 2 3 4 |
|
Asynchronous Initialization¶
As usual, __init__
is synchronous. This may pose problems if you're trying to do something asynchronous in it, so there are various ways of solving it.
If you're okay with only doing the asynchronous logic as the bot is starting up (and never again), there are two methods:
1 2 3 4 |
|
1 2 3 4 5 6 7 |
|
If you want to do the asynchronous logic every time the extension is loaded, you'll need to use asyncio.create_task
:
1 2 3 4 5 6 7 8 9 |
|
Warning about asyncio.create_task
asyncio.create_task
only works if there is an event loop. For the sake of simplicity we won't discuss what that is too much, but the loop is only created when asyncio.run()
is called (as it is in bot.start()
). This means that if you call asyncio.create_task
before bot.start()
, it will not work. If you need to do asynchronous logic before the bot starts, you'll need to load the extension in an asynchronous function and use await bot.astart()
instead of bot.start()
.
For example, this format of loading extensions will allow you to use asyncio.create_task
:
1 2 3 4 5 6 7 8 |
|
Cleanup¶
You may have some logic to do while unloading a specific extension. For that, you can override the drop
method in your extension:
1 2 3 4 |
|
The drop
method is synchronous. If you need to do something asynchronous, you can create a task with asyncio
to do it:
Note about asyncio.create_task
Usually, there's always an event loop running when unloading an extension (even when the bot is shutting down), so you can use asyncio.create_task
without any problems. However, if you are unloading an extension before asyncio.run()
has called, the warning from above applies.
1 2 3 4 5 6 7 8 9 10 |
|
Advanced Usage¶
Loading All Extensions In a Folder¶
Sometimes, you may have a lot of extensions contained in one folder. Writing them all out is both time consuming and not very scalable, so you may want an easier way to load them.
If your folder with all of your extensions is "flat" (only containing Python files for extensions and no subfolders), then your best bet is to use pkgutil.iter_modules
and a for loop:
1 2 3 4 5 6 |
|
iter_modules
finds all modules (which include Python extension files) in the directories provided. By default, this just returns the module/import name without the folder name, so we need to add the folder name back in through the prefix
argument. Note how the folder passed and the prefix are basically the same thing - the prefix just has a period at the the end.
If your folder with all of your extensions is not flat (for example, if you have subfolders in the extension folder containing Python files for extensions), you'll likely want to use glob.glob
instead:
1 2 3 4 5 6 7 |
|
Note that glob.glob
returns the filenames of all files that match the pattern we provided. To turn it into a module/import name, we need to remove the ".py" suffix and replace the slashes with periods. On Windows, you may need to replace the slashes with backslashes instead.
Note About Loading Extensions From a File
While these are two possible ways, they are by no means the only ways of finding all extensions in the folder and loading them. Which method is best method depends on your use case and is purely subjective.
The setup
/teardown
Function¶
You may have noticed that the Extension
in the extension file is simply just a class, with no way of loading it. interactions.py is smart enough to detect Extension
subclasses and use them when loading from a file, but if you want more customization when loading an extension, you'll need to use the setup
function.
The setup
function should be outside of any Extension
subclass, and takes in the bot instance, like so:
1 2 3 4 5 6 |
|
Here, the Extension
subclass is initialized inside the setup
function, and does not need to do any special function to add the extension in beyond being created using the instance.
A similar function can be used for cleanup, called teardown
. It takes no arguments, and should be outside of any Extension
subclass, like so:
1 2 3 4 5 6 |
|
You usually do not need to worry about unloading the specific extensions themselves, as interactions.py will do that for you.
Passing Arguments to Extensions¶
If you would like to pass more than just the bot while initializing an extension, you can pass keyword arguments to the load_extension
method:
1 2 3 4 5 |
|
If you're using a setup
function, the argument will be passed to that function instead, so you'll need to pass it to the Extension
subclass yourself:
1 2 3 4 5 |
|
Extension-Wide Checks¶
Sometimes, it is useful to have a check run before running any command in an extension. Thankfully, all you need to do is use add_ext_check
:
1 2 3 4 5 6 7 8 9 10 11 |
|
Global Checks¶
You may want to have a check that runs on every command in a bot. If all of your commands are in extensions (a good idea), you can use a custom subclass of Extension
to do it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Pre And Post Run Events¶
Pre- and post-run events are similar to checks. They run before and after a command is invoked, respectively:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Extension-Wide Error Handlers¶
Sometimes, you may want to have a custom error handler for all commands in an extension. You can do this by using set_extension_error
:
1 2 3 4 5 6 7 |
|
Error Handling Priority
Only one error handler will run. Similar to CSS, the most specific handler takes precedence. This goes: command error handlers -> extension -> listeners.
Extension Auto Defer¶
You may want to automatically defer all commands in that extension. You can do this by using add_ext_auto_defer
:
1 2 3 |
|
Auto Defer Handling Priority
Similar to errors, only one auto defer will be run, and the most specific auto defer takes precendence. This goes: command auto defer -> extension -> bot.