123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- """ """
- from __future__ import annotations
- from functools import total_ordering
- from limits.typing import ClassVar, NamedTuple, cast
- def safe_string(value: bytes | str | int | float) -> str:
- """
- normalize a byte/str/int or float to a str
- """
- if isinstance(value, bytes):
- return value.decode()
- return str(value)
- class Granularity(NamedTuple):
- seconds: int
- name: str
- TIME_TYPES = dict(
- day=Granularity(60 * 60 * 24, "day"),
- month=Granularity(60 * 60 * 24 * 30, "month"),
- year=Granularity(60 * 60 * 24 * 30 * 12, "year"),
- hour=Granularity(60 * 60, "hour"),
- minute=Granularity(60, "minute"),
- second=Granularity(1, "second"),
- )
- GRANULARITIES: dict[str, type[RateLimitItem]] = {}
- class RateLimitItemMeta(type):
- def __new__(
- cls,
- name: str,
- parents: tuple[type, ...],
- dct: dict[str, Granularity | list[str]],
- ) -> RateLimitItemMeta:
- if "__slots__" not in dct:
- dct["__slots__"] = []
- granularity = super().__new__(cls, name, parents, dct)
- if "GRANULARITY" in dct:
- GRANULARITIES[dct["GRANULARITY"][1]] = cast(
- type[RateLimitItem], granularity
- )
- return granularity
- # pylint: disable=no-member
- @total_ordering
- class RateLimitItem(metaclass=RateLimitItemMeta):
- """
- defines a Rate limited resource which contains the characteristic
- namespace, amount and granularity multiples of the rate limiting window.
- :param amount: the rate limit amount
- :param multiples: multiple of the 'per' :attr:`GRANULARITY`
- (e.g. 'n' per 'm' seconds)
- :param namespace: category for the specific rate limit
- """
- __slots__ = ["namespace", "amount", "multiples"]
- GRANULARITY: ClassVar[Granularity]
- """
- A tuple describing the granularity of this limit as
- (number of seconds, name)
- """
- def __init__(
- self, amount: int, multiples: int | None = 1, namespace: str = "LIMITER"
- ):
- self.namespace = namespace
- self.amount = int(amount)
- self.multiples = int(multiples or 1)
- @classmethod
- def check_granularity_string(cls, granularity_string: str) -> bool:
- """
- Checks if this instance matches a *granularity_string*
- of type ``n per hour``, ``n per minute`` etc,
- by comparing with :attr:`GRANULARITY`
- """
- return granularity_string.lower() in cls.GRANULARITY.name
- def get_expiry(self) -> int:
- """
- :return: the duration the limit is enforced for in seconds.
- """
- return self.GRANULARITY.seconds * self.multiples
- def key_for(self, *identifiers: bytes | str | int | float) -> str:
- """
- Constructs a key for the current limit and any additional
- identifiers provided.
- :param identifiers: a list of strings to append to the key
- :return: a string key identifying this resource with
- each identifier separated with a '/' delimiter.
- """
- remainder = "/".join(
- [safe_string(k) for k in identifiers]
- + [
- safe_string(self.amount),
- safe_string(self.multiples),
- self.GRANULARITY.name,
- ]
- )
- return f"{self.namespace}/{remainder}"
- def __eq__(self, other: object) -> bool:
- if isinstance(other, RateLimitItem):
- return (
- self.amount == other.amount
- and self.GRANULARITY == other.GRANULARITY
- and self.multiples == other.multiples
- )
- return False
- def __repr__(self) -> str:
- return f"{self.amount} per {self.multiples} {self.GRANULARITY.name}"
- def __lt__(self, other: RateLimitItem) -> bool:
- return self.GRANULARITY.seconds < other.GRANULARITY.seconds
- def __hash__(self) -> int:
- return hash((self.namespace, self.amount, self.multiples, self.GRANULARITY))
- class RateLimitItemPerYear(RateLimitItem):
- """
- per year rate limited resource.
- """
- GRANULARITY = TIME_TYPES["year"]
- """A year"""
- class RateLimitItemPerMonth(RateLimitItem):
- """
- per month rate limited resource.
- """
- GRANULARITY = TIME_TYPES["month"]
- """A month"""
- class RateLimitItemPerDay(RateLimitItem):
- """
- per day rate limited resource.
- """
- GRANULARITY = TIME_TYPES["day"]
- """A day"""
- class RateLimitItemPerHour(RateLimitItem):
- """
- per hour rate limited resource.
- """
- GRANULARITY = TIME_TYPES["hour"]
- """An hour"""
- class RateLimitItemPerMinute(RateLimitItem):
- """
- per minute rate limited resource.
- """
- GRANULARITY = TIME_TYPES["minute"]
- """A minute"""
- class RateLimitItemPerSecond(RateLimitItem):
- """
- per second rate limited resource.
- """
- GRANULARITY = TIME_TYPES["second"]
- """A second"""
|