├── ratelimit ├── __init__.py ├── time_bucketed.py └── gcra.py ├── requirements.txt ├── .gitignore ├── main_gcra.py ├── main_tb.py ├── README.md └── LICENSE /ratelimit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | redis==3.4.1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | old_main.py 3 | -------------------------------------------------------------------------------- /main_gcra.py: -------------------------------------------------------------------------------- 1 | import redis 2 | from datetime import timedelta 3 | from ratelimit import gcra 4 | 5 | 6 | r = redis.Redis(host='localhost', port=6379, db=0) 7 | requests = 10 8 | 9 | for i in range(requests): 10 | if gcra.request_is_limited(r, 'admin', 10, timedelta(minutes=1)): 11 | print ('🛑 Request is limited') 12 | else: 13 | print ('✅ Request is allowed') 14 | -------------------------------------------------------------------------------- /main_tb.py: -------------------------------------------------------------------------------- 1 | import redis 2 | from datetime import timedelta 3 | from ratelimit import time_bucketed 4 | 5 | 6 | r = redis.Redis(host='localhost', port=6379, db=0) 7 | requests = 25 8 | 9 | for i in range(requests): 10 | if time_bucketed.request_is_limited(r, 'admin', 20, timedelta(seconds=30)): 11 | print ('🛑 Request is limited') 12 | else: 13 | print ('✅ Request is allowed') 14 | -------------------------------------------------------------------------------- /ratelimit/time_bucketed.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | from redis import Redis 3 | 4 | 5 | def request_is_limited(r: Redis, key: str, limit: int, period: timedelta): 6 | if r.setnx(key, limit): 7 | r.expire(key, int(period.total_seconds())) 8 | bucket_val = r.get(key) 9 | if bucket_val and int(bucket_val) > 0: 10 | r.decrby(key, 1) 11 | return False 12 | return True 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rate limiting using Python and Redis 2 | 3 | This repository contains a Python implementation of `time-bucketed` and `GCRA` algorithms to limit requests. 4 | For more info go to the article [Rate limiting using Python and Redis](https://dev.to/astagi/rate-limiting-using-python-and-redis-58gk) 5 | 6 | ## Setup 7 | 8 | Configure [Redis](https://redis.io/) and make it running on the default port `6379`. Then install Python dependencies 9 | 10 | ```sh 11 | pip install redis 12 | ``` 13 | 14 | ## Run Time-bucketed 15 | 16 | ```sh 17 | python main_tb.py 18 | ``` 19 | 20 | ## Run GCRA 21 | 22 | ```sh 23 | python main_gcra.py 24 | ``` 25 | -------------------------------------------------------------------------------- /ratelimit/gcra.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | from redis import Redis 3 | 4 | 5 | def request_is_limited(r: Redis, key: str, limit: int, period: timedelta): 6 | period_in_seconds = int(period.total_seconds()) 7 | t = r.time()[0] 8 | separation = round(period_in_seconds / limit) 9 | r.setnx(key, 0) 10 | try: 11 | with r.lock('lock:' + key, blocking_timeout=5) as lock: 12 | tat = max(int(r.get(key)), t) 13 | if tat - t <= period_in_seconds - separation: 14 | new_tat = max(tat, t) + separation 15 | r.set(key, new_tat) 16 | return False 17 | return True 18 | except LockError: 19 | return True 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Andrea Stagi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | --------------------------------------------------------------------------------