Skip to content

Commit

Permalink
feat: add once decorator to discord.Client (#1940)
Browse files Browse the repository at this point in the history
Signed-off-by: Om <92863779+Om1609@users.noreply.github.com>
Co-authored-by: BobDotCom <71356958+BobDotCom@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Feb 26, 2023
1 parent 2e09718 commit 13dbcdd
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -15,6 +15,8 @@ These changes are available on the `master` branch, but have not yet been releas
- Added new events `on_bridge_command`, `on_bridge_command_completion`, and
`on_bridge_command_error`.
([#1916](https://github.com/Pycord-Development/pycord/pull/1916))
- Added the `@client.once()` decorator, which serves as a one-time event listener.
([#1940](https://github.com/Pycord-Development/pycord/pull/1940))

### Fixed

Expand Down
58 changes: 58 additions & 0 deletions discord/client.py
Expand Up @@ -1286,6 +1286,64 @@ async def on_ready():
_log.debug("%s has successfully been registered as an event", coro.__name__)
return coro

def once(
self, name: str = MISSING, check: Callable[..., bool] | None = None
) -> Coro:
"""A decorator that registers an event to listen to only once.
You can find more info about the events on the :ref:`documentation below <discord-api-events>`.
The events must be a :ref:`coroutine <coroutine>`, if not, :exc:`TypeError` is raised.
Parameters
----------
name: :class:`str`
The name of the event we want to listen to. This is passed to
:py:meth:`~discord.Client.wait_for`. Defaults to ``func.__name__``.
check: Optional[Callable[..., :class:`bool`]]
A predicate to check what to wait for. The arguments must meet the
parameters of the event being waited for.
Raises
------
TypeError
The coroutine passed is not actually a coroutine.
Example
-------
.. code-block:: python3
@client.once()
async def ready():
print('Ready!')
"""

def decorator(func: Coro) -> Coro:
if not asyncio.iscoroutinefunction(func):
raise TypeError("event registered must be a coroutine function")

async def wrapped() -> None:
nonlocal name
nonlocal check

name = func.__name__ if name is MISSING else name

args = await self.wait_for(name, check=check)

arg_len = func.__code__.co_argcount
if arg_len == 0 and args is None:
await func()
elif arg_len == 1:
await func(args)
else:
await func(*args)

self.loop.create_task(wrapped())
return func

return decorator

async def change_presence(
self,
*,
Expand Down
10 changes: 8 additions & 2 deletions docs/api/clients.rst
Expand Up @@ -10,7 +10,7 @@ Bots
.. autoclass:: Bot
:members:
:inherited-members:
:exclude-members: command, event, message_command, slash_command, user_command, listen
:exclude-members: command, event, message_command, slash_command, user_command, listen, once

.. automethod:: Bot.command(**kwargs)
:decorator:
Expand All @@ -30,6 +30,9 @@ Bots
.. automethod:: Bot.listen(name=None)
:decorator:

.. automethod:: Bot.once(name=None, check=None)
:decorator:

.. attributetable:: AutoShardedBot
.. autoclass:: AutoShardedBot
:members:
Expand All @@ -41,14 +44,17 @@ Clients
.. attributetable:: Client
.. autoclass:: Client
:members:
:exclude-members: fetch_guilds, event
:exclude-members: fetch_guilds, event, once

.. automethod:: Client.event()
:decorator:

.. automethod:: Client.fetch_guilds
:async-for:

.. automethod:: Client.once(name=None, check=None)
:decorator:

.. attributetable:: AutoShardedClient
.. autoclass:: AutoShardedClient
:members:
12 changes: 9 additions & 3 deletions docs/api/events.rst
Expand Up @@ -7,10 +7,11 @@ Event Reference

This section outlines the different types of events listened by :class:`Client`.

There are 3 ways to register an event, the first way is through the use of
There are 4 ways to register an event, the first way is through the use of
:meth:`Client.event`. The second way is through subclassing :class:`Client` and
overriding the specific events. The third way is through the use of :meth:`Client.listen`, which can be used to assign multiple
event handlers instead of only one like in :meth:`Client.event`. For example:
overriding the specific events. The third way is through the use of :meth:`Client.listen`,
which can be used to assign multiple event handlers instead of only one like in :meth:`Client.event`.
The fourth way is through the use of :meth:`Client.once`, which serves as a one-time event listener. For example:

.. code-block:: python
:emphasize-lines: 17, 22
Expand Down Expand Up @@ -40,6 +41,11 @@ event handlers instead of only one like in :meth:`Client.event`. For example:
async def on_message(message: discord.Message):
print(f"Received {message.content}")
# Runs only for the 1st 'on_message' event. Can be useful for listening to 'on_ready'
@client.once()
async def message(message: discord.Message):
print(f"Received {message.content}")
If an event handler raises an exception, :func:`on_error` will be called
to handle it, which defaults to print a traceback and ignoring the exception.
Expand Down
5 changes: 4 additions & 1 deletion docs/ext/commands/api.rst
Expand Up @@ -23,7 +23,7 @@ Bot
.. autoclass:: discord.ext.commands.Bot
:members:
:inherited-members:
:exclude-members: after_invoke, before_invoke, check, check_once, command, event, group, listen
:exclude-members: after_invoke, before_invoke, check, check_once, command, event, group, listen, once

.. automethod:: Bot.after_invoke()
:decorator:
Expand All @@ -49,6 +49,9 @@ Bot
.. automethod:: Bot.listen(name=None)
:decorator:

.. automethod:: Bot.once(name=None, check=None)
:decorator:

AutoShardedBot
~~~~~~~~~~~~~~

Expand Down

0 comments on commit 13dbcdd

Please sign in to comment.