Skip to content

Extension

class Extension

A class that allows you to separate your commands and listeners into separate files. Skins require an entrypoint in the same file called setup, this function allows client to load the Extension.

Example Usage:
1
2
3
4
5
6
7
class ExampleExt(Extension):
    def __init__(self, bot):
        print("Extension Created")

    @prefixed_command()
    async def some_command(self, context):
        await ctx.send(f"I was sent from a extension called {self.name}")

Attributes:

Name Type Description
bot Client

A reference to the client

name str

The name of this Extension (read-only)

description str

A description of this Extension

extension_checks str

A list of checks to be ran on any command in this extension

extension_prerun List

A list of coroutines to be run before any command in this extension

extension_postrun List

A list of coroutines to be run after any command in this extension

Source code in naff/models/naff/extension.py
class Extension:
    """
    A class that allows you to separate your commands and listeners into separate files. Skins require an entrypoint in the same file called `setup`, this function allows client to load the Extension.

    ??? Hint "Example Usage:"
        ```python
        class ExampleExt(Extension):
            def __init__(self, bot):
                print("Extension Created")

            @prefixed_command()
            async def some_command(self, context):
                await ctx.send(f"I was sent from a extension called {self.name}")
        ```

    Attributes:
        bot Client: A reference to the client
        name str: The name of this Extension (`read-only`)
        description str: A description of this Extension
        extension_checks str: A list of checks to be ran on any command in this extension
        extension_prerun List: A list of coroutines to be run before any command in this extension
        extension_postrun List: A list of coroutines to be run after any command in this extension

    """

    bot: "Client"
    name: str
    extension_name: str
    description: str
    extension_checks: List
    extension_prerun: List
    extension_postrun: List
    extension_error: Optional[Callable[..., Coroutine]]
    _commands: List
    _listeners: List
    auto_defer: "AutoDefer"

    def __new__(cls, bot: "Client", *args, **kwargs) -> "Extension":
        new_cls = super().__new__(cls)
        new_cls.bot = bot
        new_cls.name = cls.__name__
        new_cls.extension_checks = []
        new_cls.extension_prerun = []
        new_cls.extension_postrun = []
        new_cls.extension_error = None
        new_cls.auto_defer = MISSING

        new_cls.description = kwargs.get("Description", None)
        if not new_cls.description:
            new_cls.description = inspect.cleandoc(cls.__doc__) if cls.__doc__ else None

        # load commands from class
        new_cls._commands = []
        new_cls._listeners = []

        for _name, val in inspect.getmembers(
            new_cls, predicate=lambda x: isinstance(x, (naff.BaseCommand, naff.Listener, Task))
        ):
            if isinstance(val, naff.BaseCommand):
                val.extension = new_cls
                val = wrap_partial(val, new_cls)

                if not isinstance(val, naff.PrefixedCommand) or not val.is_subcommand:
                    # we do not want to add prefixed subcommands
                    new_cls._commands.append(val)

                    if isinstance(val, naff.ModalCommand):
                        bot.add_modal_callback(val)
                    elif isinstance(val, naff.ComponentCommand):
                        bot.add_component_callback(val)
                    elif isinstance(val, naff.HybridCommand):
                        bot.add_hybrid_command(val)
                    elif isinstance(val, naff.InteractionCommand):
                        bot.add_interaction(val)
                    else:
                        bot.add_prefixed_command(val)

            elif isinstance(val, naff.Listener):
                val = val.copy_with_binding(new_cls)
                bot.add_listener(val)
                new_cls.listeners.append(val)
            elif isinstance(val, Task):
                wrap_partial(val, new_cls)

        logger.debug(
            f"{len(new_cls._commands)} commands and {len(new_cls.listeners)} listeners"
            f" have been loaded from `{new_cls.name}`"
        )

        new_cls.extension_name = inspect.getmodule(new_cls).__name__
        new_cls.bot.ext[new_cls.name] = new_cls

        if hasattr(new_cls, "async_start"):
            if inspect.iscoroutinefunction(new_cls.async_start):
                bot.async_startup_tasks.append(new_cls.async_start())
            else:
                raise TypeError("async_start is a reserved method and must be a coroutine")

        return new_cls

    @property
    def __name__(self) -> str:
        return self.name

    @property
    def commands(self) -> List["BaseCommand"]:
        """Get the commands from this Extension."""
        return self._commands

    @property
    def listeners(self) -> List["Listener"]:
        """Get the listeners from this Extension."""
        return self._listeners

    def drop(self) -> None:
        """Called when this Extension is being removed."""
        for func in self._commands:
            if isinstance(func, naff.ModalCommand):
                for listener in func.listeners:
                    # noinspection PyProtectedMember
                    self.bot._modal_callbacks.pop(listener)
            elif isinstance(func, naff.ComponentCommand):
                for listener in func.listeners:
                    # noinspection PyProtectedMember
                    self.bot._component_callbacks.pop(listener)
            elif isinstance(func, naff.InteractionCommand):
                for scope in func.scopes:
                    if self.bot.interactions.get(scope):
                        self.bot.interactions[scope].pop(func.resolved_name, [])

                if isinstance(func, naff.HybridCommand):
                    # here's where things get complicated - we need to unload the prefixed command
                    # by necessity, there's a lot of logic here to determine what needs to be unloaded
                    if not func.callback:  # not like it was added
                        return

                    if func.is_subcommand:
                        prefixed_base = self.bot.prefixed_commands.get(str(func.name))
                        _base_cmd = prefixed_base
                        if not prefixed_base:
                            # if something weird happened here, here's a safeguard
                            continue

                        if func.group_name:
                            prefixed_base = prefixed_base.subcommands.get(str(func.group_name))
                            if not prefixed_base:
                                continue

                        prefixed_base.remove_command(str(func.sub_cmd_name))

                        if not prefixed_base.subcommands:
                            # the base cmd is now empty, delete it
                            if func.group_name:
                                _base_cmd.remove_command(str(func.group_name))  # type: ignore

                                # and now the base command is empty
                                if not _base_cmd.subcommands:  # type: ignore
                                    # in case you're curious, i did try to put the below behavior
                                    # in a function here, but then it turns out a weird python
                                    # bug can happen if i did that
                                    if cmd := self.bot.prefixed_commands.pop(str(func.name), None):
                                        for alias in cmd.aliases:
                                            self.bot.prefixed_commands.pop(alias, None)

                            elif cmd := self.bot.prefixed_commands.pop(str(func.name), None):
                                for alias in cmd.aliases:
                                    self.bot.prefixed_commands.pop(alias, None)

                    elif cmd := self.bot.prefixed_commands.pop(str(func.name), None):
                        for alias in cmd.aliases:
                            self.bot.prefixed_commands.pop(alias, None)

            elif isinstance(func, naff.PrefixedCommand):
                if not func.is_subcommand:
                    self.bot.prefixed_commands.pop(func.name, None)
                    for alias in func.aliases:
                        self.bot.prefixed_commands.pop(alias, None)
        for func in self.listeners:
            self.bot.listeners[func.event].remove(func)

        self.bot.ext.pop(self.name, None)
        logger.debug(f"{self.name} has been drop")

    def add_ext_auto_defer(self, ephemeral: bool = False, time_until_defer: float = 0.0) -> None:
        """
        Add a auto defer for all commands in this extension.

        Args:
            ephemeral: Should the command be deferred as ephemeral
            time_until_defer: How long to wait before deferring automatically

        """
        self.auto_defer = naff.AutoDefer(enabled=True, ephemeral=ephemeral, time_until_defer=time_until_defer)

    def add_ext_check(self, coroutine: Callable[["Context"], Awaitable[bool]]) -> None:
        """
        Add a coroutine as a check for all commands in this extension to run. This coroutine must take **only** the parameter `context`.

        ??? Hint "Example Usage:"
            ```python
            def __init__(self, bot):
                self.add_ext_check(self.example)

            @staticmethod
            async def example(context: Context):
                if context.author.id == 123456789:
                    return True
                return False
            ```
        Args:
            coroutine: The coroutine to use as a check

        """
        if not asyncio.iscoroutinefunction(coroutine):
            raise TypeError("Check must be a coroutine")

        if not self.extension_checks:
            self.extension_checks = []

        self.extension_checks.append(coroutine)

    def add_extension_prerun(self, coroutine: Callable[..., Coroutine]) -> None:
        """
        Add a coroutine to be run **before** all commands in this Extension.

        Note:
            Pre-runs will **only** be run if the commands checks pass

        ??? Hint "Example Usage:"
            ```python
            def __init__(self, bot):
                self.add_extension_prerun(self.example)

            async def example(self, context: Context):
                await ctx.send("I ran first")
            ```

        Args:
            coroutine: The coroutine to run

        """
        if not asyncio.iscoroutinefunction(coroutine):
            raise TypeError("Callback must be a coroutine")

        if not self.extension_prerun:
            self.extension_prerun = []
        self.extension_prerun.append(coroutine)

    def add_extension_postrun(self, coroutine: Callable[..., Coroutine]) -> None:
        """
        Add a coroutine to be run **after** all commands in this Extension.

        ??? Hint "Example Usage:"
            ```python
            def __init__(self, bot):
                self.add_extension_postrun(self.example)

            async def example(self, context: Context):
                await ctx.send("I ran first")
            ```

        Args:
            coroutine: The coroutine to run

        """
        if not asyncio.iscoroutinefunction(coroutine):
            raise TypeError("Callback must be a coroutine")

        if not self.extension_postrun:
            self.extension_postrun = []
        self.extension_postrun.append(coroutine)

    def set_extension_error(self, coroutine: Callable[..., Coroutine]) -> None:
        """
        Add a coroutine to handle any exceptions raised in this extension.

        ??? Hint "Example Usage:"
            ```python
            def __init__(self, bot):
                self.set_extension_error(self.example)

        Args:
            coroutine: The coroutine to run

        """
        if not asyncio.iscoroutinefunction(coroutine):
            raise TypeError("Callback must be a coroutine")

        if self.extension_error:
            logger.warning("Extension error callback has been overridden!")
        self.extension_error = coroutine

