bubble-analysis 0.2.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.
Potentially problematic release.
This version of bubble-analysis might be problematic. Click here for more details.
- bubble_analysis-0.2.0/LICENSE +21 -0
- bubble_analysis-0.2.0/PKG-INFO +264 -0
- bubble_analysis-0.2.0/README.md +229 -0
- bubble_analysis-0.2.0/bubble/__init__.py +3 -0
- bubble_analysis-0.2.0/bubble/cache.py +207 -0
- bubble_analysis-0.2.0/bubble/cli.py +470 -0
- bubble_analysis-0.2.0/bubble/config.py +52 -0
- bubble_analysis-0.2.0/bubble/detectors.py +90 -0
- bubble_analysis-0.2.0/bubble/enums.py +65 -0
- bubble_analysis-0.2.0/bubble/extractor.py +829 -0
- bubble_analysis-0.2.0/bubble/formatters.py +887 -0
- bubble_analysis-0.2.0/bubble/integrations/__init__.py +92 -0
- bubble_analysis-0.2.0/bubble/integrations/base.py +98 -0
- bubble_analysis-0.2.0/bubble/integrations/cli_scripts/__init__.py +49 -0
- bubble_analysis-0.2.0/bubble/integrations/cli_scripts/cli.py +108 -0
- bubble_analysis-0.2.0/bubble/integrations/cli_scripts/detector.py +149 -0
- bubble_analysis-0.2.0/bubble/integrations/django/__init__.py +63 -0
- bubble_analysis-0.2.0/bubble/integrations/django/cli.py +111 -0
- bubble_analysis-0.2.0/bubble/integrations/django/detector.py +331 -0
- bubble_analysis-0.2.0/bubble/integrations/django/semantics.py +40 -0
- bubble_analysis-0.2.0/bubble/integrations/fastapi/__init__.py +57 -0
- bubble_analysis-0.2.0/bubble/integrations/fastapi/cli.py +110 -0
- bubble_analysis-0.2.0/bubble/integrations/fastapi/detector.py +176 -0
- bubble_analysis-0.2.0/bubble/integrations/fastapi/semantics.py +14 -0
- bubble_analysis-0.2.0/bubble/integrations/flask/__init__.py +57 -0
- bubble_analysis-0.2.0/bubble/integrations/flask/cli.py +110 -0
- bubble_analysis-0.2.0/bubble/integrations/flask/detector.py +191 -0
- bubble_analysis-0.2.0/bubble/integrations/flask/semantics.py +19 -0
- bubble_analysis-0.2.0/bubble/integrations/formatters.py +268 -0
- bubble_analysis-0.2.0/bubble/integrations/generic/__init__.py +13 -0
- bubble_analysis-0.2.0/bubble/integrations/generic/config.py +106 -0
- bubble_analysis-0.2.0/bubble/integrations/generic/detector.py +346 -0
- bubble_analysis-0.2.0/bubble/integrations/generic/frameworks.py +145 -0
- bubble_analysis-0.2.0/bubble/integrations/models.py +68 -0
- bubble_analysis-0.2.0/bubble/integrations/queries.py +481 -0
- bubble_analysis-0.2.0/bubble/loader.py +118 -0
- bubble_analysis-0.2.0/bubble/models.py +397 -0
- bubble_analysis-0.2.0/bubble/propagation.py +737 -0
- bubble_analysis-0.2.0/bubble/protocols.py +104 -0
- bubble_analysis-0.2.0/bubble/queries.py +627 -0
- bubble_analysis-0.2.0/bubble/results.py +211 -0
- bubble_analysis-0.2.0/bubble/stubs.py +89 -0
- bubble_analysis-0.2.0/bubble/timing.py +144 -0
- bubble_analysis-0.2.0/bubble_analysis.egg-info/PKG-INFO +264 -0
- bubble_analysis-0.2.0/bubble_analysis.egg-info/SOURCES.txt +67 -0
- bubble_analysis-0.2.0/bubble_analysis.egg-info/dependency_links.txt +1 -0
- bubble_analysis-0.2.0/bubble_analysis.egg-info/entry_points.txt +2 -0
- bubble_analysis-0.2.0/bubble_analysis.egg-info/requires.txt +10 -0
- bubble_analysis-0.2.0/bubble_analysis.egg-info/top_level.txt +1 -0
- bubble_analysis-0.2.0/pyproject.toml +100 -0
- bubble_analysis-0.2.0/setup.cfg +4 -0
- bubble_analysis-0.2.0/tests/test_callers.py +32 -0
- bubble_analysis-0.2.0/tests/test_catches.py +28 -0
- bubble_analysis-0.2.0/tests/test_cli_smoke.py +87 -0
- bubble_analysis-0.2.0/tests/test_drf_dispatch.py +167 -0
- bubble_analysis-0.2.0/tests/test_entrypoints.py +54 -0
- bubble_analysis-0.2.0/tests/test_escapes.py +39 -0
- bubble_analysis-0.2.0/tests/test_exceptions.py +53 -0
- bubble_analysis-0.2.0/tests/test_flask_appbuilder.py +35 -0
- bubble_analysis-0.2.0/tests/test_generic_detector.py +193 -0
- bubble_analysis-0.2.0/tests/test_generic_handler.py +50 -0
- bubble_analysis-0.2.0/tests/test_hierarchy.py +127 -0
- bubble_analysis-0.2.0/tests/test_init.py +84 -0
- bubble_analysis-0.2.0/tests/test_propagation.py +64 -0
- bubble_analysis-0.2.0/tests/test_raises.py +48 -0
- bubble_analysis-0.2.0/tests/test_resolution.py +283 -0
- bubble_analysis-0.2.0/tests/test_routes_to.py +111 -0
- bubble_analysis-0.2.0/tests/test_stats.py +28 -0
- bubble_analysis-0.2.0/tests/test_trust_features.py +248 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
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,264 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bubble-analysis
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Static analysis tool for tracing exception flow through Python codebases
|
|
5
|
+
Author-email: Ian McLaughlin <ianm199@github.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ianm199/flow
|
|
8
|
+
Project-URL: Repository, https://github.com/ianm199/flow
|
|
9
|
+
Project-URL: Issues, https://github.com/ianm199/flow/issues
|
|
10
|
+
Keywords: static-analysis,exceptions,python,code-analysis,flask,fastapi
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
|
+
Classifier: Topic :: Software Development :: Testing
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: libcst>=1.1.0
|
|
26
|
+
Requires-Dist: typer>=0.12.0
|
|
27
|
+
Requires-Dist: rich>=13.0.0
|
|
28
|
+
Requires-Dist: msgpack>=1.0.0
|
|
29
|
+
Requires-Dist: pyyaml>=6.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# flow
|
|
37
|
+
|
|
38
|
+
Static analysis tool for tracing exception flow through Python codebases.
|
|
39
|
+
|
|
40
|
+
**What can escape from my API endpoints?** Flow answers this by parsing your code, building a call graph, and computing which exceptions propagate to each entrypoint.
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install bubble-analysis
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Check Flask routes for uncaught exceptions
|
|
50
|
+
bubble flask audit -d /path/to/project
|
|
51
|
+
|
|
52
|
+
# Check FastAPI routes
|
|
53
|
+
bubble fastapi audit -d /path/to/project
|
|
54
|
+
|
|
55
|
+
# Deep dive into one function
|
|
56
|
+
bubble escapes create_user -d /path/to/project
|
|
57
|
+
|
|
58
|
+
# Visualize the call tree
|
|
59
|
+
bubble trace create_user -d /path/to/project
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## What It Does
|
|
63
|
+
|
|
64
|
+
Flow finds your HTTP routes and CLI scripts, traces the call graph, and reports which exceptions can escape:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
$ flow flask audit
|
|
68
|
+
|
|
69
|
+
Scanning 23 flask entrypoints...
|
|
70
|
+
|
|
71
|
+
3 entrypoints have uncaught exceptions:
|
|
72
|
+
|
|
73
|
+
POST /users/import
|
|
74
|
+
└─ FileNotFoundError (importers.py:45)
|
|
75
|
+
└─ ValidationError (validators.py:12)
|
|
76
|
+
|
|
77
|
+
GET /reports/{id}
|
|
78
|
+
└─ PermissionError (auth.py:89)
|
|
79
|
+
|
|
80
|
+
20 entrypoints fully covered by exception handlers
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For a specific endpoint, see the full picture:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
$ flow escapes create_user
|
|
87
|
+
|
|
88
|
+
Exceptions that can escape from POST /users:
|
|
89
|
+
|
|
90
|
+
FRAMEWORK-HANDLED (converted to HTTP response):
|
|
91
|
+
HTTPException
|
|
92
|
+
└─ becomes: HTTP 404
|
|
93
|
+
└─ raised in: routes/users.py:45 (get_user) [high confidence]
|
|
94
|
+
|
|
95
|
+
CAUGHT BY GLOBAL HANDLER:
|
|
96
|
+
ValidationError (@errorhandler(AppError))
|
|
97
|
+
└─ raised in: validators.py:27 (validate_input) [high confidence]
|
|
98
|
+
|
|
99
|
+
UNCAUGHT (will propagate to caller):
|
|
100
|
+
ConnectionError
|
|
101
|
+
└─ raised in: db/client.py:45 (execute) [medium confidence]
|
|
102
|
+
└─ call path: create_user → save_user → db.execute
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Visualize as a tree:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
$ flow trace create_user
|
|
109
|
+
|
|
110
|
+
POST /users → escapes: ValidationError, ConnectionError
|
|
111
|
+
├── validate_input() → ValidationError
|
|
112
|
+
│ └── raises ValidationError (validators.py:27)
|
|
113
|
+
└── save_user() → ConnectionError
|
|
114
|
+
└── db.execute() → ConnectionError
|
|
115
|
+
└── raises ConnectionError (db/client.py:45)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Features
|
|
119
|
+
|
|
120
|
+
- **Entrypoint detection**: Flask routes, FastAPI routes, CLI scripts (`if __name__ == "__main__"`)
|
|
121
|
+
- **Global handler awareness**: Understands `@errorhandler`, `add_exception_handler`
|
|
122
|
+
- **Exception hierarchy**: Knows that catching `AppError` also catches `ValidationError` if it's a subclass
|
|
123
|
+
- **Polymorphism**: Expands abstract method calls to all concrete implementations
|
|
124
|
+
- **Framework-handled exceptions**: Detects HTTPException, ValidationError → HTTP responses
|
|
125
|
+
- **Confidence levels**: Shows high/medium/low confidence based on resolution quality
|
|
126
|
+
- **Resolution modes**: `--strict` for precision, `--aggressive` for recall
|
|
127
|
+
- **Exception stubs**: Declare what external libraries can raise (requests, sqlalchemy, etc.)
|
|
128
|
+
- **JSON output**: All commands support `-f json` for CI/automation
|
|
129
|
+
- **Caching**: SQLite-based caching for fast repeated analysis
|
|
130
|
+
|
|
131
|
+
## Commands
|
|
132
|
+
|
|
133
|
+
### Core Commands (framework-agnostic)
|
|
134
|
+
|
|
135
|
+
| Command | Description |
|
|
136
|
+
|---------|-------------|
|
|
137
|
+
| `bubble raises <exception>` | Find all places an exception is raised |
|
|
138
|
+
| `bubble escapes <function>` | Show what can escape from a specific function |
|
|
139
|
+
| `bubble callers <function>` | Find all callers of a function |
|
|
140
|
+
| `bubble catches <exception>` | Find all places an exception is caught |
|
|
141
|
+
| `bubble trace <function>` | Visualize exception flow as a call tree |
|
|
142
|
+
| `bubble exceptions` | Show the exception class hierarchy |
|
|
143
|
+
| `bubble subclasses <class>` | Show class inheritance tree |
|
|
144
|
+
| `bubble stubs <action>` | Manage exception stubs (`list`, `init`, `validate`) |
|
|
145
|
+
| `bubble stats` | Show codebase statistics |
|
|
146
|
+
|
|
147
|
+
### Framework-Specific Commands
|
|
148
|
+
|
|
149
|
+
| Command | Description |
|
|
150
|
+
|---------|-------------|
|
|
151
|
+
| `bubble flask audit` | Check Flask routes for escaping exceptions |
|
|
152
|
+
| `bubble flask entrypoints` | List Flask HTTP routes |
|
|
153
|
+
| `bubble flask routes-to <exc>` | Which Flask routes can trigger this exception? |
|
|
154
|
+
| `bubble fastapi audit` | Check FastAPI routes for escaping exceptions |
|
|
155
|
+
| `bubble fastapi entrypoints` | List FastAPI HTTP routes |
|
|
156
|
+
| `bubble fastapi routes-to <exc>` | Which FastAPI routes can trigger this exception? |
|
|
157
|
+
| `bubble cli audit` | Check CLI scripts for escaping exceptions |
|
|
158
|
+
| `bubble cli entrypoints` | List CLI scripts |
|
|
159
|
+
| `bubble cli scripts-to <exc>` | Which CLI scripts can trigger this exception? |
|
|
160
|
+
|
|
161
|
+
All commands accept:
|
|
162
|
+
- `-d, --directory`: Directory to analyze (default: current)
|
|
163
|
+
- `-f, --format`: Output format (`text` or `json`)
|
|
164
|
+
- `--no-cache`: Disable caching
|
|
165
|
+
|
|
166
|
+
The `escapes` command accepts additional flags:
|
|
167
|
+
- `--strict`: High precision mode - only includes precisely resolved calls
|
|
168
|
+
- `--aggressive`: High recall mode - includes fuzzy matches
|
|
169
|
+
|
|
170
|
+
## Supported Frameworks
|
|
171
|
+
|
|
172
|
+
**Detected automatically:**
|
|
173
|
+
- Flask (`@app.route`, `@blueprint.route`, `@app.errorhandler`)
|
|
174
|
+
- FastAPI (`@router.get/post/put/delete`, `add_exception_handler`)
|
|
175
|
+
- CLI scripts (`if __name__ == "__main__"`)
|
|
176
|
+
|
|
177
|
+
**Not yet supported:**
|
|
178
|
+
- Django
|
|
179
|
+
- Celery tasks
|
|
180
|
+
- Scheduled jobs (APScheduler, etc.)
|
|
181
|
+
|
|
182
|
+
Custom patterns can be added via `.flow/detectors/` (run `bubble init` to set up).
|
|
183
|
+
|
|
184
|
+
## Adding Custom Detectors
|
|
185
|
+
|
|
186
|
+
Flow is designed to be extended with AI coding agents. The detector interface is intentionally simple: implement a protocol that returns entrypoints and handlers from parsed code.
|
|
187
|
+
|
|
188
|
+
To add support for a new framework (Django, Celery, your internal RPC layer, etc.):
|
|
189
|
+
|
|
190
|
+
1. Run `bubble init` to create the `.flow/` directory structure
|
|
191
|
+
2. Point your AI agent at `flow/protocols.py` to see the `EntrypointDetector` interface
|
|
192
|
+
3. Ask it to implement a detector for your framework in `.flow/detectors/`
|
|
193
|
+
|
|
194
|
+
Example prompt for an AI agent:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
Read flow/protocols.py and flow/detectors.py to understand how entrypoint
|
|
198
|
+
detection works. Then implement a detector for Django that finds:
|
|
199
|
+
- Views decorated with @api_view
|
|
200
|
+
- Class-based views inheriting from APIView
|
|
201
|
+
- URL patterns in urls.py
|
|
202
|
+
|
|
203
|
+
Put the implementation in .flow/detectors/django.py
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The detector just needs to implement:
|
|
207
|
+
- `detect_entrypoints(functions, classes, ...)` → list of `Entrypoint`
|
|
208
|
+
- `detect_global_handlers(...)` → list of `GlobalHandler`
|
|
209
|
+
|
|
210
|
+
Flow will automatically load any `.py` files in `.flow/detectors/` and use them alongside the built-in Flask/FastAPI detectors.
|
|
211
|
+
|
|
212
|
+
## Configuration
|
|
213
|
+
|
|
214
|
+
Flow can be configured via `.flow/config.yaml`:
|
|
215
|
+
|
|
216
|
+
```yaml
|
|
217
|
+
resolution_mode: default # "strict", "default", or "aggressive"
|
|
218
|
+
exclude:
|
|
219
|
+
- vendor
|
|
220
|
+
- migrations
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Exception Stubs
|
|
224
|
+
|
|
225
|
+
Flow includes built-in stubs for common libraries (requests, sqlalchemy, httpx, redis, boto3). These declare what exceptions external library functions can raise.
|
|
226
|
+
|
|
227
|
+
Add custom stubs in `.flow/stubs/`:
|
|
228
|
+
|
|
229
|
+
```yaml
|
|
230
|
+
# .flow/stubs/mylib.yaml
|
|
231
|
+
mylib:
|
|
232
|
+
do_thing:
|
|
233
|
+
- MyLibError
|
|
234
|
+
- TimeoutError
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Manage stubs with `bubble stubs list` and `bubble stubs validate`.
|
|
238
|
+
|
|
239
|
+
## How It Works
|
|
240
|
+
|
|
241
|
+
1. **Parse**: LibCST parses all Python files
|
|
242
|
+
2. **Extract**: Find functions, classes, raise/catch sites, calls, entrypoints
|
|
243
|
+
3. **Build call graph**: Track who calls whom, resolve method calls
|
|
244
|
+
4. **Propagate**: Fixed-point iteration computes which exceptions escape each function
|
|
245
|
+
5. **Report**: For each entrypoint, show caught vs uncaught exceptions
|
|
246
|
+
|
|
247
|
+
## Limitations
|
|
248
|
+
|
|
249
|
+
- **Over-approximation**: May report more exceptions than actually possible (e.g., all implementations of an abstract method)
|
|
250
|
+
- **Under-approximation**: Dynamic dispatch, `eval()`, and external libraries can't be fully traced
|
|
251
|
+
- **No runtime info**: Analysis is purely static
|
|
252
|
+
|
|
253
|
+
## Development
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
git clone https://github.com/ianm199/flow
|
|
257
|
+
cd flow
|
|
258
|
+
pip install -e ".[dev]"
|
|
259
|
+
pytest
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
MIT
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# flow
|
|
2
|
+
|
|
3
|
+
Static analysis tool for tracing exception flow through Python codebases.
|
|
4
|
+
|
|
5
|
+
**What can escape from my API endpoints?** Flow answers this by parsing your code, building a call graph, and computing which exceptions propagate to each entrypoint.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install bubble-analysis
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Check Flask routes for uncaught exceptions
|
|
15
|
+
bubble flask audit -d /path/to/project
|
|
16
|
+
|
|
17
|
+
# Check FastAPI routes
|
|
18
|
+
bubble fastapi audit -d /path/to/project
|
|
19
|
+
|
|
20
|
+
# Deep dive into one function
|
|
21
|
+
bubble escapes create_user -d /path/to/project
|
|
22
|
+
|
|
23
|
+
# Visualize the call tree
|
|
24
|
+
bubble trace create_user -d /path/to/project
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## What It Does
|
|
28
|
+
|
|
29
|
+
Flow finds your HTTP routes and CLI scripts, traces the call graph, and reports which exceptions can escape:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
$ flow flask audit
|
|
33
|
+
|
|
34
|
+
Scanning 23 flask entrypoints...
|
|
35
|
+
|
|
36
|
+
3 entrypoints have uncaught exceptions:
|
|
37
|
+
|
|
38
|
+
POST /users/import
|
|
39
|
+
└─ FileNotFoundError (importers.py:45)
|
|
40
|
+
└─ ValidationError (validators.py:12)
|
|
41
|
+
|
|
42
|
+
GET /reports/{id}
|
|
43
|
+
└─ PermissionError (auth.py:89)
|
|
44
|
+
|
|
45
|
+
20 entrypoints fully covered by exception handlers
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For a specific endpoint, see the full picture:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
$ flow escapes create_user
|
|
52
|
+
|
|
53
|
+
Exceptions that can escape from POST /users:
|
|
54
|
+
|
|
55
|
+
FRAMEWORK-HANDLED (converted to HTTP response):
|
|
56
|
+
HTTPException
|
|
57
|
+
└─ becomes: HTTP 404
|
|
58
|
+
└─ raised in: routes/users.py:45 (get_user) [high confidence]
|
|
59
|
+
|
|
60
|
+
CAUGHT BY GLOBAL HANDLER:
|
|
61
|
+
ValidationError (@errorhandler(AppError))
|
|
62
|
+
└─ raised in: validators.py:27 (validate_input) [high confidence]
|
|
63
|
+
|
|
64
|
+
UNCAUGHT (will propagate to caller):
|
|
65
|
+
ConnectionError
|
|
66
|
+
└─ raised in: db/client.py:45 (execute) [medium confidence]
|
|
67
|
+
└─ call path: create_user → save_user → db.execute
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Visualize as a tree:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
$ flow trace create_user
|
|
74
|
+
|
|
75
|
+
POST /users → escapes: ValidationError, ConnectionError
|
|
76
|
+
├── validate_input() → ValidationError
|
|
77
|
+
│ └── raises ValidationError (validators.py:27)
|
|
78
|
+
└── save_user() → ConnectionError
|
|
79
|
+
└── db.execute() → ConnectionError
|
|
80
|
+
└── raises ConnectionError (db/client.py:45)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Features
|
|
84
|
+
|
|
85
|
+
- **Entrypoint detection**: Flask routes, FastAPI routes, CLI scripts (`if __name__ == "__main__"`)
|
|
86
|
+
- **Global handler awareness**: Understands `@errorhandler`, `add_exception_handler`
|
|
87
|
+
- **Exception hierarchy**: Knows that catching `AppError` also catches `ValidationError` if it's a subclass
|
|
88
|
+
- **Polymorphism**: Expands abstract method calls to all concrete implementations
|
|
89
|
+
- **Framework-handled exceptions**: Detects HTTPException, ValidationError → HTTP responses
|
|
90
|
+
- **Confidence levels**: Shows high/medium/low confidence based on resolution quality
|
|
91
|
+
- **Resolution modes**: `--strict` for precision, `--aggressive` for recall
|
|
92
|
+
- **Exception stubs**: Declare what external libraries can raise (requests, sqlalchemy, etc.)
|
|
93
|
+
- **JSON output**: All commands support `-f json` for CI/automation
|
|
94
|
+
- **Caching**: SQLite-based caching for fast repeated analysis
|
|
95
|
+
|
|
96
|
+
## Commands
|
|
97
|
+
|
|
98
|
+
### Core Commands (framework-agnostic)
|
|
99
|
+
|
|
100
|
+
| Command | Description |
|
|
101
|
+
|---------|-------------|
|
|
102
|
+
| `bubble raises <exception>` | Find all places an exception is raised |
|
|
103
|
+
| `bubble escapes <function>` | Show what can escape from a specific function |
|
|
104
|
+
| `bubble callers <function>` | Find all callers of a function |
|
|
105
|
+
| `bubble catches <exception>` | Find all places an exception is caught |
|
|
106
|
+
| `bubble trace <function>` | Visualize exception flow as a call tree |
|
|
107
|
+
| `bubble exceptions` | Show the exception class hierarchy |
|
|
108
|
+
| `bubble subclasses <class>` | Show class inheritance tree |
|
|
109
|
+
| `bubble stubs <action>` | Manage exception stubs (`list`, `init`, `validate`) |
|
|
110
|
+
| `bubble stats` | Show codebase statistics |
|
|
111
|
+
|
|
112
|
+
### Framework-Specific Commands
|
|
113
|
+
|
|
114
|
+
| Command | Description |
|
|
115
|
+
|---------|-------------|
|
|
116
|
+
| `bubble flask audit` | Check Flask routes for escaping exceptions |
|
|
117
|
+
| `bubble flask entrypoints` | List Flask HTTP routes |
|
|
118
|
+
| `bubble flask routes-to <exc>` | Which Flask routes can trigger this exception? |
|
|
119
|
+
| `bubble fastapi audit` | Check FastAPI routes for escaping exceptions |
|
|
120
|
+
| `bubble fastapi entrypoints` | List FastAPI HTTP routes |
|
|
121
|
+
| `bubble fastapi routes-to <exc>` | Which FastAPI routes can trigger this exception? |
|
|
122
|
+
| `bubble cli audit` | Check CLI scripts for escaping exceptions |
|
|
123
|
+
| `bubble cli entrypoints` | List CLI scripts |
|
|
124
|
+
| `bubble cli scripts-to <exc>` | Which CLI scripts can trigger this exception? |
|
|
125
|
+
|
|
126
|
+
All commands accept:
|
|
127
|
+
- `-d, --directory`: Directory to analyze (default: current)
|
|
128
|
+
- `-f, --format`: Output format (`text` or `json`)
|
|
129
|
+
- `--no-cache`: Disable caching
|
|
130
|
+
|
|
131
|
+
The `escapes` command accepts additional flags:
|
|
132
|
+
- `--strict`: High precision mode - only includes precisely resolved calls
|
|
133
|
+
- `--aggressive`: High recall mode - includes fuzzy matches
|
|
134
|
+
|
|
135
|
+
## Supported Frameworks
|
|
136
|
+
|
|
137
|
+
**Detected automatically:**
|
|
138
|
+
- Flask (`@app.route`, `@blueprint.route`, `@app.errorhandler`)
|
|
139
|
+
- FastAPI (`@router.get/post/put/delete`, `add_exception_handler`)
|
|
140
|
+
- CLI scripts (`if __name__ == "__main__"`)
|
|
141
|
+
|
|
142
|
+
**Not yet supported:**
|
|
143
|
+
- Django
|
|
144
|
+
- Celery tasks
|
|
145
|
+
- Scheduled jobs (APScheduler, etc.)
|
|
146
|
+
|
|
147
|
+
Custom patterns can be added via `.flow/detectors/` (run `bubble init` to set up).
|
|
148
|
+
|
|
149
|
+
## Adding Custom Detectors
|
|
150
|
+
|
|
151
|
+
Flow is designed to be extended with AI coding agents. The detector interface is intentionally simple: implement a protocol that returns entrypoints and handlers from parsed code.
|
|
152
|
+
|
|
153
|
+
To add support for a new framework (Django, Celery, your internal RPC layer, etc.):
|
|
154
|
+
|
|
155
|
+
1. Run `bubble init` to create the `.flow/` directory structure
|
|
156
|
+
2. Point your AI agent at `flow/protocols.py` to see the `EntrypointDetector` interface
|
|
157
|
+
3. Ask it to implement a detector for your framework in `.flow/detectors/`
|
|
158
|
+
|
|
159
|
+
Example prompt for an AI agent:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
Read flow/protocols.py and flow/detectors.py to understand how entrypoint
|
|
163
|
+
detection works. Then implement a detector for Django that finds:
|
|
164
|
+
- Views decorated with @api_view
|
|
165
|
+
- Class-based views inheriting from APIView
|
|
166
|
+
- URL patterns in urls.py
|
|
167
|
+
|
|
168
|
+
Put the implementation in .flow/detectors/django.py
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The detector just needs to implement:
|
|
172
|
+
- `detect_entrypoints(functions, classes, ...)` → list of `Entrypoint`
|
|
173
|
+
- `detect_global_handlers(...)` → list of `GlobalHandler`
|
|
174
|
+
|
|
175
|
+
Flow will automatically load any `.py` files in `.flow/detectors/` and use them alongside the built-in Flask/FastAPI detectors.
|
|
176
|
+
|
|
177
|
+
## Configuration
|
|
178
|
+
|
|
179
|
+
Flow can be configured via `.flow/config.yaml`:
|
|
180
|
+
|
|
181
|
+
```yaml
|
|
182
|
+
resolution_mode: default # "strict", "default", or "aggressive"
|
|
183
|
+
exclude:
|
|
184
|
+
- vendor
|
|
185
|
+
- migrations
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Exception Stubs
|
|
189
|
+
|
|
190
|
+
Flow includes built-in stubs for common libraries (requests, sqlalchemy, httpx, redis, boto3). These declare what exceptions external library functions can raise.
|
|
191
|
+
|
|
192
|
+
Add custom stubs in `.flow/stubs/`:
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
# .flow/stubs/mylib.yaml
|
|
196
|
+
mylib:
|
|
197
|
+
do_thing:
|
|
198
|
+
- MyLibError
|
|
199
|
+
- TimeoutError
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Manage stubs with `bubble stubs list` and `bubble stubs validate`.
|
|
203
|
+
|
|
204
|
+
## How It Works
|
|
205
|
+
|
|
206
|
+
1. **Parse**: LibCST parses all Python files
|
|
207
|
+
2. **Extract**: Find functions, classes, raise/catch sites, calls, entrypoints
|
|
208
|
+
3. **Build call graph**: Track who calls whom, resolve method calls
|
|
209
|
+
4. **Propagate**: Fixed-point iteration computes which exceptions escape each function
|
|
210
|
+
5. **Report**: For each entrypoint, show caught vs uncaught exceptions
|
|
211
|
+
|
|
212
|
+
## Limitations
|
|
213
|
+
|
|
214
|
+
- **Over-approximation**: May report more exceptions than actually possible (e.g., all implementations of an abstract method)
|
|
215
|
+
- **Under-approximation**: Dynamic dispatch, `eval()`, and external libraries can't be fully traced
|
|
216
|
+
- **No runtime info**: Analysis is purely static
|
|
217
|
+
|
|
218
|
+
## Development
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
git clone https://github.com/ianm199/flow
|
|
222
|
+
cd flow
|
|
223
|
+
pip install -e ".[dev]"
|
|
224
|
+
pytest
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
MIT
|