limitra 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- limitra-0.0.1/.github/workflows/ci.yml +75 -0
- limitra-0.0.1/.github/workflows/python-package.yml +28 -0
- limitra-0.0.1/.gitignore +34 -0
- limitra-0.0.1/.mise-tasks/black +4 -0
- limitra-0.0.1/.mise-tasks/check +7 -0
- limitra-0.0.1/.mise-tasks/ci +60 -0
- limitra-0.0.1/.mise-tasks/doc +7 -0
- limitra-0.0.1/.mise-tasks/install +4 -0
- limitra-0.0.1/.mise-tasks/isort +4 -0
- limitra-0.0.1/.mise-tasks/mypy +4 -0
- limitra-0.0.1/.mise-tasks/pylint +4 -0
- limitra-0.0.1/.mise-tasks/test +4 -0
- limitra-0.0.1/.mise-tasks/test-no-redis +4 -0
- limitra-0.0.1/LICENSE +21 -0
- limitra-0.0.1/PKG-INFO +66 -0
- limitra-0.0.1/README.md +35 -0
- limitra-0.0.1/docs/getting-started/configuration.md +129 -0
- limitra-0.0.1/docs/getting-started/installation.md +30 -0
- limitra-0.0.1/docs/getting-started/reference.md +155 -0
- limitra-0.0.1/docs/guide/algorithms.md +186 -0
- limitra-0.0.1/docs/guide/backends.md +82 -0
- limitra-0.0.1/docs/guide/decorator.md +192 -0
- limitra-0.0.1/docs/guide/error-handling.md +175 -0
- limitra-0.0.1/docs/images/fixed_window.png +0 -0
- limitra-0.0.1/docs/images/leaky_bucket.png +0 -0
- limitra-0.0.1/docs/images/logo.png +0 -0
- limitra-0.0.1/docs/images/sliding_window.png +0 -0
- limitra-0.0.1/docs/images/token_bucket.png +0 -0
- limitra-0.0.1/docs/index.md +57 -0
- limitra-0.0.1/docs/integrations/django.md +53 -0
- limitra-0.0.1/docs/integrations/fastapi.md +63 -0
- limitra-0.0.1/docs/integrations/flask.md +59 -0
- limitra-0.0.1/docs/logo.png +0 -0
- limitra-0.0.1/docs/stylesheets/extra.css +3 -0
- limitra-0.0.1/limitra/__init__.py +27 -0
- limitra-0.0.1/limitra/_config.py +95 -0
- limitra-0.0.1/limitra/_decorator.py +279 -0
- limitra-0.0.1/limitra/_version.py +24 -0
- limitra-0.0.1/limitra/algorithms/__init__.py +23 -0
- limitra-0.0.1/limitra/algorithms/fixed_window.py +133 -0
- limitra-0.0.1/limitra/algorithms/leaky_bucket.py +139 -0
- limitra-0.0.1/limitra/algorithms/sliding_window.py +144 -0
- limitra-0.0.1/limitra/algorithms/token_bucket.py +136 -0
- limitra-0.0.1/limitra/base.py +152 -0
- limitra-0.0.1/limitra/exceptions.py +15 -0
- limitra-0.0.1/limitra/integrations/__init__.py +0 -0
- limitra-0.0.1/limitra/py.typed +0 -0
- limitra-0.0.1/limitra.egg-info/PKG-INFO +66 -0
- limitra-0.0.1/limitra.egg-info/SOURCES.txt +67 -0
- limitra-0.0.1/limitra.egg-info/dependency_links.txt +1 -0
- limitra-0.0.1/limitra.egg-info/top_level.txt +1 -0
- limitra-0.0.1/mise.toml +15 -0
- limitra-0.0.1/mkdocs.yml +65 -0
- limitra-0.0.1/pyproject.toml +90 -0
- limitra-0.0.1/requirements-dev.txt +18 -0
- limitra-0.0.1/requirements-docs.txt +1 -0
- limitra-0.0.1/setup.cfg +4 -0
- limitra-0.0.1/tests/__init__.py +0 -0
- limitra-0.0.1/tests/conftest.py +63 -0
- limitra-0.0.1/tests/integrations/__init__.py +0 -0
- limitra-0.0.1/tests/integrations/test_django.py +175 -0
- limitra-0.0.1/tests/integrations/test_fastapi.py +145 -0
- limitra-0.0.1/tests/integrations/test_flask.py +141 -0
- limitra-0.0.1/tests/redis/__init__.py +0 -0
- limitra-0.0.1/tests/redis/test_algorithms.py +172 -0
- limitra-0.0.1/tests/redis/test_fail_open.py +67 -0
- limitra-0.0.1/tests/shared.py +48 -0
- limitra-0.0.1/tests/test_algorithms.py +118 -0
- limitra-0.0.1/tests/test_limitra.py +600 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: ["main"]
|
|
8
|
+
|
|
9
|
+
permissions: {}
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
name: Python ${{ matrix.python-version }}
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
strategy:
|
|
18
|
+
fail-fast: false
|
|
19
|
+
matrix:
|
|
20
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
with:
|
|
25
|
+
persist-credentials: false
|
|
26
|
+
|
|
27
|
+
- uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: ${{ matrix.python-version }}
|
|
30
|
+
allow-prereleases: true
|
|
31
|
+
|
|
32
|
+
- uses: astral-sh/setup-uv@v4
|
|
33
|
+
|
|
34
|
+
- uses: jdx/mise-action@v2
|
|
35
|
+
with:
|
|
36
|
+
install: false
|
|
37
|
+
|
|
38
|
+
- name: Install dependencies
|
|
39
|
+
run: uv pip install --system -e '.' -r requirements-dev.txt
|
|
40
|
+
|
|
41
|
+
- name: Lint
|
|
42
|
+
run: mise run pylint
|
|
43
|
+
|
|
44
|
+
- name: Type check
|
|
45
|
+
run: mise run mypy
|
|
46
|
+
|
|
47
|
+
- name: Run tests
|
|
48
|
+
run: mise run test
|
|
49
|
+
|
|
50
|
+
test-min-deps:
|
|
51
|
+
name: Min deps / Python 3.10
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
permissions:
|
|
54
|
+
contents: read
|
|
55
|
+
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@v4
|
|
58
|
+
with:
|
|
59
|
+
persist-credentials: false
|
|
60
|
+
|
|
61
|
+
- uses: actions/setup-python@v5
|
|
62
|
+
with:
|
|
63
|
+
python-version: "3.10"
|
|
64
|
+
|
|
65
|
+
- uses: astral-sh/setup-uv@v4
|
|
66
|
+
|
|
67
|
+
- uses: jdx/mise-action@v2
|
|
68
|
+
with:
|
|
69
|
+
install: false
|
|
70
|
+
|
|
71
|
+
- name: Install minimum dependencies
|
|
72
|
+
run: uv pip install --system --resolution lowest-direct -e '.' -r requirements-dev.txt
|
|
73
|
+
|
|
74
|
+
- name: Run tests
|
|
75
|
+
run: mise run test
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
permissions: {}
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
publish:
|
|
11
|
+
name: Build & publish
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
persist-credentials: false
|
|
21
|
+
|
|
22
|
+
- uses: astral-sh/setup-uv@v4
|
|
23
|
+
|
|
24
|
+
- name: Build
|
|
25
|
+
run: uv build
|
|
26
|
+
|
|
27
|
+
- name: Publish
|
|
28
|
+
run: uv publish
|
limitra-0.0.1/.gitignore
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Generated by setuptools-scm
|
|
2
|
+
limitra/_version.py
|
|
3
|
+
|
|
4
|
+
# Virtual environment
|
|
5
|
+
.venv/
|
|
6
|
+
|
|
7
|
+
# Python bytecode
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[cod]
|
|
10
|
+
|
|
11
|
+
# Build / distribution
|
|
12
|
+
build/
|
|
13
|
+
dist/
|
|
14
|
+
*.egg-info/
|
|
15
|
+
*.egg
|
|
16
|
+
|
|
17
|
+
# MkDocs
|
|
18
|
+
site/
|
|
19
|
+
|
|
20
|
+
# Test & coverage
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.coverage
|
|
23
|
+
.coverage.*
|
|
24
|
+
htmlcov/
|
|
25
|
+
|
|
26
|
+
# Type checking & linting
|
|
27
|
+
.mypy_cache/
|
|
28
|
+
.pylint.d/
|
|
29
|
+
|
|
30
|
+
# OS
|
|
31
|
+
.DS_Store
|
|
32
|
+
|
|
33
|
+
# mise
|
|
34
|
+
.mise.local.toml
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#MISE description="Run full CI workflow (lint + typecheck + tests) across all Python versions from the GitHub Actions matrix"
|
|
3
|
+
VERSIONS=("3.10" "3.11" "3.12" "3.13" "3.14")
|
|
4
|
+
FAILED=()
|
|
5
|
+
|
|
6
|
+
CI_BASE="$HOME/.cache/limitra-ci-$$"
|
|
7
|
+
mkdir -p "$CI_BASE"
|
|
8
|
+
trap 'rm -rf "$CI_BASE"' EXIT
|
|
9
|
+
|
|
10
|
+
export TMPDIR="$HOME/.cache/limitra-tmp"
|
|
11
|
+
export UV_CACHE_DIR="$HOME/.cache/uv"
|
|
12
|
+
mkdir -p "$TMPDIR" "$UV_CACHE_DIR"
|
|
13
|
+
|
|
14
|
+
for version in "${VERSIONS[@]}"; do
|
|
15
|
+
echo ""
|
|
16
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
17
|
+
echo " Python $version"
|
|
18
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
19
|
+
|
|
20
|
+
mise install python@$version
|
|
21
|
+
|
|
22
|
+
VENV="$CI_BASE/${version}/venv"
|
|
23
|
+
uv venv "$VENV" --python $version --seed -q
|
|
24
|
+
uv pip install -q -r requirements-dev.txt -e . --python "$VENV/bin/python"
|
|
25
|
+
|
|
26
|
+
echo "--- Lint ---"
|
|
27
|
+
if ! "$VENV/bin/pylint" limitra tests/; then
|
|
28
|
+
FAILED+=("$version:lint"); continue
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
echo "--- Type check ---"
|
|
32
|
+
if ! "$VENV/bin/mypy" limitra --strict; then
|
|
33
|
+
FAILED+=("$version:mypy"); continue
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
echo "--- Tests ---"
|
|
37
|
+
if ! "$VENV/bin/pytest" -v --tb=short; then
|
|
38
|
+
FAILED+=("$version:tests")
|
|
39
|
+
fi
|
|
40
|
+
done
|
|
41
|
+
|
|
42
|
+
echo ""
|
|
43
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
44
|
+
echo " Min deps / Python 3.10"
|
|
45
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
46
|
+
|
|
47
|
+
VENV="$CI_BASE/3.10-min/venv"
|
|
48
|
+
uv venv "$VENV" --python 3.10 --seed -q
|
|
49
|
+
uv pip install -q --resolution lowest-direct -r requirements-dev.txt -e . --python "$VENV/bin/python"
|
|
50
|
+
|
|
51
|
+
if ! "$VENV/bin/pytest" -v --tb=short; then
|
|
52
|
+
FAILED+=("3.10:min-deps")
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
echo ""
|
|
56
|
+
if [ ${#FAILED[@]} -gt 0 ]; then
|
|
57
|
+
echo "FAILED: ${FAILED[*]}"
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
echo "All versions passed!"
|
limitra-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Léo Kling
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
limitra-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: limitra
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Simple, framework-agnostic rate limiting for Python — sliding window, token bucket, leaky bucket, fixed window, memory and Redis backends
|
|
5
|
+
Author-email: Léo Kling <contact@leok.dev>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/leo-kling/limitra
|
|
8
|
+
Project-URL: Repository, https://github.com/leo-kling/limitra
|
|
9
|
+
Project-URL: Issues, https://github.com/leo-kling/limitra/issues
|
|
10
|
+
Keywords: rate-limit,rate-limiter,rate-limiting,ratelimit,throttle,throttling,sliding-window,token-bucket,leaky-bucket,fixed-window,fastapi,django,flask,redis,api
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Classifier: Framework :: AsyncIO
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
<p align="center">
|
|
33
|
+
<img src="https://limitra.pages.dev/logo.png" alt="Limitra" width="120" />
|
|
34
|
+
</p>
|
|
35
|
+
|
|
36
|
+
<h1 align="center">Limitra</h1>
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<img src="https://github.com/leo-kling/limitra/actions/workflows/ci.yml/badge.svg" alt="CI" />
|
|
40
|
+
<img src="https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13%20|%203.14-blue" alt="Python versions" />
|
|
41
|
+
<img src="https://img.shields.io/badge/license-MIT-lightgrey" alt="MIT license" />
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
<p align="center">Simple, framework-agnostic rate limiting for Python.</p>
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install limitra
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from limitra import LimitraConfig, rate_limit
|
|
58
|
+
|
|
59
|
+
LimitraConfig(redis_url="redis://localhost:6379", project="my-service")
|
|
60
|
+
|
|
61
|
+
@rate_limit(requests=10, window=60, key="user_id")
|
|
62
|
+
def create_post(user_id: str, content: str):
|
|
63
|
+
...
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
→ Full documentation: **[limitra.leok.dev](https://limitra.leok.dev)**
|
limitra-0.0.1/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://limitra.pages.dev/logo.png" alt="Limitra" width="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Limitra</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img src="https://github.com/leo-kling/limitra/actions/workflows/ci.yml/badge.svg" alt="CI" />
|
|
9
|
+
<img src="https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13%20|%203.14-blue" alt="Python versions" />
|
|
10
|
+
<img src="https://img.shields.io/badge/license-MIT-lightgrey" alt="MIT license" />
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">Simple, framework-agnostic rate limiting for Python.</p>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install limitra
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from limitra import LimitraConfig, rate_limit
|
|
27
|
+
|
|
28
|
+
LimitraConfig(redis_url="redis://localhost:6379", project="my-service")
|
|
29
|
+
|
|
30
|
+
@rate_limit(requests=10, window=60, key="user_id")
|
|
31
|
+
def create_post(user_id: str, content: str):
|
|
32
|
+
...
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
→ Full documentation: **[limitra.leok.dev](https://limitra.leok.dev)**
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
## How it works
|
|
4
|
+
|
|
5
|
+
Limitra uses two levels of configuration:
|
|
6
|
+
|
|
7
|
+
| | `LimitraConfig()` | `@rate_limit()` |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| **When** | Once at app startup | On each function to protect |
|
|
10
|
+
| **What** | Redis connection, namespace, defaults | The rule: how many requests, which window |
|
|
11
|
+
| **Scope** | Global — applies to all limiters | Local — this function only |
|
|
12
|
+
|
|
13
|
+
In practice: `LimitraConfig()` connects Limitra to Redis and sets global defaults. `@rate_limit()` defines the rule on a specific function and inherits everything set by `LimitraConfig()`.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Full example
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
# main.py (or app.py, startup.py...)
|
|
21
|
+
from limitra import LimitraConfig, rate_limit, RateLimitExceeded
|
|
22
|
+
|
|
23
|
+
# 1. Global setup — call once at startup
|
|
24
|
+
LimitraConfig(
|
|
25
|
+
redis_url="redis://localhost:6379",
|
|
26
|
+
project="my-service", # isolates Redis keys for this service
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# 2. Decorate each function to protect
|
|
30
|
+
@rate_limit(requests=10, window=60, key="user_id")
|
|
31
|
+
def create_post(user_id: str, content: str):
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
@rate_limit(requests=1000, window=60)
|
|
35
|
+
def public_feed():
|
|
36
|
+
...
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Without Redis — memory backend
|
|
42
|
+
|
|
43
|
+
If you don't need Redis (local development, single-process app), `LimitraConfig()` is not required. The memory backend is used by default:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from limitra import rate_limit
|
|
47
|
+
|
|
48
|
+
@rate_limit(requests=10, window=60, key="user_id", backend="memory")
|
|
49
|
+
def create_post(user_id: str, content: str):
|
|
50
|
+
...
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
!!! warning
|
|
54
|
+
The memory backend is not shared between processes or restarts. For production apps with multiple workers, use Redis.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## `LimitraConfig()` parameters
|
|
59
|
+
|
|
60
|
+
### Redis connection
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# Option A — URL
|
|
64
|
+
LimitraConfig(redis_url="redis://localhost:6379")
|
|
65
|
+
LimitraConfig(redis_url="rediss://user:pass@host:6380/0") # TLS
|
|
66
|
+
|
|
67
|
+
# Option B — pre-built client (useful with connection pooling)
|
|
68
|
+
import redis
|
|
69
|
+
client = redis.Redis(host="localhost", port=6379, db=0)
|
|
70
|
+
LimitraConfig(redis_client=client)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
| Parameter | Type | Default | Description |
|
|
74
|
+
|---|---|---|---|
|
|
75
|
+
| `redis_url` | `str` | `None` | Connection URL passed to `redis.from_url()` |
|
|
76
|
+
| `redis_client` | `Redis` | `None` | Pre-built Redis client |
|
|
77
|
+
| `redis_db` | `int` | `0` | Redis database index (only used with `redis_url`) |
|
|
78
|
+
|
|
79
|
+
### Key namespace
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
LimitraConfig(
|
|
83
|
+
redis_url="redis://shared:6379",
|
|
84
|
+
project="user-svc", # → keys: user-svc:user:alice
|
|
85
|
+
prefix="rl", # → keys: rl:user-svc:user:alice
|
|
86
|
+
suffix="v2", # → keys: rl:user-svc:user:alice:v2
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| Parameter | Type | Default | Description |
|
|
91
|
+
|---|---|---|---|
|
|
92
|
+
| `project` | `str` | `None` | Namespace prefix — recommended to isolate microservices sharing the same Redis |
|
|
93
|
+
| `prefix` | `str` | `None` | Key prefix added before `project` |
|
|
94
|
+
| `suffix` | `str` | `None` | Key suffix added after the identifier |
|
|
95
|
+
|
|
96
|
+
### Defaults
|
|
97
|
+
|
|
98
|
+
These values are used by `@rate_limit()` when the corresponding parameters are not specified.
|
|
99
|
+
|
|
100
|
+
| Parameter | Type | Default | Description |
|
|
101
|
+
|---|---|---|---|
|
|
102
|
+
| `default_algorithm` | `str` | `"sliding_window"` | Default algorithm |
|
|
103
|
+
| `default_backend` | `str` | `"redis"` | Default backend after `LimitraConfig()` is called |
|
|
104
|
+
| `fail_open` | `bool` | `False` | When `True`, allow requests if Redis is unreachable instead of raising an error |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Overriding `LimitraConfig()` per function
|
|
109
|
+
|
|
110
|
+
Any `@rate_limit()` parameter overrides the global setting for that function only:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
LimitraConfig(redis_url="redis://localhost:6379", project="my-svc")
|
|
114
|
+
|
|
115
|
+
# Uses all globals — project "my-svc", redis backend
|
|
116
|
+
@rate_limit(requests=100, window=60)
|
|
117
|
+
def normal_endpoint():
|
|
118
|
+
...
|
|
119
|
+
|
|
120
|
+
# Overrides the backend for this function only
|
|
121
|
+
@rate_limit(requests=100, window=60, backend="memory")
|
|
122
|
+
def local_only_endpoint():
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
# Overrides the project for this function only
|
|
126
|
+
@rate_limit(requests=5, window=60, project="billing")
|
|
127
|
+
def billing_endpoint():
|
|
128
|
+
...
|
|
129
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
**Python 3.10+** is required.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install limitra
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Limitra has zero runtime dependencies. The memory backend works out of the box.
|
|
10
|
+
|
|
11
|
+
## Redis backend
|
|
12
|
+
|
|
13
|
+
Limitra does not ship `redis` as a dependency — if you're using Redis for rate limiting, you're most likely already using it elsewhere in your project.
|
|
14
|
+
|
|
15
|
+
Add it to your project's own dependencies:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install redis # or: poetry add redis, uv add redis, etc.
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Limitra detects it automatically. Any `redis>=4.0` version works.
|
|
22
|
+
|
|
23
|
+
!!! note
|
|
24
|
+
If `redis` is not installed and you configure a Redis backend, Limitra raises `ImportError` with a clear message at startup.
|
|
25
|
+
|
|
26
|
+
## Local Redis for development
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
docker run -p 6379:6379 redis:alpine
|
|
30
|
+
```
|