property readonly commands: List[BaseCommand]

Get the commands from this Extension.

property readonly listeners: List[Listener]

Get the listeners from this Extension.

method drop(self)

Called when this Extension is being removed.

Source code in naff/models/naff/extension.py
def drop(self) -> None:
    """Called when this Extension is being removed."""
    for func in self._commands:
        if isinstance(func, naff.ModalCommand):
            for listener in func.listeners:
                # noinspection PyProtectedMember
                self.bot._modal_callbacks.pop(listener)
        elif isinstance(func, naff.ComponentCommand):
            for listener in func.listeners:
                # noinspection PyProtectedMember
                self.bot._component_callbacks.pop(listener)
        elif isinstance(func, naff.InteractionCommand):
            for scope in func.scopes:
                if self.bot.interactions.get(scope):
                    self.bot.interactions[scope].pop(func.resolved_name, [])

            if isinstance(func, naff.HybridCommand):
                # here's where things get complicated - we need to unload the prefixed command
                # by necessity, there's a lot of logic here to determine what needs to be unloaded
                if not func.callback:  # not like it was added
                    return

                if func.is_subcommand:
                    prefixed_base = self.bot.prefixed_commands.get(str(func.name))
                    _base_cmd = prefixed_base
                    if not prefixed_base:
                        # if something weird happened here, here's a safeguard
                        continue

                    if func.group_name:
                        prefixed_base = prefixed_base.subcommands.get(str(func.group_name))
                        if not prefixed_base:
                            continue

                    prefixed_base.remove_command(str(func.sub_cmd_name))

                    if not prefixed_base.subcommands:
                        # the base cmd is now empty, delete it
                        if func.group_name:
                            _base_cmd.remove_command(str(func.group_name))  # type: ignore

                            # and now the base command is empty
                            if not _base_cmd.subcommands:  # type: ignore
                                # in case you're curious, i did try to put the below behavior
                                # in a function here, but then it turns out a weird python
                                # bug can happen if i did that
                                if cmd := self.bot.prefixed_commands.pop(str(func.name), None):
                                    for alias in cmd.aliases:
                                        self.bot.prefixed_commands.pop(alias, None)

                        elif cmd := self.bot.prefixed_commands.pop(str(func.name), None):
                            for alias in cmd.aliases:
                                self.bot.prefixed_commands.pop(alias, None)

                elif cmd := self.bot.prefixed_commands.pop(str(func.name), None):
                    for alias in cmd.aliases:
                        self.bot.prefixed_commands.pop(alias, None)

        elif isinstance(func, naff.PrefixedCommand):
            if not func.is_subcommand:
                self.bot.prefixed_commands.pop(func.name, None)
                for alias in func.aliases:
                    self.bot.prefixed_commands.pop(alias, None)
    for func in self.listeners:
        self.bot.listeners[func.event].remove(func)

    self.bot.ext.pop(self.name, None)
    logger.debug(f"{self.name} has been drop")

