-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement ratelimit module and add Application.rate_limiter. Handle RateLimitError on server and client. Rate limit AuthRequest.verify(). Also make current user and client available as context variables and type touched code. Close #46.
- Loading branch information
1 parent
82597ee
commit eeb5a09
Showing
13 changed files
with
166 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
noyainrain.micro ~= 0.52.0 | ||
noyainrain.micro ~= 0.53.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# ratelimit | ||
# Released into the public domain | ||
# https://github.com/noyainrain/micro/blob/master/micro/ratelimit.py | ||
|
||
"""Mechanism to limit the rate of operations per client.""" | ||
|
||
from asyncio import get_event_loop | ||
from dataclasses import dataclass | ||
from datetime import timedelta | ||
from typing import Dict, Tuple | ||
|
||
@dataclass(frozen=True) | ||
class RateLimit: | ||
"""Rate limit rule for an operation. | ||
.. attribute:: id | ||
Unique ID of the rule. | ||
.. attribute:: n | ||
Maximum number of operations. | ||
.. attribute:: time_frame | ||
Reference time frame. | ||
""" | ||
id: str | ||
n: int | ||
time_frame: timedelta | ||
|
||
def __post_init__(self) -> None: | ||
if not self.id: | ||
raise ValueError('Empty id') | ||
if self.n <= 0: | ||
raise ValueError('Out-of-range n') | ||
if self.time_frame <= timedelta(): | ||
raise ValueError('Out-of-range time_frame') | ||
|
||
class RateLimiter: | ||
"""Mechanism to limit the rate of operations per client.""" | ||
|
||
def __init__(self) -> None: | ||
self._counters: Dict[Tuple[RateLimit, str], int] = {} | ||
|
||
def count(self, limit: RateLimit, client: str) -> None: | ||
"""Count an operation by *client*. | ||
The operation is defined by *limit*. *client* is an identifier, e.g. a network address. If | ||
the client exceeds the allowed rate limit, a :exc:`RateLimitError` is raised. | ||
""" | ||
key = (limit, client) | ||
if key not in self._counters: | ||
self._counters[key] = 0 | ||
get_event_loop().call_later(limit.time_frame.total_seconds(), | ||
lambda: self._counters.pop(key)) | ||
self._counters[key] += 1 | ||
if self._counters[key] > limit.n: | ||
raise RateLimitError(client) | ||
|
||
class RateLimitError(Exception): | ||
"""Raised if a client exceeds the allowed rate limit for an operation.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,7 +82,7 @@ def _render_email_auth_message(email, auth_request, auth): | |
|
||
def test_user_set_email_auth_invalid(self): | ||
auth_request = self.user.set_email('[email protected]') | ||
with self.assertRaisesRegex(ValueError, 'auth_invalid'): | ||
with self.assertRaisesRegex(ValueError, 'code'): | ||
self.user.finish_set_email(auth_request, 'foo') | ||
|
||
def test_user_remove_email(self): | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# ratelimit | ||
# Released into the public domain | ||
# https://github.com/noyainrain/micro/blob/master/micro/ratelimit.py | ||
|
||
# pylint: disable=missing-docstring; test module | ||
|
||
from asyncio import sleep | ||
from datetime import timedelta | ||
from tornado.testing import AsyncTestCase, gen_test | ||
|
||
from micro.ratelimit import RateLimit, RateLimiter, RateLimitError | ||
|
||
class RateLimiterTest(AsyncTestCase): | ||
LIMIT = RateLimit('meow', 2, timedelta(seconds=0.1)) | ||
|
||
def setUp(self) -> None: | ||
super().setUp() | ||
self.rate_limiter = RateLimiter() | ||
|
||
def test_count(self) -> None: | ||
self.rate_limiter.count(self.LIMIT, 'local') | ||
self.rate_limiter.count(self.LIMIT, 'local') | ||
with self.assertRaises(RateLimitError): | ||
self.rate_limiter.count(self.LIMIT, 'local') | ||
|
||
@gen_test # type: ignore | ||
async def test_count_after_time_frame(self) -> None: | ||
self.rate_limiter.count(self.LIMIT, 'local') | ||
self.rate_limiter.count(self.LIMIT, 'local') | ||
await sleep(0.2) | ||
self.rate_limiter.count(self.LIMIT, 'local') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ | |
|
||
setup( | ||
name='noyainrain.micro', | ||
version='0.52.0', | ||
version='0.53.0', | ||
url='https://github.com/noyainrain/micro', | ||
maintainer='Sven James', | ||
maintainer_email='[email protected]', | ||
|