Browse Source

[commands] Add Bot.check_once for a global check that is called once.

There is a counterpart for this in cogs, called __global_check_once.
This allows for predicates that would filter a command globally that
do not necessarily require rechecking in the case of e.g. the help
command such as blocking users or blocking channels.
pull/609/merge
Rapptz 8 years ago
parent
commit
717f11d635
  1. 85
      discord/ext/commands/bot.py
  2. 4
      docs/migrating.rst

85
discord/ext/commands/bot.py

@ -133,6 +133,7 @@ class BotBase(GroupMixin):
self.cogs = {}
self.extensions = {}
self._checks = []
self._check_once = []
self._before_invoke = None
self._after_invoke = None
self.description = inspect.cleandoc(description) if description else ''
@ -236,24 +237,32 @@ class BotBase(GroupMixin):
.. code-block:: python3
@bot.check
def whitelist(ctx):
return ctx.message.author.id in my_whitelist
def check_commands(ctx):
return ctx.command.qualified_name in allowed_commands
"""
self.add_check(func)
return func
def add_check(self, func):
def add_check(self, func, *, call_once=False):
"""Adds a global check to the bot.
This is the non-decorator interface to :meth:`.check`.
This is the non-decorator interface to :meth:`.check`
and :meth:`.check_once`.
Parameters
-----------
func
The function that was used as a global check.
call_once: bool
If the function should only be called once per
:meth:`.invoke` call.
"""
self._checks.append(func)
if call_once:
self._check_once.append(func)
else:
self._checks.append(func)
def remove_check(self, func):
"""Removes a global check from the bot.
@ -270,14 +279,51 @@ class BotBase(GroupMixin):
try:
self._checks.remove(func)
except ValueError:
pass
try:
self._check_once.remove(func)
except ValueError:
pass
def check_once(self, func):
"""A decorator that adds a "call once" global check to the bot.
Unlike regular global checks, this one is called only once
per :meth:`.invoke` call.
Regular global checks are called whenever a command is called
or :meth:`.Command.can_run` is called. This type of check
bypasses that and ensures that it's called only once, even inside
the default help command.
.. note::
This function can either be a regular function or a coroutine.
Similar to a command :func:`.check`\, this takes a single parameter
of type :class:`.Context` and can only raise exceptions derived from
:exc:`.CommandError`.
Example
---------
.. code-block:: python3
@bot.check_once
def whitelist(ctx):
return ctx.message.author.id in my_whitelist
"""
self.add_check(func, call_once=True)
return func
@asyncio.coroutine
def can_run(self, ctx):
if len(self._checks) == 0:
def can_run(self, ctx, *, call_once=False):
data = self._check_once if call_once else self._checks
if len(data) == 0:
return True
return (yield from discord.utils.async_all(f(ctx) for f in self._checks))
return (yield from discord.utils.async_all(f(ctx) for f in data))
@asyncio.coroutine
def is_owner(self, user):
@ -465,7 +511,9 @@ class BotBase(GroupMixin):
into a singular class that shares some state or no state at all.
The cog can also have a ``__global_check`` member function that allows
you to define a global check. See :meth:`.check` for more info.
you to define a global check. See :meth:`.check` for more info. If
the name is ``__global_check_once`` then it's equivalent to the
:meth:`.check_once` decorator.
More information will be documented soon.
@ -484,6 +532,13 @@ class BotBase(GroupMixin):
else:
self.add_check(check)
try:
check = getattr(cog, '_{.__class__.__name__}__global_check_once'.format(cog))
except AttributeError:
pass
else:
self.add_check(check, call_once=True)
members = inspect.getmembers(cog)
for name, member in members:
# register commands the cog has
@ -575,6 +630,13 @@ class BotBase(GroupMixin):
else:
self.remove_check(check)
try:
check = getattr(cog, '_{0.__class__.__name__}__global_check_once'.format(cog))
except AttributeError:
pass
else:
self.remove_check(check)
unloader_name = '_{0.__class__.__name__}__unload'.format(cog)
try:
unloader = getattr(cog, unloader_name)
@ -786,7 +848,8 @@ class BotBase(GroupMixin):
if ctx.command is not None:
self.dispatch('command', ctx)
try:
yield from ctx.command.invoke(ctx)
if (yield from self.can_run(ctx, call_once=True)):
yield from ctx.command.invoke(ctx)
except CommandError as e:
yield from ctx.command.dispatch_error(ctx, e)
else:

4
docs/migrating.rst

@ -918,7 +918,9 @@ The previous ``__check`` special method has been renamed to ``__global_check`` t
check.
To complement the new ``__global_check`` there is now a new ``__local_check`` to facilitate a check that will run on
every command in the cog.
every command in the cog. There is also a ``__global_check_once``, which is similar to a global check instead it is only
called once per :meth:`.Bot.invoke` call rather than every :meth:`.Command.invoke` call. Practically, the difference is
only for black-listing users or channels without constantly opening a database connection.
Cogs have also gained a ``__before_invoke`` and ``__after_invoke`` cog local before and after invocation hook, which
can be seen in :ref:`migrating_1_0_before_after_hook`.

Loading…
Cancel
Save