method add_ext_auto_defer(self, ephemeral, time_until_defer)

Add a auto defer for all commands in this extension.

Parameters:

Name Type Description Default
ephemeral bool

Should the command be deferred as ephemeral

False
time_until_defer float

How long to wait before deferring automatically

0.0
Source code in naff/models/naff/extension.py
def add_ext_auto_defer(self, ephemeral: bool = False, time_until_defer: float = 0.0) -> None:
    """
    Add a auto defer for all commands in this extension.

    Args:
        ephemeral: Should the command be deferred as ephemeral
        time_until_defer: How long to wait before deferring automatically

    """
    self.auto_defer = naff.AutoDefer(enabled=True, ephemeral=ephemeral, time_until_defer=time_until_defer)

method add_ext_check(self, coroutine)

Add a coroutine as a check for all commands in this extension to run. This coroutine must take only the parameter context.

Example Usage:
1
2
3
4
5
6
7
8
def __init__(self, bot):
    self.add_ext_check(self.example)

@staticmethod
async def example(context: Context):
    if context.author.id == 123456789:
        return True
    return False

Parameters:

Name Type Description Default
coroutine Callable[[Context], Awaitable[bool]]

The coroutine to use as a check

required
Source code in naff/models/naff/extension.py
def add_ext_check(self, coroutine: Callable[["Context"], Awaitable[bool]]) -> None:
    """
    Add a coroutine as a check for all commands in this extension to run. This coroutine must take **only** the parameter `context`.

    ??? Hint "Example Usage:"
        ```python
        def __init__(self, bot):
            self.add_ext_check(self.example)

        @staticmethod
        async def example(context: Context):
            if context.author.id == 123456789:
                return True
            return False
        ```
    Args:
        coroutine: The coroutine to use as a check

    """
    if not asyncio.iscoroutinefunction(coroutine):
        raise TypeError("Check must be a coroutine")

    if not self.extension_checks:
        self.extension_checks = []

    self.extension_checks.append(coroutine)

