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.

examples/manager.py - creating a Bot object#
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.

examples/manager.py - create a custom component manager#
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.

examples/manager.py - create a manager object and register all components#
manager = MyManager(bot)
manager.basic_config()

Then we make a simple component. For a more detailed explanation, see the ‘button.py’ example.

examples/manager.py - creating a component#
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.

examples/manager.py - creating a command and send 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#

examples/manager.py#
 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"))