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