fluxlimit 0.1.0__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.
Files changed (33) hide show
  1. fluxlimit-0.1.0/.github/workflows/ci.yml +36 -0
  2. fluxlimit-0.1.0/.github/workflows/publish-testpypi.yml +38 -0
  3. fluxlimit-0.1.0/.github/workflows/publish.yml +72 -0
  4. fluxlimit-0.1.0/.gitignore +40 -0
  5. fluxlimit-0.1.0/LICENSE +21 -0
  6. fluxlimit-0.1.0/PKG-INFO +339 -0
  7. fluxlimit-0.1.0/README.md +286 -0
  8. fluxlimit-0.1.0/pyproject.toml +93 -0
  9. fluxlimit-0.1.0/src/fluxlimit/__init__.py +74 -0
  10. fluxlimit-0.1.0/src/fluxlimit/algorithms/__init__.py +22 -0
  11. fluxlimit-0.1.0/src/fluxlimit/algorithms/base.py +64 -0
  12. fluxlimit-0.1.0/src/fluxlimit/algorithms/fixed_window.py +64 -0
  13. fluxlimit-0.1.0/src/fluxlimit/algorithms/leaky_bucket.py +64 -0
  14. fluxlimit-0.1.0/src/fluxlimit/algorithms/sliding_window.py +64 -0
  15. fluxlimit-0.1.0/src/fluxlimit/algorithms/token_bucket.py +67 -0
  16. fluxlimit-0.1.0/src/fluxlimit/backends/__init__.py +13 -0
  17. fluxlimit-0.1.0/src/fluxlimit/backends/base.py +79 -0
  18. fluxlimit-0.1.0/src/fluxlimit/backends/memory.py +207 -0
  19. fluxlimit-0.1.0/src/fluxlimit/backends/postgres.py +305 -0
  20. fluxlimit-0.1.0/src/fluxlimit/backends/redis.py +335 -0
  21. fluxlimit-0.1.0/src/fluxlimit/core.py +225 -0
  22. fluxlimit-0.1.0/src/fluxlimit/exceptions.py +33 -0
  23. fluxlimit-0.1.0/src/fluxlimit/metrics.py +68 -0
  24. fluxlimit-0.1.0/src/fluxlimit/middleware/__init__.py +27 -0
  25. fluxlimit-0.1.0/src/fluxlimit/middleware/_helpers.py +40 -0
  26. fluxlimit-0.1.0/src/fluxlimit/middleware/asgi.py +129 -0
  27. fluxlimit-0.1.0/src/fluxlimit/middleware/flask.py +234 -0
  28. fluxlimit-0.1.0/tests/test_algorithms.py +75 -0
  29. fluxlimit-0.1.0/tests/test_core.py +60 -0
  30. fluxlimit-0.1.0/tests/test_fastapi.py +181 -0
  31. fluxlimit-0.1.0/tests/test_flux_flask.py +135 -0
  32. fluxlimit-0.1.0/tests/test_middleware_imports.py +36 -0
  33. fluxlimit-0.1.0/uv.lock +2499 -0
