def test_error(self, metrics): limiter = RateLimiter( storage.MemoryStorage(), "1 per minute", identifiers=["foo"], metrics=metrics, ) def raiser(*args, **kwargs): raise redis.ConnectionError() limiter._window = pretend.stub(hit=raiser, test=raiser, get_window_stats=raiser) assert limiter.test("foo") assert limiter.hit("foo") assert limiter.resets_in("foo") is None assert metrics.increment.calls == [ pretend.call("warehouse.ratelimiter.error", tags=["call:test"]), pretend.call("warehouse.ratelimiter.error", tags=["call:hit"]), pretend.call("warehouse.ratelimiter.error", tags=["call:resets_in"]), ]
def test_results_in(self): limiter = RateLimiter(storage.MemoryStorage(), "1 per minute") assert limiter.resets_in("foo") is None while limiter.hit("foo"): pass assert limiter.resets_in("foo") > datetime.timedelta(seconds=0) assert limiter.resets_in("foo") < datetime.timedelta(seconds=60)
def test_namespacing(self): storage_ = storage.MemoryStorage() limiter1 = RateLimiter(storage_, "1 per minute", identifiers=["foo"]) limiter2 = RateLimiter(storage_, "1 per minute") assert limiter1.test("bar") assert limiter2.test("bar") while limiter1.hit("bar"): pass assert limiter2.test("bar") assert not limiter1.test("bar")
def test_basic(self): limiter = RateLimiter(storage.MemoryStorage(), "1 per minute", identifiers=["foo"]) assert limiter.test("foo") assert limiter.test("bar") while limiter.hit("bar"): pass assert limiter.test("foo") assert not limiter.test("bar")
def test_clear(self, metrics): limiter = RateLimiter(storage.MemoryStorage(), "1 per minute", metrics=metrics) assert limiter.test("foo") while limiter.hit("foo"): pass assert not limiter.test("foo") limiter.clear("foo") assert limiter.test("foo")
def test_results_in_expired(self): limiter = RateLimiter(storage.MemoryStorage(), "1 per minute; 1 per hour; 1 per day") current = datetime.datetime.now(tz=datetime.timezone.utc) stats = iter([ (0, 0), ((current + datetime.timedelta(seconds=60)).timestamp(), 0), ((current + datetime.timedelta(seconds=5)).timestamp(), 0), ]) limiter._window = pretend.stub( get_window_stats=lambda l, *a: next(stats)) resets_in = limiter.resets_in("foo") assert resets_in > datetime.timedelta(seconds=0) assert resets_in <= datetime.timedelta(seconds=5)
from limits import storage from limits import strategies from limits import parse from sanic import exceptions class TooManyRequests(exceptions.InvalidUsage): status_code = 429 _MEMORY_STORAGE = storage.MemoryStorage() _MOVING_WINDOW = strategies.MovingWindowRateLimiter(_MEMORY_STORAGE) _LIMIT_MESSAGE = "Paste creation limited at 6 per minute." def limiter(frequency): frequency = parse(frequency) def decorator(function): def wrapper(request, *args, **kwargs): namespace = f"{function.__module__}.{function.__name__}" ip = request.ip[0] or '127.0.0.1' ip = request.headers.get("X-Forwarded-For", ip) if not _MOVING_WINDOW.hit(frequency, namespace, ip): raise TooManyRequests(_LIMIT_MESSAGE) return function(request, *args, **kwargs) return wrapper return decorator