method add_extension_prerun(self, coroutine)

Add a coroutine to be run before all commands in this Extension.

Note

Pre-runs will only be run if the commands checks pass

Example Usage:
1
2
3
4
5
def __init__(self, bot):
    self.add_extension_prerun(self.example)

async def example(self, context: Context):
    await ctx.send("I ran first")

Parameters:

Name Type Description Default
coroutine Callable[..., Coroutine]

The coroutine to run

required
Source code in naff/models/naff/extension.py
def add_extension_prerun(self, coroutine: Callable[..., Coroutine]) -> None:
    """
    Add a coroutine to be run **before** all commands in this Extension.

    Note:
        Pre-runs will **only** be run if the commands checks pass

    ??? Hint "Example Usage:"
        ```python
        def __init__(self, bot):
            self.add_extension_prerun(self.example)

        async def example(self, context: Context):
            await ctx.send("I ran first")
        ```

    Args:
        coroutine: The coroutine to run

    """
    if not asyncio.iscoroutinefunction(coroutine):
        raise TypeError("Callback must be a coroutine")

    if not self.extension_prerun:
        self.extension_prerun = []
    self.extension_prerun.append(coroutine)

method add_extension_postrun(self, coroutine)

Add a coroutine to be run after all commands in this Extension.

Example Usage:
1
2
3
4
5
def __init__(self, bot):
    self.add_extension_postrun(self.example)

async def example(self, context: Context):
    await ctx.send("I ran first")

Parameters:

Name Type Description Default
coroutine Callable[..., Coroutine]

The coroutine to run

required
Source code in naff/models/naff/extension.py
def add_extension_postrun(self, coroutine: Callable[..., Coroutine]) -> None:
    """
    Add a coroutine to be run **after** all commands in this Extension.

    ??? Hint "Example Usage:"
        ```python
        def __init__(self, bot):
            self.add_extension_postrun(self.example)

        async def example(self, context: Context):
            await ctx.send("I ran first")
        ```

    Args:
        coroutine: The coroutine to run

    """
    if not asyncio.iscoroutinefunction(coroutine):
        raise TypeError("Callback must be a coroutine")

    if not self.extension_postrun:
        self.extension_postrun = []
    self.extension_postrun.append(coroutine)

method set_extension_error(self, coroutine)

Add a coroutine to handle any exceptions raised in this extension.

Example Usage:

```python def init(self, bot): self.set_extension_error(self.example)

Args: coroutine: The coroutine to run

Source code in naff/models/naff/extension.py
def set_extension_error(self, coroutine: Callable[..., Coroutine]) -> None:
    """
    Add a coroutine to handle any exceptions raised in this extension.

    ??? Hint "Example Usage:"
        ```python
        def __init__(self, bot):
            self.set_extension_error(self.example)

    Args:
        coroutine: The coroutine to run

    """
    if not asyncio.iscoroutinefunction(coroutine):
        raise TypeError("Callback must be a coroutine")

    if self.extension_error:
        logger.warning("Extension error callback has been overridden!")
    self.extension_error = coroutine