Component Manager#
A simple example on the use of custom managers with disnake-ext-components.
Warning
Component managers are fairly complex and leverage weak references to their components in a lot of different places. For many things to be able to unload and/or be garbage-collected appropriately, it is imperative that there are no external references to components.
Making custom component managers is therefore very sensitive to introducing a myriad of reference cycles and potentially even memory leaks, so discretion is advised.
As far as safety goes, overwriting ‘ComponentManager.callback_hook’ as is done in this example is fairly safe, provided you do not store the components externally.
First and foremost, we create a bot as per usual. Since we don’t need any
prefix command capabilities, we opt for an disnake.ext.commands.InteractionBot.
import contextlib
import os
import time
import typing
import disnake
from disnake.ext import commands, components
bot = commands.InteractionBot()
Next, we make a custom component manager.
For the sake of this example, we will make a custom manager that prints the runtime of a component callback.
class MyManager(components.ComponentManager):
@contextlib.contextmanager
def callback_hook(
self, component: components.api.RichComponent
) -> typing.Generator[None, None, None]:
start_time = time.perf_counter()
yield # The component callback will be invoked here
print(
f"Component {type(component).__name__} finished running in"
f" {(time.perf_counter() - start_time) * 1000:.3f} milliseconds."
)
Note
If you wish to stop a component from running, you can raise an exception before the yield statement.
Create a manager object and register all components (current and future) to this manager.
manager = MyManager(bot)
manager.basic_config()
Then we make a simple component. For a more detailed explanation, see the ‘button.py’ example.
class MyButton(components.RichButton):
label = "0"
count: int
async def callback(self, interaction: components.MessageInteraction) -> None:
self.count += 1
self.label = str(self.count)
await interaction.response.edit_message(components=self)
Finally, we make a command that sends the component.
@bot.slash_command() # pyright: ignore # still some unknowns in disnake
async def test_button(inter: disnake.CommandInteraction) -> None:
wrapped = components.wrap_interaction(inter)
component = MyButton(count=0)
await wrapped.send(components=component)
bot.run(os.getenv("EXAMPLE_TOKEN"))
Source Code#
1import contextlib
2import os
3import time
4import typing
5
6import disnake
7from disnake.ext import commands, components
8
9bot = commands.InteractionBot()
10
11
12class MyManager(components.ComponentManager):
13 @contextlib.contextmanager
14 def callback_hook(
15 self, component: components.api.RichComponent
16 ) -> typing.Generator[None, None, None]:
17 start_time = time.perf_counter()
18
19 yield # The component callback will be invoked here
20
21 print(
22 f"Component {type(component).__name__} finished running in"
23 f" {(time.perf_counter() - start_time) * 1000:.3f} milliseconds."
24 )
25
26
27manager = MyManager(bot)
28manager.basic_config()
29
30
31class MyButton(components.RichButton):
32 label = "0"
33
34 count: int
35
36 async def callback(self, interaction: components.MessageInteraction) -> None:
37 self.count += 1
38 self.label = str(self.count)
39
40 await interaction.response.edit_message(components=self)
41
42
43@bot.slash_command() # pyright: ignore # still some unknowns in disnake
44async def test_button(inter: disnake.CommandInteraction) -> None:
45 wrapped = components.wrap_interaction(inter)
46 component = MyButton(count=0)
47
48 await wrapped.send(components=component)
49
50
51bot.run(os.getenv("EXAMPLE_TOKEN"))