Update 2025-04-24_11:44:19
This commit is contained in:
207
venv/lib/python3.11/site-packages/limits/util.py
Normal file
207
venv/lib/python3.11/site-packages/limits/util.py
Normal file
@ -0,0 +1,207 @@
|
||||
""" """
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import importlib.resources
|
||||
import re
|
||||
import sys
|
||||
from collections import UserDict
|
||||
from types import ModuleType
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from packaging.version import Version
|
||||
|
||||
from limits.typing import NamedTuple
|
||||
|
||||
from .errors import ConfigurationError
|
||||
from .limits import GRANULARITIES, RateLimitItem
|
||||
|
||||
SEPARATORS = re.compile(r"[,;|]{1}")
|
||||
SINGLE_EXPR = re.compile(
|
||||
r"""
|
||||
\s*([0-9]+)
|
||||
\s*(/|\s*per\s*)
|
||||
\s*([0-9]+)
|
||||
*\s*(hour|minute|second|day|month|year)s?\s*""",
|
||||
re.IGNORECASE | re.VERBOSE,
|
||||
)
|
||||
EXPR = re.compile(
|
||||
rf"^{SINGLE_EXPR.pattern}(:?{SEPARATORS.pattern}{SINGLE_EXPR.pattern})*$",
|
||||
re.IGNORECASE | re.VERBOSE,
|
||||
)
|
||||
|
||||
|
||||
class WindowStats(NamedTuple):
|
||||
"""
|
||||
tuple to describe a rate limited window
|
||||
"""
|
||||
|
||||
#: Time as seconds since the Epoch when this window will be reset
|
||||
reset_time: float
|
||||
#: Quantity remaining in this window
|
||||
remaining: int
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Dependency:
|
||||
name: str
|
||||
version_required: Version | None
|
||||
version_found: Version | None
|
||||
module: ModuleType
|
||||
|
||||
|
||||
MissingModule = ModuleType("Missing")
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
_UserDict = UserDict[str, Dependency]
|
||||
else:
|
||||
_UserDict = UserDict
|
||||
|
||||
|
||||
class DependencyDict(_UserDict):
|
||||
def __getitem__(self, key: str) -> Dependency:
|
||||
dependency = super().__getitem__(key)
|
||||
|
||||
if dependency.module is MissingModule:
|
||||
message = f"'{dependency.name}' prerequisite not available."
|
||||
if dependency.version_required:
|
||||
message += (
|
||||
f" A minimum version of {dependency.version_required} is required."
|
||||
if dependency.version_required
|
||||
else ""
|
||||
)
|
||||
message += (
|
||||
" See https://limits.readthedocs.io/en/stable/storage.html#supported-versions"
|
||||
" for more details."
|
||||
)
|
||||
raise ConfigurationError(message)
|
||||
elif dependency.version_required and (
|
||||
not dependency.version_found
|
||||
or dependency.version_found < dependency.version_required
|
||||
):
|
||||
raise ConfigurationError(
|
||||
f"The minimum version of {dependency.version_required}"
|
||||
f" for '{dependency.name}' could not be found. Found version: {dependency.version_found}"
|
||||
)
|
||||
|
||||
return dependency
|
||||
|
||||
|
||||
class LazyDependency:
|
||||
"""
|
||||
Simple utility that provides an :attr:`dependency`
|
||||
to the child class to fetch any dependencies
|
||||
without having to import them explicitly.
|
||||
"""
|
||||
|
||||
DEPENDENCIES: dict[str, Version | None] | list[str] = []
|
||||
"""
|
||||
The python modules this class has a dependency on.
|
||||
Used to lazily populate the :attr:`dependencies`
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._dependencies: DependencyDict = DependencyDict()
|
||||
|
||||
@property
|
||||
def dependencies(self) -> DependencyDict:
|
||||
"""
|
||||
Cached mapping of the modules this storage depends on.
|
||||
This is done so that the module is only imported lazily
|
||||
when the storage is instantiated.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
|
||||
if not getattr(self, "_dependencies", None):
|
||||
dependencies = DependencyDict()
|
||||
mapping: dict[str, Version | None]
|
||||
|
||||
if isinstance(self.DEPENDENCIES, list):
|
||||
mapping = {dependency: None for dependency in self.DEPENDENCIES}
|
||||
else:
|
||||
mapping = self.DEPENDENCIES
|
||||
|
||||
for name, minimum_version in mapping.items():
|
||||
dependency, version = get_dependency(name)
|
||||
|
||||
dependencies[name] = Dependency(
|
||||
name, minimum_version, version, dependency
|
||||
)
|
||||
self._dependencies = dependencies
|
||||
|
||||
return self._dependencies
|
||||
|
||||
|
||||
def get_dependency(module_path: str) -> tuple[ModuleType, Version | None]:
|
||||
"""
|
||||
safe function to import a module at runtime
|
||||
"""
|
||||
try:
|
||||
if module_path not in sys.modules:
|
||||
__import__(module_path)
|
||||
root = module_path.split(".")[0]
|
||||
version = getattr(sys.modules[root], "__version__", "0.0.0")
|
||||
|
||||
return sys.modules[module_path], Version(version)
|
||||
except ImportError: # pragma: no cover
|
||||
return MissingModule, None
|
||||
|
||||
|
||||
def get_package_data(path: str) -> bytes:
|
||||
return importlib.resources.files("limits").joinpath(path).read_bytes()
|
||||
|
||||
|
||||
def parse_many(limit_string: str) -> list[RateLimitItem]:
|
||||
"""
|
||||
parses rate limits in string notation containing multiple rate limits
|
||||
(e.g. ``1/second; 5/minute``)
|
||||
|
||||
:param limit_string: rate limit string using :ref:`ratelimit-string`
|
||||
:raise ValueError: if the string notation is invalid.
|
||||
|
||||
"""
|
||||
|
||||
if not (isinstance(limit_string, str) and EXPR.match(limit_string)):
|
||||
raise ValueError(f"couldn't parse rate limit string '{limit_string}'")
|
||||
limits = []
|
||||
|
||||
for limit in SEPARATORS.split(limit_string):
|
||||
match = SINGLE_EXPR.match(limit)
|
||||
|
||||
if match:
|
||||
amount, _, multiples, granularity_string = match.groups()
|
||||
granularity = granularity_from_string(granularity_string)
|
||||
limits.append(
|
||||
granularity(int(amount), multiples and int(multiples) or None)
|
||||
)
|
||||
|
||||
return limits
|
||||
|
||||
|
||||
def parse(limit_string: str) -> RateLimitItem:
|
||||
"""
|
||||
parses a single rate limit in string notation
|
||||
(e.g. ``1/second`` or ``1 per second``)
|
||||
|
||||
:param limit_string: rate limit string using :ref:`ratelimit-string`
|
||||
:raise ValueError: if the string notation is invalid.
|
||||
|
||||
"""
|
||||
|
||||
return list(parse_many(limit_string))[0]
|
||||
|
||||
|
||||
def granularity_from_string(granularity_string: str) -> type[RateLimitItem]:
|
||||
"""
|
||||
|
||||
:param granularity_string:
|
||||
:raise ValueError:
|
||||
"""
|
||||
|
||||
for granularity in GRANULARITIES.values():
|
||||
if granularity.check_granularity_string(granularity_string):
|
||||
return granularity
|
||||
raise ValueError(f"no granularity matched for {granularity_string}")
|
Reference in New Issue
Block a user