Skip to content

Commit 0c93454

Browse files
committed
Added tests
1 parent 46db50c commit 0c93454

File tree

12 files changed

+325
-14
lines changed

12 files changed

+325
-14
lines changed

ellar_throttler/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
11
"""A rate limiting module for Ellar"""
22

33
__version__ = "0.0.1"
4+
5+
from .decorators import skip_throttle, throttle
6+
from .exception import ThrottledException
7+
from .interfaces import IThrottlerStorage
8+
from .module import ThrottlerModule
9+
from .throttler_guard import ThrottlerGuard
10+
from .throttler_service import CacheThrottlerStorageService, ThrottlerStorageService
11+
12+
__all__ = [
13+
"throttle",
14+
"skip_throttle",
15+
"ThrottlerModule",
16+
"ThrottlerGuard",
17+
"ThrottledException",
18+
"CacheThrottlerStorageService",
19+
"ThrottlerStorageService",
20+
"IThrottlerStorage",
21+
]

ellar_throttler/throttler_service.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,21 @@ class ThrottlerStorageService(IThrottlerStorage):
1515
def __init__(self) -> None:
1616
self._storage: t.Dict[str, ThrottlerStorageOption] = {}
1717

18-
def _set(self, key: str, value: ThrottlerStorageOption) -> None:
19-
self._storage[key] = value
18+
def _set(self, key: str, value: ThrottlerStorageOption) -> ThrottlerStorageOption:
19+
self.storage[key] = value
20+
return value
2021

2122
def _has_expired(self, key: str) -> bool:
22-
exp = self._storage.get(key)
23+
exp = self.storage.get(key)
2324
return exp is not None and exp.expires_at <= time.time()
2425

2526
def _has_key(self, key: str) -> bool:
2627
return self._get(key) is not None
2728

2829
def _delete(self, key: str) -> bool:
2930
try:
30-
self._storage.pop(key)
31-
except KeyError:
31+
self.storage.pop(key)
32+
except KeyError: # pragma: no cover
3233
return False
3334
return True
3435

@@ -37,14 +38,17 @@ def _get(self, key: str) -> t.Optional[ThrottlerStorageOption]:
3738
self._delete(key)
3839
return None
3940

40-
return self._storage.get(key)
41+
return self.storage.get(key)
4142

4243
@property
4344
def storage(self) -> t.Dict[str, ThrottlerStorageOption]:
4445
return self._storage
4546

4647
def get_expiration_time(self, key: str) -> int:
47-
return math.floor(self.storage[key].expires_at - time.time())
48+
cache = self._get(key)
49+
if cache is None: # pragma: no cover
50+
return -1
51+
return math.floor(cache.expires_at - time.time())
4852

4953
async def increment(self, key: str, ttl: int) -> ThrottlerStorageRecord:
5054
if not self._has_key(key):
@@ -53,20 +57,20 @@ async def increment(self, key: str, ttl: int) -> ThrottlerStorageRecord:
5357
)
5458

5559
history = self._get(key)
56-
assert history, "value can not be None"
57-
now = time.time()
5860

5961
time_to_expire = self.get_expiration_time(key)
6062

6163
if time_to_expire <= 0:
62-
history.total_hits = 0
63-
history.expires_at = now + ttl
64+
history = self._set(
65+
key, ThrottlerStorageOption(total_hits=0, expires_at=time.time() + ttl)
66+
)
6467
time_to_expire = self.get_expiration_time(key)
6568

66-
history.total_hits += 1
69+
history.total_hits += 1 # type:ignore[union-attr]
6770

6871
return ThrottlerStorageRecord(
69-
total_hits=history.total_hits, time_to_expire=time_to_expire
72+
total_hits=history.total_hits, # type:ignore[union-attr]
73+
time_to_expire=time_to_expire,
7074
)
7175

7276

@@ -76,7 +80,7 @@ def __init__(self, cache_service: ICacheService) -> None:
7680
self._cache_service = cache_service
7781

7882
@property
79-
def storage(self) -> t.Any:
83+
def storage(self) -> t.Any: # pragma: no cover
8084
return self._cache_service.get_backend()
8185

8286
async def get_expiration_time(self, key: str) -> int:

tests/app/__init__.py

Whitespace-only changes.

tests/app/controller/__init__.py

Whitespace-only changes.

tests/app/controller/app.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from ellar.common import Controller, get
2+
3+
from ellar_throttler import skip_throttle, throttle
4+
5+
from .service import AppService
6+
7+
8+
@Controller("/")
9+
@throttle(limit=2, ttl=10)
10+
class AppController:
11+
def __init__(self, app_service: AppService):
12+
self.app_service = app_service
13+
14+
@get()
15+
async def test(self):
16+
return self.app_service.success()
17+
18+
@get("/ignored")
19+
@skip_throttle()
20+
async def ignored(self):
21+
return self.app_service.ignored()

tests/app/controller/default.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from ellar.common import Controller, get
2+
3+
from .service import AppService
4+
5+
6+
@Controller("/default")
7+
class DefaultController:
8+
def __init__(self, app_service: AppService):
9+
self.app_service = app_service
10+
11+
@get()
12+
def get_default(self):
13+
return self.app_service.success()

tests/app/controller/limit.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from ellar.common import Controller, get
2+
3+
from ellar_throttler import throttle
4+
5+
from .service import AppService
6+
7+
8+
@throttle(limit=2, ttl=10)
9+
@Controller("/limit")
10+
class LimitController:
11+
def __init__(self, app_service: AppService):
12+
self.app_service = app_service
13+
14+
@get()
15+
def get_throttled(self):
16+
return self.app_service.success()
17+
18+
@throttle(limit=5, ttl=10)
19+
@get("/higher")
20+
def get_higher(self):
21+
return self.app_service.success()
22+
23+
@get("/shorter")
24+
@throttle(limit=5, ttl=2)
25+
def get_shorter(self):
26+
return self.app_service.success()
27+
28+
@get("/shorter-2")
29+
@throttle(limit=5, ttl=2)
30+
def get_shorter_2(self):
31+
return self.app_service.success()

tests/app/controller/module.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from ellar.common import Module
2+
3+
from ellar_throttler import ThrottlerModule
4+
5+
from .app import AppController
6+
from .default import DefaultController
7+
from .limit import LimitController
8+
from .service import AppService
9+
10+
11+
@Module(
12+
modules=[ThrottlerModule.module_configure(limit=5, ttl=60)],
13+
controllers=(DefaultController, LimitController, AppController),
14+
providers=[AppService],
15+
)
16+
class ControllerModule:
17+
pass

tests/app/controller/service.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from ellar.di import injectable
2+
3+
4+
@injectable()
5+
class AppService:
6+
def success(self):
7+
return {"success": True}
8+
9+
def ignored(self):
10+
return {"ignored": True}

tests/app/module.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from ellar.common import Module
2+
3+
from .controller.module import ControllerModule
4+
5+
6+
@Module(modules=(ControllerModule,))
7+
class AppModule:
8+
pass

0 commit comments

Comments
 (0)