Source code for lightbulb.buckets

# -*- coding: utf-8 -*-
# Copyright © tandemdude 2020-present
#
# This file is part of Lightbulb.
#
# Lightbulb is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Lightbulb is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Lightbulb. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations

__all__ = ["Bucket", "UserBucket", "GuildBucket", "GlobalBucket", "ChannelBucket"]

import abc
import time
import typing as t

from lightbulb import cooldown_algorithms

if t.TYPE_CHECKING:
    from lightbulb.context import base as ctx_base


[docs] class Bucket(abc.ABC): """ Base class that represents a bucket that cooldowns or max concurrency limits can be applied under. All buckets must inherit from this class. Note that for max concurrency limiting, the bucket object will never be instantiated. Args: length (:obj:`float`): Length of the cooldown timer. max_usages (:obj:`int`): Number of command usages before the cooldown is activated. """ __slots__ = ("length", "usages", "cooldown_algorithm", "activated", "start_time") def __init__( self, length: float, max_usages: int, cooldown_algorithm: t.Type[ cooldown_algorithms.CooldownAlgorithm ] = cooldown_algorithms.BangBangCooldownAlgorithm, ) -> None: self.length = length self.usages = max_usages self.cooldown_algorithm = cooldown_algorithm() self.activated = False self.start_time: t.Optional[float] = None """The start time of the bucket cooldown. This is relative to :meth:`time.perf_counter`."""
[docs] @classmethod @abc.abstractmethod def extract_hash(cls, context: ctx_base.Context) -> t.Hashable: """ Extracts the hash from the context which links a command usage to a single bucket. Args: context (:obj:`~.context.base.Context`): The context the command was invoked under. Returns: Hashable: Hashable object linking the context to a bucket. """ ...
[docs] def acquire( self, ) -> t.Union[cooldown_algorithms.CooldownStatus, t.Coroutine[t.Any, t.Any, cooldown_algorithms.CooldownStatus]]: """ Get the current state of the cooldown and add a command usage if the cooldown is not currently active or has expired. This method is **only** used when processing cooldowns. Returns: :obj:`~CooldownStatus`: The status of the cooldown bucket. """ if self.active: return cooldown_algorithms.CooldownStatus.ACTIVE elif self.activated and self.expired: return cooldown_algorithms.CooldownStatus.EXPIRED return self.cooldown_algorithm.evaluate(self)
@property def active(self) -> bool: """ Whether the cooldown represented by the bucket is currently active. """ return self.activated and not self.expired @property def expired(self) -> bool: """ Whether the cooldown represented by the bucket has expired. """ if self.start_time is not None: return time.perf_counter() >= (self.start_time + self.length) return True def activate(self) -> None: self.activated = True self.start_time = time.perf_counter()
[docs] class GlobalBucket(Bucket): """ Global bucket. All cooldowns and concurrency limits will be applied globally if you use this bucket. """ __slots__ = ()
[docs] @classmethod def extract_hash(cls, context: ctx_base.Context) -> t.Hashable: return 0
[docs] class UserBucket(Bucket): """ User bucket. All cooldowns and concurrency limits will be applied per user if you use this bucket. """ __slots__ = ()
[docs] @classmethod def extract_hash(cls, context: ctx_base.Context) -> t.Hashable: return context.author.id
[docs] class ChannelBucket(Bucket): """ Channel bucket. All cooldowns and concurrency limits will be applied per channel if you use this bucket. """ __slots__ = ()
[docs] @classmethod def extract_hash(cls, context: ctx_base.Context) -> t.Hashable: return context.channel_id
[docs] class GuildBucket(Bucket): """ Guild bucket. All cooldowns and concurrency limits will be applied per guild if you use this bucket. The command will still be permitted to be run in DMs in which case the DM channel ID will be used as the cooldown identifier instead of a guild ID. """ __slots__ = ()
[docs] @classmethod def extract_hash(cls, context: ctx_base.Context) -> t.Hashable: return context.guild_id if context.guild_id is not None else context.channel_id