Skip to content

Commit

Permalink
Use named tuple for return from get_window_stats
Browse files Browse the repository at this point in the history
  • Loading branch information
alisaifee committed Jan 13, 2023
1 parent d095641 commit 1edb1fe
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 18 deletions.
5 changes: 5 additions & 0 deletions doc/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ declaring a :attr:`GRANULARITY`
.. autoclass:: RateLimitItemPerYear


Structures
==========
.. autoclass:: limits.WindowStats
:no-inherited-members:


Exceptions
==========
Expand Down
3 changes: 2 additions & 1 deletion limits/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
RateLimitItemPerSecond,
RateLimitItemPerYear,
)
from .util import parse, parse_many
from .util import parse, parse_many, WindowStats

__all__ = [
"RateLimitItem",
Expand All @@ -27,6 +27,7 @@
"strategies",
"parse",
"parse_many",
"WindowStats",
]

__version__ = _version.get_versions()["version"] # type: ignore
11 changes: 6 additions & 5 deletions limits/aio/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from ..limits import RateLimitItem
from ..storage import StorageTypes
from ..util import WindowStats
from .storage import MovingWindowSupport, Storage


Expand Down Expand Up @@ -42,7 +43,7 @@ async def test(self, item: RateLimitItem, *identifiers: str) -> bool:
@abstractmethod
async def get_window_stats(
self, item: RateLimitItem, *identifiers: str
) -> Tuple[int, int]:
) -> WindowStats:
"""
Query the reset time and remaining amount for the limit
Expand Down Expand Up @@ -105,7 +106,7 @@ async def test(self, item: RateLimitItem, *identifiers: str) -> bool:

async def get_window_stats(
self, item: RateLimitItem, *identifiers: str
) -> Tuple[int, int]:
) -> WindowStats:
"""
returns the number of requests remaining within this limit.
Expand All @@ -119,7 +120,7 @@ async def get_window_stats(
).get_moving_window(item.key_for(*identifiers), item.amount, item.get_expiry())
reset = window_start + item.get_expiry()

return reset, item.amount - window_items
return WindowStats(reset, item.amount - window_items)


class FixedWindowRateLimiter(RateLimiter):
Expand Down Expand Up @@ -160,7 +161,7 @@ async def test(self, item: RateLimitItem, *identifiers: str) -> bool:

async def get_window_stats(
self, item: RateLimitItem, *identifiers: str
) -> Tuple[int, int]:
) -> WindowStats:
"""
Query the reset time and remaining amount for the limit
Expand All @@ -175,7 +176,7 @@ async def get_window_stats(
)
reset = await self.storage.get_expiry(item.key_for(*identifiers))

return reset, remaining
return WindowStats(reset, remaining)


class FixedWindowElasticExpiryRateLimiter(FixedWindowRateLimiter):
Expand Down
17 changes: 6 additions & 11 deletions limits/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from .limits import RateLimitItem
from .storage import MovingWindowSupport, Storage, StorageTypes
from .util import WindowStats


class RateLimiter(metaclass=ABCMeta):
Expand Down Expand Up @@ -39,9 +40,7 @@ def test(self, item: RateLimitItem, *identifiers: str) -> bool:
raise NotImplementedError

@abstractmethod
def get_window_stats(
self, item: RateLimitItem, *identifiers: str
) -> Tuple[int, int]:
def get_window_stats(self, item: RateLimitItem, *identifiers: str) -> WindowStats:
"""
Query the reset time and remaining amount for the limit
Expand Down Expand Up @@ -104,9 +103,7 @@ def test(self, item: RateLimitItem, *identifiers: str) -> bool:
< item.amount
)

def get_window_stats(
self, item: RateLimitItem, *identifiers: str
) -> Tuple[int, int]:
def get_window_stats(self, item: RateLimitItem, *identifiers: str) -> WindowStats:
"""
returns the number of requests remaining within this limit.
Expand All @@ -120,7 +117,7 @@ def get_window_stats(
).get_moving_window(item.key_for(*identifiers), item.amount, item.get_expiry())
reset = window_start + item.get_expiry()

return (reset, item.amount - window_items)
return WindowStats(reset, item.amount - window_items)


class FixedWindowRateLimiter(RateLimiter):
Expand Down Expand Up @@ -159,9 +156,7 @@ def test(self, item: RateLimitItem, *identifiers: str) -> bool:

return self.storage.get(item.key_for(*identifiers)) < item.amount

def get_window_stats(
self, item: RateLimitItem, *identifiers: str
) -> Tuple[int, int]:
def get_window_stats(self, item: RateLimitItem, *identifiers: str) -> WindowStats:
"""
Query the reset time and remaining amount for the limit
Expand All @@ -173,7 +168,7 @@ def get_window_stats(
remaining = max(0, item.amount - self.storage.get(item.key_for(*identifiers)))
reset = self.storage.get_expiry(item.key_for(*identifiers))

return (reset, remaining)
return WindowStats(reset, remaining)


class FixedWindowElasticExpiryRateLimiter(FixedWindowRateLimiter):
Expand Down
2 changes: 2 additions & 0 deletions limits/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Callable,
Dict,
List,
NamedTuple,
Optional,
Tuple,
Type,
Expand Down Expand Up @@ -127,6 +128,7 @@ def __call__(self, keys: List[Serializable], args: List[Serializable]) -> R_co:
"ItemP",
"List",
"MemcachedClientP",
"NamedTuple",
"Optional",
"P",
"ParamSpec",
Expand Down
15 changes: 14 additions & 1 deletion limits/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import pkg_resources
from packaging.version import Version

from limits.typing import Dict, List, Optional, Tuple, Type, Union
from limits.typing import Dict, List, NamedTuple, Optional, Tuple, Type, Union

from .errors import ConfigurationError
from .limits import GRANULARITIES, RateLimitItem
Expand All @@ -33,6 +33,13 @@
)


class WindowStats(NamedTuple):
#: Time in seconds when this window will be reset
reset_time: int
#: Quantity remaining in this window
remaining: int


@dataclasses.dataclass
class Dependency:
name: str
Expand All @@ -52,6 +59,7 @@ class DependencyDict(_UserDict):

def __getitem__(self, key: str) -> Dependency:
dependency = super().__getitem__(key)

if dependency == DependencyDict.Missing:
raise ConfigurationError(f"{key} prerequisite not available")
elif dependency.version_required and (
Expand All @@ -62,6 +70,7 @@ def __getitem__(self, key: str) -> Dependency:
f"The minimum version of {dependency.version_required}"
f" of {dependency.name} could not be found"
)

return dependency


Expand Down Expand Up @@ -90,6 +99,7 @@ def dependencies(self) -> DependencyDict:
:meta private:
"""

if not getattr(self, "_dependencies", None):
dependencies = DependencyDict()
mapping: Dict[str, Optional[Version]]
Expand All @@ -101,13 +111,15 @@ def dependencies(self) -> DependencyDict:

for name, minimum_version in mapping.items():
dependency, version = get_dependency(name)

if not dependency:
dependencies[name] = DependencyDict.Missing
else:
dependencies[name] = Dependency(
name, minimum_version, version, dependency
)
self._dependencies = dependencies

return self._dependencies


Expand All @@ -120,6 +132,7 @@ def get_dependency(module_path: str) -> Tuple[Optional[ModuleType], Optional[Ver
__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 None, None
Expand Down

0 comments on commit 1edb1fe

Please sign in to comment.