Skip to content

Commit

Permalink
feat!: one time event listeners (#1957)
Browse files Browse the repository at this point in the history
Signed-off-by: Om <92863779+Om1609@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: VincentRPS <vincentbusiness55@gmail.com>
Co-authored-by: Lulalaby <lala@pycord.dev>
  • Loading branch information
4 people committed Mar 25, 2023
1 parent ea84758 commit 2e9ab61
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 72 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,15 @@ These changes are available on the `master` branch, but have not yet been releas
([#1940](https://github.com/Pycord-Development/pycord/pull/1940))
- Added support for text-related features in `StageChannel`.
([#1936](https://github.com/Pycord-Development/pycord/pull/1936))
- Added support for one-time event listeners in `@client.listen()`.
([#1957](https://github.com/Pycord-Development/pycord/pull/1957))
- Added `current_page` argument to Paginator.update()
([#1983](https://github.com/Pycord-Development/pycord/pull/1983))
([#1983](https://github.com/Pycord-Development/pycord/pull/1983)

### Removed

- Removed `@client.once()` in favour of `@client.listen(once=True)`
([#1957](https://github.com/Pycord-Development/pycord/pull/1957))

## [2.4.1] - 2023-03-20

Expand Down
76 changes: 17 additions & 59 deletions discord/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,9 +435,19 @@ def dispatch(self, event: str, *args: Any, **kwargs: Any) -> None:
else:
self._schedule_event(coro, method, *args, **kwargs)

# collect the once listeners as removing them from the list
# while iterating over it causes issues
once_listeners = []

# Schedule additional handlers registered with @listen
for coro in self._event_handlers.get(method, []):
self._schedule_event(coro, method, *args, **kwargs)
if coro._once:
once_listeners.append(coro)

# remove the once listeners
for coro in once_listeners:
self._event_handlers[method].remove(coro)

async def on_error(self, event_method: str, *args: Any, **kwargs: Any) -> None:
"""|coro|
Expand Down Expand Up @@ -1204,7 +1214,7 @@ def remove_listener(self, func: Coro, name: str = MISSING) -> None:
except ValueError:
pass

def listen(self, name: str = MISSING) -> Callable[[Coro], Coro]:
def listen(self, name: str = MISSING, once: bool = False) -> Callable[[Coro], Coro]:
"""A decorator that registers another function as an external
event listener. Basically this allows you to listen to multiple
events from different places e.g. such as :func:`.on_ready`
Expand Down Expand Up @@ -1233,6 +1243,11 @@ async def on_message(message):
async def my_message(message):
print('two')
# listen to the first event only
@client.listen('on_ready', once=True)
async def on_ready():
print('ready!')
Would print one and two in an unspecified order.
"""

Expand All @@ -1241,6 +1256,7 @@ def decorator(func: Coro) -> Coro:
if name == "on_application_command_error":
return self.event(func)

func._once = once
self.add_listener(func, name)
return func

Expand Down Expand Up @@ -1286,64 +1302,6 @@ 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
11 changes: 4 additions & 7 deletions docs/api/clients.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Bots
.. autoclass:: Bot
:members:
:inherited-members:
:exclude-members: command, event, message_command, slash_command, user_command, listen, once
:exclude-members: command, event, message_command, slash_command, user_command, listen

.. automethod:: Bot.command(**kwargs)
:decorator:
Expand All @@ -27,10 +27,7 @@ Bots
.. automethod:: Bot.user_command(**kwargs)
:decorator:

.. automethod:: Bot.listen(name=None)
:decorator:

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

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

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

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

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

.. attributetable:: AutoShardedClient
Expand Down
7 changes: 2 additions & 5 deletions docs/ext/commands/api.rst
Original file line number Diff line number Diff line change
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, once
:exclude-members: after_invoke, before_invoke, check, check_once, command, event, group, listen

.. automethod:: Bot.after_invoke()
:decorator:
Expand All @@ -46,10 +46,7 @@ Bot
.. automethod:: Bot.group(*args, **kwargs)
:decorator:

.. automethod:: Bot.listen(name=None)
:decorator:

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

AutoShardedBot
Expand Down

0 comments on commit 2e9ab61

Please sign in to comment.