# Upstash Ratelimit Python SDK
upstash-ratelimit is a connectionless rate limiting library for Python, designed to be used in serverless environments such as:
- AWS Lambda
- Vercel Serverless
- Google Cloud Functions
- and other environments where HTTP is preferred over TCP.
The SDK is currently compatible with Python 3.8 and above.
<!-- toc -->
- [Upstash Ratelimit Python SDK](#upstash-ratelimit-python-sdk)
- [Quick Start](#quick-start)
- [Install](#install)
- [Create database](#create-database)
- [Usage](#usage)
- [Block until ready](#block-until-ready)
- [Using multiple limits](#using-multiple-limits)
- [Ratelimiting algorithms](#ratelimiting-algorithms)
- [Fixed Window](#fixed-window)
- [Pros](#pros)
- [Cons](#cons)
- [Usage](#usage-1)
- [Sliding Window](#sliding-window)
- [Pros](#pros-1)
- [Cons](#cons-1)
- [Usage](#usage-2)
- [Token Bucket](#token-bucket)
- [Pros](#pros-2)
- [Cons](#cons-2)
- [Usage](#usage-3)
- [Contributing](#contributing)
- [Preparing the environment](#preparing-the-environment)
- [Running tests](#running-tests)
<!-- tocstop -->
# Quick Start
## Install
```bash
pip install upstash-ratelimit
```
## Create database
To be able to use upstash-ratelimit, you need to create a database on [Upstash](https://console.upstash.com/).
## Usage
For possible Redis client configurations, have a look at the [Redis SDK repository](https://github.com/upstash/redis-python).
> This library supports asyncio as well. To use it, import the asyncio-based
variant from the `upstash_ratelimit.asyncio` module.
```python
from upstash_ratelimit import Ratelimit, FixedWindow
from upstash_redis import Redis
# Create a new ratelimiter, that allows 10 requests per 10 seconds
ratelimit = Ratelimit(
redis=Redis.from_env(),
limiter=FixedWindow(max_requests=10, window=10),
# Optional prefix for the keys used in Redis. This is useful
# if you want to share a Redis instance with other applications
# and want to avoid key collisions. The default prefix is
# "@upstash/ratelimit"
prefix="@upstash/ratelimit",
)
# Use a constant string to limit all requests with a single ratelimit
# Or use a user ID, API key or IP address for individual limits.
identifier = "api"
response = ratelimit.limit(identifier)
if not response.allowed:
print("Unable to process at this time")
else:
do_expensive_calculation()
print("Here you go!")
```
The `limit` method also returns the following metadata:
```python
@dataclasses.dataclass
class Response:
allowed: bool
"""
Whether the request may pass(`True`) or exceeded the limit(`False`)
"""
limit: int
"""
Maximum number of requests allowed within a window.
"""
remaining: int
"""
How many requests the user has left within the current window.
"""
reset: float
"""
Unix timestamp in seconds when the limits are reset
"""
```
## Block until ready
You also have the option to try and wait for a request to pass in the given timeout.
It is very similar to the `limit` method and takes an identifier and returns the same
response. However if the current limit has already been exceeded, it will automatically
wait until the next window starts and will try again. Setting the timeout parameter (in seconds) will cause the method to block a finite amount of time.
```python
from upstash_ratelimit import Ratelimit, SlidingWindow
from upstash_redis import Redis
# Create a new ratelimiter, that allows 10 requests per 10 seconds
ratelimit = Ratelimit(
redis=Redis.from_env(),
limiter=SlidingWindow(max_requests=10, window=10),
)
response = ratelimit.block_until_ready("id", timeout=30)
if not response.allowed:
print("Unable to process, even after 30 seconds")
else:
do_expensive_calculation()
print("Here you go!")
```
## Using multiple limits
Sometimes you might want to apply different limits to different users. For example you might want to allow 10 requests per 10 seconds for free users, but 60 requests per 10 seconds for paid users.
Here's how you could do that:
```python
from upstash_ratelimit import Ratelimit, SlidingWindow
from upstash_redis import Redis
class MultiRL:
def __init__(self) -> None:
redis = Redis.from_env()
self.free = Ratelimit(
redis=redis,
limiter=SlidingWindow(max_requests=10, window=10),
prefix="ratelimit:free",
)
self.paid = Ratelimit(
redis=redis,
limiter=SlidingWindow(max_requests=60, window=10),
prefix="ratelimit:paid",
)
# Create a new ratelimiter, that allows 10 requests per 10 seconds
ratelimit = MultiRL()
ratelimit.free.limit("userIP")
ratelimit.paid.limit("userIP")
```
# Ratelimiting algorithms
## Fixed Window
This algorithm divides time into fixed durations/windows. For example each window is 10 seconds long. When a new request comes in, the current time is used to determine the window and a counter is increased. If the counter is larger than the set limit, the request is rejected.
### Pros
- Very cheap in terms of data size and computation
- Newer requests are not starved due to a high burst in the past
### Cons
- Can cause high bursts at the window boundaries to leak through
- Causes request stampedes if many users are trying to access your server, whenever a new window begins
### Usage
```python
from upstash_ratelimit import Ratelimit, FixedWindow
from upstash_redis import Redis
ratelimit = Ratelimit(
redis=Redis.from_env(),
limiter=FixedWindow(max_requests=10, window=10),
)
```
## Sliding Window
Builds on top of fixed window but instead of a fixed window, we use a rolling window. Take this example: We have a rate limit of 10 requests per 1 minute. We divide time into 1 minute slices, just like in the fixed window algorithm. Window 1 will be from 00:00:00 to 00:01:00 (HH:MM:SS). Let's assume it is currently 00:01:15 and we have received 4 requests in the first window and 5 requests so far in the current window. The approximation to determine if the request should pass works like this:
```python
limit = 10
# 4 request from the old window, weighted + requests in current window
rate = 4 * ((60 - 15) / 60) + 5 = 8
return rate < limit # True means we should allow the request
```
### Pros
- Solves the issue near boundary from fixed window.
### Cons
- More expensive in terms of storage and computation
- It's only an approximation because it assumes a uniform request flow in the previous window
### Usage
```python
from upstash_ratelimit import Ratelimit, SlidingWindow
from upstash_redis import Redis
ratelimit = Ratelimit(
redis=Redis.from_env(),
limiter=SlidingWindow(max_requests=10, window=10),
)
```
## Token Bucket
Consider a bucket filled with maximum number of tokens that refills constantly at a rate per interval. Every request will remove one token from the bucket and if there is no token to take, the request is rejected.
### Pros
- Bursts of requests are smoothed out and you can process them at a constant rate.
- Allows setting a higher initial burst limit by setting maximum number of tokens higher than the refill rate
### Cons
- Expensive in terms of computation
### Usage
```python
from upstash_ratelimit import Ratelimit, TokenBucket
from upstash_redis import Redis
ratelimit = Ratelimit(
redis=Redis.from_env(),
limiter=TokenBucket(max_tokens=10, refill_rate=5, interval=10),
)
```
# Custom Rates
When rate limiting, you may want different requests to consume different amounts of tokens.
This could be useful when processing batches of requests where you want to rate limit based
on items in the batch or when you want to rate limit based on the number of tokens.
To achieve this, you can simply pass `rate` parameter when calling the limit method:
```python
from upstash_ratelimit import Ratelimit, FixedWindow
from upstash_redis import Redis
ratelimit = Ratelimit(
redis=Redis.from_env(),
limiter=FixedWindow(max_requests=10, window=10),
)
# pass rate as 5 to subtract 5 from the number of
# allowed requests in the window:
identifier = "api"
response = ratelimit.limit(identifier, rate=5)
```
# Contributing
## Preparing the environment
This project uses [Poetry](https://python-poetry.org) for packaging and dependency management. Make sure you are able to create the poetry shell with relevant dependencies.
You will also need a database on [Upstash](https://console.upstash.com/).
## Running tests
To run all the tests, make sure the poetry virtual environment activated with all
the necessary dependencies. Set the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` environment variables and run:
```bash
poetry run pytest
```
Raw data
{
"_id": null,
"home_page": "https://github.com/upstash/ratelimit-python",
"name": "upstash-ratelimit",
"maintainer": "Upstash",
"docs_url": null,
"requires_python": "<4.0,>=3.8",
"maintainer_email": "support@upstash.com",
"keywords": "ratelimit, rate limit, Upstash rate limit, Redis rate limit",
"author": "Upstash",
"author_email": "support@upstash.com",
"download_url": "https://files.pythonhosted.org/packages/6f/95/74a3a7547a68f5e8d9f8cb99a6a73da96f13a359c6f7f4f15deceecabb35/upstash_ratelimit-1.1.0.tar.gz",
"platform": null,
"description": "# Upstash Ratelimit Python SDK\n\nupstash-ratelimit is a connectionless rate limiting library for Python, designed to be used in serverless environments such as:\n- AWS Lambda\n- Vercel Serverless\n- Google Cloud Functions\n- and other environments where HTTP is preferred over TCP.\n\nThe SDK is currently compatible with Python 3.8 and above.\n\n<!-- toc -->\n- [Upstash Ratelimit Python SDK](#upstash-ratelimit-python-sdk)\n- [Quick Start](#quick-start)\n - [Install](#install)\n - [Create database](#create-database)\n - [Usage](#usage)\n - [Block until ready](#block-until-ready)\n - [Using multiple limits](#using-multiple-limits)\n- [Ratelimiting algorithms](#ratelimiting-algorithms)\n - [Fixed Window](#fixed-window)\n - [Pros](#pros)\n - [Cons](#cons)\n - [Usage](#usage-1)\n - [Sliding Window](#sliding-window)\n - [Pros](#pros-1)\n - [Cons](#cons-1)\n - [Usage](#usage-2)\n - [Token Bucket](#token-bucket)\n - [Pros](#pros-2)\n - [Cons](#cons-2)\n - [Usage](#usage-3)\n- [Contributing](#contributing)\n - [Preparing the environment](#preparing-the-environment)\n - [Running tests](#running-tests)\n<!-- tocstop -->\n\n# Quick Start\n\n## Install\n\n```bash\npip install upstash-ratelimit\n```\n\n## Create database\nTo be able to use upstash-ratelimit, you need to create a database on [Upstash](https://console.upstash.com/).\n\n## Usage\n\nFor possible Redis client configurations, have a look at the [Redis SDK repository](https://github.com/upstash/redis-python).\n\n> This library supports asyncio as well. To use it, import the asyncio-based\n variant from the `upstash_ratelimit.asyncio` module.\n\n```python\nfrom upstash_ratelimit import Ratelimit, FixedWindow\nfrom upstash_redis import Redis\n\n# Create a new ratelimiter, that allows 10 requests per 10 seconds\nratelimit = Ratelimit(\n redis=Redis.from_env(),\n limiter=FixedWindow(max_requests=10, window=10),\n # Optional prefix for the keys used in Redis. This is useful\n # if you want to share a Redis instance with other applications\n # and want to avoid key collisions. The default prefix is\n # \"@upstash/ratelimit\"\n prefix=\"@upstash/ratelimit\",\n)\n\n# Use a constant string to limit all requests with a single ratelimit\n# Or use a user ID, API key or IP address for individual limits.\nidentifier = \"api\"\nresponse = ratelimit.limit(identifier)\n\nif not response.allowed:\n print(\"Unable to process at this time\")\nelse:\n do_expensive_calculation()\n print(\"Here you go!\")\n\n```\n\nThe `limit` method also returns the following metadata:\n\n\n```python\n@dataclasses.dataclass\nclass Response:\n allowed: bool\n \"\"\"\n Whether the request may pass(`True`) or exceeded the limit(`False`)\n \"\"\"\n\n limit: int\n \"\"\"\n Maximum number of requests allowed within a window.\n \"\"\"\n\n remaining: int\n \"\"\"\n How many requests the user has left within the current window.\n \"\"\"\n\n reset: float\n \"\"\"\n Unix timestamp in seconds when the limits are reset\n \"\"\"\n```\n\n## Block until ready\n\nYou also have the option to try and wait for a request to pass in the given timeout.\n\nIt is very similar to the `limit` method and takes an identifier and returns the same \nresponse. However if the current limit has already been exceeded, it will automatically \nwait until the next window starts and will try again. Setting the timeout parameter (in seconds) will cause the method to block a finite amount of time.\n\n```python\nfrom upstash_ratelimit import Ratelimit, SlidingWindow\nfrom upstash_redis import Redis\n\n# Create a new ratelimiter, that allows 10 requests per 10 seconds\nratelimit = Ratelimit(\n redis=Redis.from_env(),\n limiter=SlidingWindow(max_requests=10, window=10),\n)\n\nresponse = ratelimit.block_until_ready(\"id\", timeout=30)\n\nif not response.allowed:\n print(\"Unable to process, even after 30 seconds\")\nelse:\n do_expensive_calculation()\n print(\"Here you go!\")\n```\n\n\n## Using multiple limits\nSometimes you might want to apply different limits to different users. For example you might want to allow 10 requests per 10 seconds for free users, but 60 requests per 10 seconds for paid users.\n\nHere's how you could do that:\n\n```python\nfrom upstash_ratelimit import Ratelimit, SlidingWindow\nfrom upstash_redis import Redis\n\nclass MultiRL:\n def __init__(self) -> None:\n redis = Redis.from_env()\n self.free = Ratelimit(\n redis=redis,\n limiter=SlidingWindow(max_requests=10, window=10),\n prefix=\"ratelimit:free\",\n )\n\n self.paid = Ratelimit(\n redis=redis,\n limiter=SlidingWindow(max_requests=60, window=10),\n prefix=\"ratelimit:paid\",\n )\n\n# Create a new ratelimiter, that allows 10 requests per 10 seconds\nratelimit = MultiRL()\n\nratelimit.free.limit(\"userIP\")\nratelimit.paid.limit(\"userIP\")\n```\n\n# Ratelimiting algorithms\n\n## Fixed Window\n\nThis algorithm divides time into fixed durations/windows. For example each window is 10 seconds long. When a new request comes in, the current time is used to determine the window and a counter is increased. If the counter is larger than the set limit, the request is rejected.\n\n### Pros\n- Very cheap in terms of data size and computation\n- Newer requests are not starved due to a high burst in the past\n\n### Cons\n- Can cause high bursts at the window boundaries to leak through\n- Causes request stampedes if many users are trying to access your server, whenever a new window begins\n\n### Usage\n\n```python\nfrom upstash_ratelimit import Ratelimit, FixedWindow\nfrom upstash_redis import Redis\n\nratelimit = Ratelimit(\n redis=Redis.from_env(),\n limiter=FixedWindow(max_requests=10, window=10),\n)\n```\n\n## Sliding Window\n\nBuilds on top of fixed window but instead of a fixed window, we use a rolling window. Take this example: We have a rate limit of 10 requests per 1 minute. We divide time into 1 minute slices, just like in the fixed window algorithm. Window 1 will be from 00:00:00 to 00:01:00 (HH:MM:SS). Let's assume it is currently 00:01:15 and we have received 4 requests in the first window and 5 requests so far in the current window. The approximation to determine if the request should pass works like this:\n\n```python\nlimit = 10\n\n# 4 request from the old window, weighted + requests in current window\nrate = 4 * ((60 - 15) / 60) + 5 = 8\n\nreturn rate < limit # True means we should allow the request\n```\n\n### Pros\n- Solves the issue near boundary from fixed window.\n\n### Cons\n- More expensive in terms of storage and computation\n- It's only an approximation because it assumes a uniform request flow in the previous window\n\n### Usage\n\n```python\nfrom upstash_ratelimit import Ratelimit, SlidingWindow\nfrom upstash_redis import Redis\n\nratelimit = Ratelimit(\n redis=Redis.from_env(),\n limiter=SlidingWindow(max_requests=10, window=10),\n)\n```\n\n## Token Bucket\n\nConsider a bucket filled with maximum number of tokens that refills constantly at a rate per interval. Every request will remove one token from the bucket and if there is no token to take, the request is rejected.\n\n### Pros\n- Bursts of requests are smoothed out and you can process them at a constant rate.\n- Allows setting a higher initial burst limit by setting maximum number of tokens higher than the refill rate\n\n### Cons\n- Expensive in terms of computation\n\n### Usage\n\n```python\nfrom upstash_ratelimit import Ratelimit, TokenBucket\nfrom upstash_redis import Redis\n\nratelimit = Ratelimit(\n redis=Redis.from_env(),\n limiter=TokenBucket(max_tokens=10, refill_rate=5, interval=10),\n)\n```\n\n# Custom Rates\n\nWhen rate limiting, you may want different requests to consume different amounts of tokens.\nThis could be useful when processing batches of requests where you want to rate limit based\non items in the batch or when you want to rate limit based on the number of tokens.\n\nTo achieve this, you can simply pass `rate` parameter when calling the limit method:\n\n```python\n\nfrom upstash_ratelimit import Ratelimit, FixedWindow\nfrom upstash_redis import Redis\n\nratelimit = Ratelimit(\n redis=Redis.from_env(),\n limiter=FixedWindow(max_requests=10, window=10),\n)\n\n# pass rate as 5 to subtract 5 from the number of\n# allowed requests in the window:\nidentifier = \"api\"\nresponse = ratelimit.limit(identifier, rate=5)\n```\n\n# Contributing\n\n## Preparing the environment\nThis project uses [Poetry](https://python-poetry.org) for packaging and dependency management. Make sure you are able to create the poetry shell with relevant dependencies.\n\nYou will also need a database on [Upstash](https://console.upstash.com/).\n\n## Running tests\nTo run all the tests, make sure the poetry virtual environment activated with all \nthe necessary dependencies. Set the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` environment variables and run:\n\n```bash\npoetry run pytest\n```\n",
"bugtrack_url": null,
"license": null,
"summary": "Serverless ratelimiting package from Upstash",
"version": "1.1.0",
"project_urls": {
"Homepage": "https://github.com/upstash/ratelimit-python",
"Repository": "https://github.com/upstash/ratelimit-python"
},
"split_keywords": [
"ratelimit",
" rate limit",
" upstash rate limit",
" redis rate limit"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "f3bc893430e103b36be54e7a7b105b102273bda7e8c7567cd72fb1d44473a857",
"md5": "97b101ba3aca765008fe89e3fe48bb95",
"sha256": "cb3944063df199f47e4b24fd9a3760131a71cfeacd7a0182fc6ff5b920d16a8b"
},
"downloads": -1,
"filename": "upstash_ratelimit-1.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "97b101ba3aca765008fe89e3fe48bb95",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.8",
"size": 13245,
"upload_time": "2024-05-16T08:47:34",
"upload_time_iso_8601": "2024-05-16T08:47:34.048302Z",
"url": "https://files.pythonhosted.org/packages/f3/bc/893430e103b36be54e7a7b105b102273bda7e8c7567cd72fb1d44473a857/upstash_ratelimit-1.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "6f9574a3a7547a68f5e8d9f8cb99a6a73da96f13a359c6f7f4f15deceecabb35",
"md5": "7fb273ce73aa18e9f50e767c1ac5d1a2",
"sha256": "b396332ef42392c255b01958e3af3b45cd86ca2e2f78f9ea34c4714d64e56f15"
},
"downloads": -1,
"filename": "upstash_ratelimit-1.1.0.tar.gz",
"has_sig": false,
"md5_digest": "7fb273ce73aa18e9f50e767c1ac5d1a2",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.8",
"size": 12198,
"upload_time": "2024-05-16T08:47:35",
"upload_time_iso_8601": "2024-05-16T08:47:35.820664Z",
"url": "https://files.pythonhosted.org/packages/6f/95/74a3a7547a68f5e8d9f8cb99a6a73da96f13a359c6f7f4f15deceecabb35/upstash_ratelimit-1.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-05-16 08:47:35",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "upstash",
"github_project": "ratelimit-python",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "upstash-ratelimit"
}