fastapi-loopguard 0.3.1__tar.gz → 0.4.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.
- fastapi_loopguard-0.4.0/PKG-INFO +127 -0
- fastapi_loopguard-0.4.0/README.md +82 -0
- fastapi_loopguard-0.4.0/assets/error-page-screenshot-console.png +0 -0
- fastapi_loopguard-0.4.0/assets/error-page-screenshot-endpoint.png +0 -0
- fastapi_loopguard-0.4.0/assets/error-page-screenshot.png +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/pyproject.toml +1 -1
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/src/fastapi_loopguard/config.py +11 -0
- fastapi_loopguard-0.4.0/src/fastapi_loopguard/middleware.py +634 -0
- fastapi_loopguard-0.4.0/tests/test_enforcement_mode.py +511 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/tests/test_middleware.py +1 -0
- fastapi_loopguard-0.3.1/PKG-INFO +0 -154
- fastapi_loopguard-0.3.1/README.md +0 -109
- fastapi_loopguard-0.3.1/src/fastapi_loopguard/middleware.py +0 -236
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/.github/dependabot.yml +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/.github/workflows/ci.yml +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/.github/workflows/publish.yml +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/.gitignore +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/CLAUDE.md +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/LICENSE +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/assets/loopguard-logo.webp +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/examples/demo_app.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/examples/locustfile.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/examples/run_stress_test.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/examples/stress_app.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/src/fastapi_loopguard/__init__.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/src/fastapi_loopguard/context.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/src/fastapi_loopguard/logging.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/src/fastapi_loopguard/metrics.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/src/fastapi_loopguard/monitor.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/src/fastapi_loopguard/py.typed +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/src/fastapi_loopguard/pytest_plugin.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/tests/__init__.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/tests/test_config.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/tests/test_context.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/tests/test_cumulative_blocking.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/tests/test_logging.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/tests/test_metrics.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/tests/test_monitor.py +0 -0
- {fastapi_loopguard-0.3.1 → fastapi_loopguard-0.4.0}/tests/test_pytest_plugin.py +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-loopguard
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Detect event-loop blocking in FastAPI/Starlette with per-request attribution
|
|
5
|
+
Project-URL: Homepage, https://github.com/parhamdavari/fastapi-loopguard
|
|
6
|
+
Project-URL: Documentation, https://github.com/parhamdavari/fastapi-loopguard#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/parhamdavari/fastapi-loopguard
|
|
8
|
+
Project-URL: Issues, https://github.com/parhamdavari/fastapi-loopguard/issues
|
|
9
|
+
Author: Parham
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: asyncio,blocking,event-loop,fastapi,middleware,monitoring,performance,starlette
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Framework :: AsyncIO
|
|
15
|
+
Classifier: Framework :: FastAPI
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: System :: Monitoring
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Requires-Dist: starlette<1.0,>=0.37.0
|
|
26
|
+
Provides-Extra: all
|
|
27
|
+
Requires-Dist: prometheus-client>=0.19.0; extra == 'all'
|
|
28
|
+
Requires-Dist: structlog>=24.1.0; extra == 'all'
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: coverage>=7.4.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: fastapi>=0.110.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: httpx>=0.27.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
37
|
+
Provides-Extra: prometheus
|
|
38
|
+
Requires-Dist: prometheus-client>=0.19.0; extra == 'prometheus'
|
|
39
|
+
Provides-Extra: stress
|
|
40
|
+
Requires-Dist: locust>=2.20.0; extra == 'stress'
|
|
41
|
+
Requires-Dist: uvicorn>=0.27.0; extra == 'stress'
|
|
42
|
+
Provides-Extra: structlog
|
|
43
|
+
Requires-Dist: structlog>=24.1.0; extra == 'structlog'
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
|
|
46
|
+
<p align="center">
|
|
47
|
+
<img src="assets/loopguard-logo.webp" alt="LoopGuard" width="280" />
|
|
48
|
+
</p>
|
|
49
|
+
|
|
50
|
+
<p align="center">
|
|
51
|
+
<strong>Catch event-loop blocking in FastAPI with per-request attribution.</strong>
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
<p align="center">
|
|
55
|
+
<a href="https://badge.fury.io/py/fastapi-loopguard"><img src="https://badge.fury.io/py/fastapi-loopguard.svg" alt="PyPI version"></a>
|
|
56
|
+
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.12+-blue.svg" alt="Python 3.12+"></a>
|
|
57
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
58
|
+
</p>
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
When a request blocks your event loop (via `time.sleep()`, blocking I/O, or CPU work), LoopGuard detects it **and tells you which endpoint caused it**.
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install fastapi-loopguard
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Quick Start
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from fastapi import FastAPI
|
|
74
|
+
from fastapi_loopguard import LoopGuardMiddleware
|
|
75
|
+
|
|
76
|
+
app = FastAPI()
|
|
77
|
+
app.add_middleware(LoopGuardMiddleware)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Enforcement Modes
|
|
81
|
+
|
|
82
|
+
| Mode | Behavior | Use Case |
|
|
83
|
+
|------|----------|----------|
|
|
84
|
+
| `"warn"` | Console warnings + headers | **Default** |
|
|
85
|
+
| `"strict"` | HTTP 503 + error page | Development / CI |
|
|
86
|
+
| `"log"` | Silent logging | Production |
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from fastapi_loopguard import LoopGuardConfig
|
|
90
|
+
|
|
91
|
+
# Development: strict enforcement (503 on blocking)
|
|
92
|
+
config = LoopGuardConfig(dev_mode=True)
|
|
93
|
+
|
|
94
|
+
# Production: silent logging
|
|
95
|
+
config = LoopGuardConfig(enforcement_mode="log")
|
|
96
|
+
|
|
97
|
+
app.add_middleware(LoopGuardMiddleware, config=config)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## What You Get
|
|
101
|
+
|
|
102
|
+
### Strict Mode
|
|
103
|
+
Returns an educational 503 page that explains what went wrong and how to fix it:
|
|
104
|
+
|
|
105
|
+
<p align="center">
|
|
106
|
+
<img src="assets/error-page-screenshot.png" alt="Strict mode error page" width="600" />
|
|
107
|
+
</p>
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### Warn Mode
|
|
112
|
+
Adds diagnostic headers to every response for debugging:
|
|
113
|
+
|
|
114
|
+
<p align="center">
|
|
115
|
+
<img src="assets/error-page-screenshot-endpoint.png" alt="Warn mode headers" width="600" />
|
|
116
|
+
</p>
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
### Log Mode
|
|
121
|
+
Writes structured logs with full request attribution:
|
|
122
|
+
|
|
123
|
+
<p align="center">
|
|
124
|
+
<img src="assets/error-page-screenshot-console.png" alt="Console output" width="600" />
|
|
125
|
+
</p>
|
|
126
|
+
|
|
127
|
+
---
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/loopguard-logo.webp" alt="LoopGuard" width="280" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<strong>Catch event-loop blocking in FastAPI with per-request attribution.</strong>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://badge.fury.io/py/fastapi-loopguard"><img src="https://badge.fury.io/py/fastapi-loopguard.svg" alt="PyPI version"></a>
|
|
11
|
+
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.12+-blue.svg" alt="Python 3.12+"></a>
|
|
12
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
When a request blocks your event loop (via `time.sleep()`, blocking I/O, or CPU work), LoopGuard detects it **and tells you which endpoint caused it**.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install fastapi-loopguard
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from fastapi import FastAPI
|
|
29
|
+
from fastapi_loopguard import LoopGuardMiddleware
|
|
30
|
+
|
|
31
|
+
app = FastAPI()
|
|
32
|
+
app.add_middleware(LoopGuardMiddleware)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Enforcement Modes
|
|
36
|
+
|
|
37
|
+
| Mode | Behavior | Use Case |
|
|
38
|
+
|------|----------|----------|
|
|
39
|
+
| `"warn"` | Console warnings + headers | **Default** |
|
|
40
|
+
| `"strict"` | HTTP 503 + error page | Development / CI |
|
|
41
|
+
| `"log"` | Silent logging | Production |
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from fastapi_loopguard import LoopGuardConfig
|
|
45
|
+
|
|
46
|
+
# Development: strict enforcement (503 on blocking)
|
|
47
|
+
config = LoopGuardConfig(dev_mode=True)
|
|
48
|
+
|
|
49
|
+
# Production: silent logging
|
|
50
|
+
config = LoopGuardConfig(enforcement_mode="log")
|
|
51
|
+
|
|
52
|
+
app.add_middleware(LoopGuardMiddleware, config=config)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## What You Get
|
|
56
|
+
|
|
57
|
+
### Strict Mode
|
|
58
|
+
Returns an educational 503 page that explains what went wrong and how to fix it:
|
|
59
|
+
|
|
60
|
+
<p align="center">
|
|
61
|
+
<img src="assets/error-page-screenshot.png" alt="Strict mode error page" width="600" />
|
|
62
|
+
</p>
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### Warn Mode
|
|
67
|
+
Adds diagnostic headers to every response for debugging:
|
|
68
|
+
|
|
69
|
+
<p align="center">
|
|
70
|
+
<img src="assets/error-page-screenshot-endpoint.png" alt="Warn mode headers" width="600" />
|
|
71
|
+
</p>
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### Log Mode
|
|
76
|
+
Writes structured logs with full request attribution:
|
|
77
|
+
|
|
78
|
+
<p align="center">
|
|
79
|
+
<img src="assets/error-page-screenshot-console.png" alt="Console output" width="600" />
|
|
80
|
+
</p>
|
|
81
|
+
|
|
82
|
+
---
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -34,6 +34,10 @@ class LoopGuardConfig:
|
|
|
34
34
|
log_blocking_events: bool = True
|
|
35
35
|
prometheus_enabled: bool = False
|
|
36
36
|
|
|
37
|
+
# Enforcement mode: how aggressively to respond to blocking
|
|
38
|
+
# "log" = just log (production), "warn" = loud warnings, "strict" = 503 errors
|
|
39
|
+
enforcement_mode: str = "warn"
|
|
40
|
+
|
|
37
41
|
# Adaptive threshold settings
|
|
38
42
|
adaptive_threshold: bool = False
|
|
39
43
|
adaptive_window_size: int = 1000
|
|
@@ -86,3 +90,10 @@ class LoopGuardConfig:
|
|
|
86
90
|
raise ValueError(
|
|
87
91
|
"cumulative_window_ms cannot be less than monitor_interval_ms"
|
|
88
92
|
)
|
|
93
|
+
# Enforcement mode validation
|
|
94
|
+
valid_enforcement_modes = {"log", "warn", "strict"}
|
|
95
|
+
if self.enforcement_mode not in valid_enforcement_modes:
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"enforcement_mode must be one of {valid_enforcement_modes}, "
|
|
98
|
+
f"got '{self.enforcement_mode}'"
|
|
99
|
+
)
|