fastapi-loopguard 0.4.0__tar.gz → 0.4.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.
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/PKG-INFO +5 -1
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/README.md +4 -0
- fastapi_loopguard-0.4.1/docs/CONFIGURATION.md +129 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/pyproject.toml +1 -1
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/src/fastapi_loopguard/config.py +1 -1
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/src/fastapi_loopguard/middleware.py +4 -4
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/tests/test_cumulative_blocking.py +15 -3
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/.github/dependabot.yml +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/.github/workflows/ci.yml +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/.github/workflows/publish.yml +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/.gitignore +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/CLAUDE.md +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/LICENSE +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/assets/error-page-screenshot-console.png +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/assets/error-page-screenshot-endpoint.png +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/assets/error-page-screenshot.png +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/assets/loopguard-logo.webp +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/examples/demo_app.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/examples/locustfile.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/examples/run_stress_test.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/examples/stress_app.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/src/fastapi_loopguard/__init__.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/src/fastapi_loopguard/context.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/src/fastapi_loopguard/logging.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/src/fastapi_loopguard/metrics.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/src/fastapi_loopguard/monitor.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/src/fastapi_loopguard/py.typed +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/src/fastapi_loopguard/pytest_plugin.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/tests/__init__.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/tests/test_config.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/tests/test_context.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/tests/test_enforcement_mode.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/tests/test_logging.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/tests/test_metrics.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/tests/test_middleware.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/tests/test_monitor.py +0 -0
- {fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/tests/test_pytest_plugin.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-loopguard
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Detect event-loop blocking in FastAPI/Starlette with per-request attribution
|
|
5
5
|
Project-URL: Homepage, https://github.com/parhamdavari/fastapi-loopguard
|
|
6
6
|
Project-URL: Documentation, https://github.com/parhamdavari/fastapi-loopguard#readme
|
|
@@ -125,3 +125,7 @@ Writes structured logs with full request attribution:
|
|
|
125
125
|
</p>
|
|
126
126
|
|
|
127
127
|
---
|
|
128
|
+
|
|
129
|
+
<p align="center">
|
|
130
|
+
<a href="docs/CONFIGURATION.md"><strong>Full Configuration Reference</strong></a>
|
|
131
|
+
</p>
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Configuration Reference
|
|
2
|
+
|
|
3
|
+
All configuration options for `LoopGuardConfig`.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from fastapi_loopguard import LoopGuardConfig
|
|
9
|
+
|
|
10
|
+
config = LoopGuardConfig(
|
|
11
|
+
# Enforcement
|
|
12
|
+
enforcement_mode="warn", # "log" | "warn" | "strict"
|
|
13
|
+
dev_mode=False, # Auto-escalates to strict when True
|
|
14
|
+
|
|
15
|
+
# Detection tuning
|
|
16
|
+
monitor_interval_ms=10.0, # How often to check (ms)
|
|
17
|
+
threshold_multiplier=5.0, # Blocking = lag > baseline × multiplier
|
|
18
|
+
fallback_threshold_ms=50.0, # Threshold if calibration fails
|
|
19
|
+
|
|
20
|
+
# Cumulative detection (enabled by default)
|
|
21
|
+
cumulative_blocking_enabled=True,
|
|
22
|
+
cumulative_blocking_threshold_ms=200.0,
|
|
23
|
+
cumulative_window_ms=1000.0,
|
|
24
|
+
|
|
25
|
+
# Adaptive threshold (disabled by default)
|
|
26
|
+
adaptive_threshold=False,
|
|
27
|
+
|
|
28
|
+
# Integrations
|
|
29
|
+
prometheus_enabled=False,
|
|
30
|
+
log_blocking_events=True,
|
|
31
|
+
)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Core Settings
|
|
37
|
+
|
|
38
|
+
| Option | Type | Default | Description |
|
|
39
|
+
|--------|------|---------|-------------|
|
|
40
|
+
| `enabled` | bool | `True` | Master switch. Set `False` to disable entirely. |
|
|
41
|
+
| `enforcement_mode` | str | `"warn"` | How to respond: `"log"`, `"warn"`, or `"strict"` |
|
|
42
|
+
| `dev_mode` | bool | `False` | Enables response headers. Auto-escalates to strict mode. |
|
|
43
|
+
| `log_blocking_events` | bool | `True` | Log blocking events to console |
|
|
44
|
+
| `exclude_paths` | frozenset | `{"/health", ...}` | Paths to skip monitoring |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Detection Tuning
|
|
49
|
+
|
|
50
|
+
| Option | Type | Default | Description |
|
|
51
|
+
|--------|------|---------|-------------|
|
|
52
|
+
| `monitor_interval_ms` | float | `10.0` | Sentinel check frequency (ms) |
|
|
53
|
+
| `threshold_multiplier` | float | `5.0` | Blocking detected when lag > baseline × this |
|
|
54
|
+
| `calibration_iterations` | int | `100` | Samples during startup calibration |
|
|
55
|
+
| `fallback_threshold_ms` | float | `50.0` | Used if calibration is unreliable |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Cumulative Blocking Detection
|
|
60
|
+
|
|
61
|
+
Catches "death by a thousand cuts" - many small blocks that add up.
|
|
62
|
+
|
|
63
|
+
| Option | Type | Default | Description |
|
|
64
|
+
|--------|------|---------|-------------|
|
|
65
|
+
| `cumulative_blocking_enabled` | bool | `True` | Enable cumulative detection |
|
|
66
|
+
| `cumulative_blocking_threshold_ms` | float | `200.0` | Alert if total blocking exceeds this... |
|
|
67
|
+
| `cumulative_window_ms` | float | `1000.0` | ...within this time window (ms) |
|
|
68
|
+
|
|
69
|
+
**Example:** With defaults, alerts if blocking totals >200ms within any 1-second window.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Adaptive Threshold
|
|
74
|
+
|
|
75
|
+
Dynamically adjusts threshold based on observed latency. Useful for high-concurrency environments.
|
|
76
|
+
|
|
77
|
+
| Option | Type | Default | Description |
|
|
78
|
+
|--------|------|---------|-------------|
|
|
79
|
+
| `adaptive_threshold` | bool | `False` | Enable adaptive mode |
|
|
80
|
+
| `adaptive_window_size` | int | `1000` | Samples in sliding window |
|
|
81
|
+
| `adaptive_percentile` | float | `0.95` | Percentile for baseline (0.5-0.99) |
|
|
82
|
+
| `adaptive_min_samples` | int | `100` | Min samples before activation |
|
|
83
|
+
| `adaptive_update_interval_ms` | float | `1000.0` | Recalculation frequency (ms) |
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Integrations
|
|
88
|
+
|
|
89
|
+
| Option | Type | Default | Description |
|
|
90
|
+
|--------|------|---------|-------------|
|
|
91
|
+
| `prometheus_enabled` | bool | `False` | Expose Prometheus metrics |
|
|
92
|
+
|
|
93
|
+
When enabled, exposes:
|
|
94
|
+
- `loopguard_blocking_events_total` - Counter of blocking events
|
|
95
|
+
- `loopguard_blocking_duration_ms` - Histogram of blocking durations
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Common Configurations
|
|
100
|
+
|
|
101
|
+
### Development (strict enforcement)
|
|
102
|
+
```python
|
|
103
|
+
config = LoopGuardConfig(dev_mode=True)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Production (silent monitoring)
|
|
107
|
+
```python
|
|
108
|
+
config = LoopGuardConfig(
|
|
109
|
+
enforcement_mode="log",
|
|
110
|
+
prometheus_enabled=True,
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### High-concurrency (adaptive threshold)
|
|
115
|
+
```python
|
|
116
|
+
config = LoopGuardConfig(
|
|
117
|
+
adaptive_threshold=True,
|
|
118
|
+
adaptive_percentile=0.99,
|
|
119
|
+
)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Sensitive detection (lower threshold)
|
|
123
|
+
```python
|
|
124
|
+
config = LoopGuardConfig(
|
|
125
|
+
monitor_interval_ms=5.0,
|
|
126
|
+
threshold_multiplier=3.0,
|
|
127
|
+
fallback_threshold_ms=30.0,
|
|
128
|
+
)
|
|
129
|
+
```
|
|
@@ -46,7 +46,7 @@ class LoopGuardConfig:
|
|
|
46
46
|
adaptive_update_interval_ms: float = 1000.0
|
|
47
47
|
|
|
48
48
|
# Cumulative blocking detection
|
|
49
|
-
cumulative_blocking_enabled: bool =
|
|
49
|
+
cumulative_blocking_enabled: bool = True
|
|
50
50
|
cumulative_blocking_threshold_ms: float = 200.0
|
|
51
51
|
cumulative_window_ms: float = 1000.0
|
|
52
52
|
|
|
@@ -264,10 +264,10 @@ class LoopGuardMiddleware:
|
|
|
264
264
|
def _log_console_warning(self, ctx: RequestContext) -> None:
|
|
265
265
|
"""Print attention-grabbing console warning to stderr."""
|
|
266
266
|
warning = f"""
|
|
267
|
-
{
|
|
268
|
-
{
|
|
267
|
+
{"=" * 72}
|
|
268
|
+
{"!" * 72}
|
|
269
269
|
LOOPGUARD: Event Loop Blocked!
|
|
270
|
-
{
|
|
270
|
+
{"!" * 72}
|
|
271
271
|
|
|
272
272
|
Request: {ctx.method} {ctx.path}
|
|
273
273
|
Request ID: {ctx.request_id}
|
|
@@ -283,7 +283,7 @@ class LoopGuardMiddleware:
|
|
|
283
283
|
subprocess.run(...) -> await asyncio.create_subprocess_exec(...)
|
|
284
284
|
|
|
285
285
|
Docs: https://fastapi.tiangolo.com/async/
|
|
286
|
-
{
|
|
286
|
+
{"=" * 72}
|
|
287
287
|
"""
|
|
288
288
|
print(warning, file=sys.stderr)
|
|
289
289
|
|
|
@@ -66,13 +66,25 @@ async def test_cumulative_blocking_detection() -> None:
|
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
@pytest.mark.asyncio
|
|
69
|
-
async def
|
|
70
|
-
# Verify
|
|
69
|
+
async def test_cumulative_blocking_enabled_by_default() -> None:
|
|
70
|
+
# Verify cumulative blocking is enabled by default
|
|
71
71
|
config = LoopGuardConfig(
|
|
72
72
|
enabled=True,
|
|
73
73
|
monitor_interval_ms=10.0,
|
|
74
74
|
fallback_threshold_ms=50.0,
|
|
75
|
-
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
assert config.cumulative_blocking_enabled is True
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@pytest.mark.asyncio
|
|
81
|
+
async def test_cumulative_blocking_can_be_disabled() -> None:
|
|
82
|
+
# Verify it doesn't trigger when explicitly disabled
|
|
83
|
+
config = LoopGuardConfig(
|
|
84
|
+
enabled=True,
|
|
85
|
+
monitor_interval_ms=10.0,
|
|
86
|
+
fallback_threshold_ms=50.0,
|
|
87
|
+
cumulative_blocking_enabled=False,
|
|
76
88
|
)
|
|
77
89
|
|
|
78
90
|
assert config.cumulative_blocking_enabled is False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/assets/error-page-screenshot-console.png
RENAMED
|
File without changes
|
{fastapi_loopguard-0.4.0 → fastapi_loopguard-0.4.1}/assets/error-page-screenshot-endpoint.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|