@@ -0,0 +1,36 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ concurrency:
10
+ group: ci-${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ test:
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v5
26
+ with:
27
+ enable-cache: true
28
+
29
+ - name: Set up Python ${{ matrix.python-version }}
30
+ run: uv python install ${{ matrix.python-version }}
31
+
32
+ - name: Install dependencies
33
+ run: uv sync --extra dev
34
+
35
+ - name: Run tests
36
+ run: uv run pytest -v --cov=fluxlimit --cov-report=term-missing
@@ -0,0 +1,38 @@
1
+ name: Publish to TestPyPI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ permissions:
7
+ contents: read
8
+ id-token: write
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: ubuntu-latest
13
+ environment:
14
+ name: testpypi
15
+ url: https://test.pypi.org/p/fluxlimit
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v5
22
+
23
+ - name: Set up Python
24
+ run: uv python install 3.12
25
+
26
+ - name: Install dependencies
27
+ run: uv sync --extra dev
28
+
29
+ - name: Run tests
30
+ run: uv run pytest
31
+
32
+ - name: Build package
33
+ run: uv build
34
+
35
+ - name: Publish to TestPyPI
36
+ uses: pypa/gh-action-pypi-publish@release/v1
37
+ with:
38
+ repository-url: https://test.pypi.org/legacy/
@@ -0,0 +1,72 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ concurrency:
9
+ group: publish-pypi
10
+ cancel-in-progress: false
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ build:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v5
23
+ with:
24
+ enable-cache: true
25
+
26
+ - name: Set up Python
27
+ run: uv python install 3.12
28
+
29
+ - name: Install dependencies
30
+ run: uv sync --extra dev --locked
31
+
32
+ - name: Verify release tag matches package version
33
+ if: github.event_name == 'release'
34
+ run: |
35
+ VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
36
+ TAG="${{ github.event.release.tag_name }}"
37
+ if [ "v${VERSION}" != "${TAG}" ]; then
38
+ echo "Version mismatch: pyproject.toml has ${VERSION}, release tag is ${TAG}"
39
+ exit 1
40
+ fi
41
+
42
+ - name: Run tests
43
+ run: uv run pytest
44
+
45
+ - name: Build package
46
+ run: uv build
47
+
48
+ - name: Upload dist artifacts
49
+ uses: actions/upload-artifact@v4
50
+ with:
51
+ name: dist
52
+ path: dist/
53
+ retention-days: 1
54
+
55
+ publish:
56
+ needs: build
57
+ runs-on: ubuntu-latest
58
+ environment:
59
+ name: pypi
60
+ url: https://pypi.org/p/fluxlimit
61
+ permissions:
62
+ id-token: write
63
+
64
+ steps:
65
+ - name: Download dist artifacts
66
+ uses: actions/download-artifact@v4
67
+ with:
68
+ name: dist
69
+ path: dist/
70
+
71
+ - name: Publish to PyPI
72
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,40 @@
1
+ # Bytecode
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Virtual environments
7
+ .venv/
8
+ venv/
9
+ env/
10
+
11
+ # Packaging / build
12
+ *.egg
13
+ *.egg-info/
14
+ .eggs/
15
+ build/
16
+ dist/
17
+
18
+ # Test & coverage
19
+ .pytest_cache/
20
+ .coverage
21
+ .coverage.*
22
+ htmlcov/
23
+ .tox/
24
+ .nox/
25
+
26
+ # Type checkers & linters
27
+ .mypy_cache/
28
+ .ruff_cache/
29
+
30
+ # IDE
31
+ .idea/
32
+ .vscode/
33
+
34
+ # OS
35
+ .DS_Store
36
+ Thumbs.db
37
+
38
+ # Local env / secrets
39
+ .env
40
+ .env.*
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anand Yadav
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.
@@ -0,0 +1,339 @@
1
+ Metadata-Version: 2.4
2
+ Name: fluxlimit
3
+ Version: 0.1.0
4
+ Summary: Distributed rate limiting with fairness for Python - token bucket, leaky bucket, and window algorithms with Redis and PostgreSQL backends.
5
+ Project-URL: Homepage, https://github.com/yourusername/fluxlimit
6
+ Project-URL: Documentation, https://github.com/yourusername/fluxlimit#readme
7
+ Project-URL: Issues, https://github.com/yourusername/fluxlimit/issues
8
+ Project-URL: Changelog, https://github.com/yourusername/fluxlimit/blob/main/CHANGELOG.md
9
+ Author-email: Your Name <you@example.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: distributed,fastapi,postgresql,rate-limiter,rate-limiting,redis,throttling,token-bucket
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Framework :: AsyncIO
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: System :: Networking
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.9
27
+ Requires-Dist: fastapi>=0.100
28
+ Provides-Extra: all
29
+ Requires-Dist: asyncpg>=0.29; extra == 'all'
30
+ Requires-Dist: flask>=2.0; extra == 'all'
31
+ Requires-Dist: prometheus-client>=0.19; extra == 'all'
32
+ Requires-Dist: redis>=5.0; extra == 'all'
33
+ Provides-Extra: dev
34
+ Requires-Dist: black>=23.0; extra == 'dev'
35
+ Requires-Dist: build>=1.0; extra == 'dev'
36
+ Requires-Dist: flask>=2.0; extra == 'dev'
37
+ Requires-Dist: httpx>=0.25; extra == 'dev'
38
+ Requires-Dist: mypy>=1.0; extra == 'dev'
39
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
40
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
41
+ Requires-Dist: pytest>=7.0; extra == 'dev'
42
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
43
+ Requires-Dist: twine>=5.0; extra == 'dev'
44
+ Provides-Extra: flask
45
+ Requires-Dist: flask>=2.0; extra == 'flask'
46
+ Provides-Extra: postgres
47
+ Requires-Dist: asyncpg>=0.29; extra == 'postgres'
48
+ Provides-Extra: prometheus
49
+ Requires-Dist: prometheus-client>=0.19; extra == 'prometheus'
50
+ Provides-Extra: redis
51
+ Requires-Dist: redis>=5.0; extra == 'redis'
52
+ Description-Content-Type: text/markdown
53
+
54
+ # fluxlimit
55
+
56
+ **Distributed rate limiting with fairness for Python.**
57
+
58
+ Production-ready rate limiting supporting token bucket, leaky bucket, fixed window, and sliding window algorithms — with strict distributed fairness via Redis Lua scripts or PostgreSQL advisory locks.
59
+
60
+ [![PyPI version](https://img.shields.io/badge/pypi-v0.1.0-blue.svg)](https://pypi.org/project/fluxlimit/)
61
+ [![Python](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12%20|%203.13-blue.svg)](https://pypi.org/project/fluxlimit/)
62
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
63
+
64
+ ## Why fluxlimit?
65
+
66
+ | Feature | slowapi | fastapi-limiter | **fluxlimit** |
67
+ |---------|---------|-----------------|---------------|
68
+ | Token bucket | ❌ | ❌ | ✅ |
69
+ | Leaky bucket | ❌ | ❌ | ✅ |
70
+ | Sliding window | ❌ | ❌ | ✅ |
71
+ | Distributed fairness | ⚠️ Best-effort | ⚠️ Best-effort | ✅ **Strict** |
72
+ | Cost-based limiting | ❌ | ❌ | ✅ |
73
+ | PostgreSQL backend | ❌ | ❌ | ✅ |
74
+ | Multiple rules per request | ❌ | ❌ | ✅ |
75
+
76
+ ## Quick Start
77
+
78
+ ```python
79
+ from fluxlimit import (
80
+ RateLimiter,
81
+ LimitRule,
82
+ TokenBucket,
83
+ TokenBucketConfig,
84
+ MemoryBackend,
85
+ )
86
+
87
+ limiter = RateLimiter(
88
+ backend=MemoryBackend(),
89
+ default_rule=LimitRule(
90
+ key="api",
91
+ algorithm=TokenBucket(),
92
+ config=TokenBucketConfig(key="api", rate=10, burst=20),
93
+ ),
94
+ )
95
+
96
+ async with limiter:
97
+ result = await limiter.check("user_123")
98
+ print(result.allowed, result.remaining)
99
+ ```
100
+
101
+
102
+ ## FastAPI Integration
103
+
104
+ FastAPI middleware is included in the core install:
105
+
106
+ ```bash
107
+ pip install fluxlimit
108
+ ```
109
+
110
+ ```python
111
+ from fastapi import FastAPI, Request
112
+ from fluxlimit import (
113
+ RateLimiter,
114
+ RateLimitMiddleware,
115
+ LimitRule,
116
+ TokenBucket,
117
+ TokenBucketConfig,
118
+ RedisBackend,
119
+ rate_limit,
120
+ )
121
+
122
+ app = FastAPI()
123
+
124
+ limiter = RateLimiter(
125
+ backend=RedisBackend("redis://localhost:6379"),
126
+ default_rule=LimitRule(
127
+ key="api",
128
+ algorithm=TokenBucket(),
129
+ config=TokenBucketConfig(key="api", rate=100, burst=150),
130
+ ),
131
+ )
132
+
133
+ app.add_middleware(
134
+ RateLimitMiddleware,
135
+ limiter=limiter,
136
+ skip_paths=["/health", "/health/*"],
137
+ )
138
+
139
+ @app.on_event("startup")
140
+ async def startup():
141
+ await limiter.connect()
142
+
143
+ @app.on_event("shutdown")
144
+ async def shutdown():
145
+ await limiter.disconnect()
146
+
147
+ @app.get("/items")
148
+ async def get_items():
149
+ return {"message": "Hello World"}
150
+ ```
151
+
152
+ ASGI middleware lives in `fluxlimit.middleware.asgi`. You can also import explicitly:
153
+
154
+ ```python
155
+ from fluxlimit.middleware.asgi import RateLimitMiddleware, rate_limit
156
+ ```
157
+
158
+ ## Flask Integration
159
+
160
+ Install the Flask extra:
161
+
162
+ ```bash
163
+ pip install fluxlimit[flask]
164
+ ```
165
+
166
+ ```python
167
+ from flask import Flask
168
+ from fluxlimit import (
169
+ RateLimiter,
170
+ LimitRule,
171
+ TokenBucket,
172
+ TokenBucketConfig,
173
+ MemoryBackend,
174
+ Fluxlimit,
175
+ )
176
+
177
+ app = Flask(__name__)
178
+
179
+ limiter = RateLimiter(
180
+ backend=MemoryBackend(),
181
+ default_rule=LimitRule(
182
+ key="api",
183
+ algorithm=TokenBucket(),
184
+ config=TokenBucketConfig(key="api", rate=10, burst=20),
185
+ ),
186
+ )
187
+
188
+ fluxlimit = Fluxlimit(app, limiter=limiter, skip_paths=["/health", "/health/*"])
189
+
190
+ @app.route("/")
191
+ @fluxlimit.limit()
192
+ def index():
193
+ return {"message": "Hello"}
194
+
195
+ @app.route("/health")
196
+ @fluxlimit.exempt
197
+ def health():
198
+ return {"status": "ok"}
199
+ ```
200
+
201
+ Flask integration lives in `fluxlimit.middleware.flask`.
202
+
203
+ ## Cost-Based Limiting
204
+ Different endpoints can consume different amounts of quota:
205
+
206
+ ```python
207
+ from fluxlimit import rate_limit
208
+
209
+ # Expensive endpoint costs 5 tokens
210
+ @app.post("/analyze")
211
+ @rate_limit(limiter, cost=5)
212
+ async def analyze(request: Request):
213
+ return {"result": "..."}
214
+ ```
215
+
216
+ ## Algorithms
217
+
218
+ ### Token Bucket
219
+ Allows controlled bursts while maintaining average rate. Best for APIs that need burst tolerance.
220
+
221
+ ```python
222
+ from fluxlimit import TokenBucket, TokenBucketConfig
223
+
224
+ TokenBucketConfig(key="api", rate=10, burst=20) # 10/sec sustained, 20 burst
225
+ ```
226
+ ### Leaky Bucket
227
+ Strict rate enforcement with no bursts. Best for protecting downstream services.
228
+
229
+ ```python
230
+ from fluxlimit import LeakyBucket, LeakyBucketConfig
231
+
232
+ LeakyBucketConfig(key="api", rate=10, capacity=100) # 10/sec, queue of 100
233
+ ```
234
+
235
+ ### Fixed Window
236
+ Simple counter in fixed time windows. Low memory, but allows bursts at boundaries.
237
+ ```python
238
+ from fluxlimit import FixedWindow, FixedWindowConfig
239
+
240
+ FixedWindowConfig(key="api", max_requests=100, window_seconds=60)
241
+ ```
242
+
243
+ ### Sliding Window
244
+ Smooth rate limiting with no boundary bursts. Higher accuracy, more storage.
245
+ ```python
246
+ from fluxlimit import SlidingWindow, SlidingWindowConfig
247
+
248
+ SlidingWindowConfig(key="api", max_requests=100, window_seconds=60)
249
+ ```
250
+ ## Backends
251
+
252
+ | Backend | Use Case | Distributed Fairness |
253
+ |---------|----------|----------------------|
254
+ | `MemoryBackend` | Single process, development | N/A |
255
+ | `RedisBackend` | Production, high throughput | ✅ Lua scripts |
256
+ | `PostgresBackend` | Teams using PostgreSQL | ✅ Advisory locks |
257
+
258
+ ## PostgreSQL Setup
259
+ ```sql
260
+ CREATE TABLE fluxlimit_rate_limits (
261
+ key TEXT PRIMARY KEY,
262
+ tokens FLOAT NOT NULL DEFAULT 0,
263
+ volume FLOAT NOT NULL DEFAULT 0,
264
+ count INTEGER NOT NULL DEFAULT 0,
265
+ window_start FLOAT NOT NULL DEFAULT 0,
266
+ last_update FLOAT NOT NULL DEFAULT 0,
267
+ algorithm VARCHAR(32) NOT NULL DEFAULT 'token_bucket'
268
+ );
269
+ ```
270
+ ## Installation
271
+ ```bash
272
+ # Core (in-memory backend + FastAPI middleware)
273
+ pip install fluxlimit
274
+
275
+ # With Redis support
276
+ pip install fluxlimit[redis]
277
+
278
+ # With PostgreSQL support
279
+ pip install fluxlimit[postgres]
280
+
281
+ # With Prometheus metrics
282
+ pip install fluxlimit[prometheus]
283
+
284
+ # With Flask extension
285
+ pip install fluxlimit[flask]
286
+
287
+ # Everything
288
+ pip install fluxlimit[all]
289
+
290
+ ```
291
+
292
+ ## Testing
293
+
294
+ Install development dependencies from the project root:
295
+
296
+ ```bash
297
+ pip install ".[dev]"
298
+ ```
299
+
300
+ Run the full test suite:
301
+
302
+ ```bash
303
+ pytest
304
+ ```
305
+
306
+ Run with verbose output or target a specific file:
307
+
308
+ ```bash
309
+ pytest -v
310
+ pytest tests/test_core.py
311
+ pytest tests/test_algorithms.py::TestTokenBucket::test_refills_over_time
312
+ ```
313
+
314
+ Generate a coverage report:
315
+
316
+ ```bash
317
+ pytest --cov=fluxlimit --cov-report=term-missing
318
+ ```
319
+
320
+ If you have not installed the package and only want to run tests against the source tree:
321
+
322
+ ```bash
323
+ PYTHONPATH=src pytest
324
+ ```
325
+
326
+ ### Test layout
327
+
328
+ | File | Covers |
329
+ |------|--------|
330
+ | `tests/test_core.py` | `RateLimiter` — allow/deny checks, `check_or_raise`, named rules |
331
+ | `tests/test_algorithms.py` | Token bucket burst/refill behavior, fixed window reset |
332
+ | `tests/test_fastapi.py` | ASGI middleware, `@rate_limit`, prefix skip, lazy imports |
333
+ | `tests/test_flux_flask.py` | Flask extension — decorators, exempt routes, skip paths |
334
+ | `tests/test_middleware_imports.py` | Lazy loading of middleware without eager framework imports |
335
+
336
+ Tests use `pytest-asyncio` with `asyncio_mode = auto` (configured in `pyproject.toml`). The in-memory backend is used by default, so no Redis or PostgreSQL instance is required.
337
+
338
+ ## License
339
+ MIT