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 | |
Attributes:
| Name | Type | Description |
|---|---|---|
bot |
Client |
A reference to the client |
name |
str |
The name of this Extension ( |
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 | |
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 | |
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 | |
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