|
@ -34,6 +34,7 @@ __all__ = ( |
|
|
'BucketType', |
|
|
'BucketType', |
|
|
'Cooldown', |
|
|
'Cooldown', |
|
|
'CooldownMapping', |
|
|
'CooldownMapping', |
|
|
|
|
|
'DynamicCooldownMapping', |
|
|
'MaxConcurrency', |
|
|
'MaxConcurrency', |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
@ -69,19 +70,15 @@ class BucketType(Enum): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Cooldown: |
|
|
class Cooldown: |
|
|
__slots__ = ('rate', 'per', 'type', '_window', '_tokens', '_last') |
|
|
__slots__ = ('rate', 'per', '_window', '_tokens', '_last') |
|
|
|
|
|
|
|
|
def __init__(self, rate, per, type): |
|
|
def __init__(self, rate, per): |
|
|
self.rate = int(rate) |
|
|
self.rate = int(rate) |
|
|
self.per = float(per) |
|
|
self.per = float(per) |
|
|
self.type = type |
|
|
|
|
|
self._window = 0.0 |
|
|
self._window = 0.0 |
|
|
self._tokens = self.rate |
|
|
self._tokens = self.rate |
|
|
self._last = 0.0 |
|
|
self._last = 0.0 |
|
|
|
|
|
|
|
|
if not callable(self.type): |
|
|
|
|
|
raise TypeError('Cooldown type must be a BucketType or callable') |
|
|
|
|
|
|
|
|
|
|
|
def get_tokens(self, current=None): |
|
|
def get_tokens(self, current=None): |
|
|
if not current: |
|
|
if not current: |
|
|
current = time.time() |
|
|
current = time.time() |
|
@ -128,15 +125,19 @@ class Cooldown: |
|
|
self._last = 0.0 |
|
|
self._last = 0.0 |
|
|
|
|
|
|
|
|
def copy(self): |
|
|
def copy(self): |
|
|
return Cooldown(self.rate, self.per, self.type) |
|
|
return Cooldown(self.rate, self.per) |
|
|
|
|
|
|
|
|
def __repr__(self): |
|
|
def __repr__(self): |
|
|
return f'<Cooldown rate: {self.rate} per: {self.per} window: {self._window} tokens: {self._tokens}>' |
|
|
return f'<Cooldown rate: {self.rate} per: {self.per} window: {self._window} tokens: {self._tokens}>' |
|
|
|
|
|
|
|
|
class CooldownMapping: |
|
|
class CooldownMapping: |
|
|
def __init__(self, original): |
|
|
def __init__(self, original, type): |
|
|
|
|
|
if not callable(type): |
|
|
|
|
|
raise TypeError('Cooldown type must be a BucketType or callable') |
|
|
|
|
|
|
|
|
self._cache = {} |
|
|
self._cache = {} |
|
|
self._cooldown = original |
|
|
self._cooldown = original |
|
|
|
|
|
self._type = type |
|
|
|
|
|
|
|
|
def copy(self): |
|
|
def copy(self): |
|
|
ret = CooldownMapping(self._cooldown) |
|
|
ret = CooldownMapping(self._cooldown) |
|
@ -152,7 +153,7 @@ class CooldownMapping: |
|
|
return cls(Cooldown(rate, per, type)) |
|
|
return cls(Cooldown(rate, per, type)) |
|
|
|
|
|
|
|
|
def _bucket_key(self, msg): |
|
|
def _bucket_key(self, msg): |
|
|
return self._cooldown.type(msg) |
|
|
return self._type(msg) |
|
|
|
|
|
|
|
|
def _verify_cache_integrity(self, current=None): |
|
|
def _verify_cache_integrity(self, current=None): |
|
|
# we want to delete all cache objects that haven't been used |
|
|
# we want to delete all cache objects that haven't been used |
|
@ -163,15 +164,19 @@ class CooldownMapping: |
|
|
for k in dead_keys: |
|
|
for k in dead_keys: |
|
|
del self._cache[k] |
|
|
del self._cache[k] |
|
|
|
|
|
|
|
|
|
|
|
def create_bucket(self, message): |
|
|
|
|
|
return self._cooldown.copy() |
|
|
|
|
|
|
|
|
def get_bucket(self, message, current=None): |
|
|
def get_bucket(self, message, current=None): |
|
|
if self._cooldown.type is BucketType.default: |
|
|
if self._type is BucketType.default: |
|
|
return self._cooldown |
|
|
return self._cooldown |
|
|
|
|
|
|
|
|
self._verify_cache_integrity(current) |
|
|
self._verify_cache_integrity(current) |
|
|
key = self._bucket_key(message) |
|
|
key = self._bucket_key(message) |
|
|
if key not in self._cache: |
|
|
if key not in self._cache: |
|
|
bucket = self._cooldown.copy() |
|
|
bucket = self.create_bucket(message) |
|
|
self._cache[key] = bucket |
|
|
if bucket is not None: |
|
|
|
|
|
self._cache[key] = bucket |
|
|
else: |
|
|
else: |
|
|
bucket = self._cache[key] |
|
|
bucket = self._cache[key] |
|
|
|
|
|
|
|
@ -181,6 +186,19 @@ class CooldownMapping: |
|
|
bucket = self.get_bucket(message, current) |
|
|
bucket = self.get_bucket(message, current) |
|
|
return bucket.update_rate_limit(current) |
|
|
return bucket.update_rate_limit(current) |
|
|
|
|
|
|
|
|
|
|
|
class DynamicCooldownMapping(CooldownMapping): |
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, factory, type): |
|
|
|
|
|
super().__init__(None, type) |
|
|
|
|
|
self._factory = factory |
|
|
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
|
def valid(self): |
|
|
|
|
|
return True |
|
|
|
|
|
|
|
|
|
|
|
def create_bucket(self, message): |
|
|
|
|
|
return self._factory(message) |
|
|
|
|
|
|
|
|
class _Semaphore: |
|
|
class _Semaphore: |
|
|
"""This class is a version of a semaphore. |
|
|
"""This class is a version of a semaphore. |
|
|
|
|
|
|
|
|