Bubble
Static analysis tool for tracing exception flow through Python codebases.
What can escape from my API endpoints? Bubble answers this by parsing your code, building a call graph, and computing which exceptions propagate to each entrypoint.
Quick Start
pip install bubble-analysis
# Check Flask routes for uncaught exceptions
bubble flask audit -d /path/to/project
# Check FastAPI routes
bubble fastapi audit -d /path/to/project
# Deep dive into one function
bubble escapes create_user -d /path/to/project
# Visualize the call tree
bubble trace create_user -d /path/to/project
What It Does
Bubble finds your HTTP routes and CLI scripts, traces the call graph, and reports which exceptions can escape:
$ bubble flask audit
Scanning 23 flask entrypoints...
3 entrypoints have uncaught exceptions:
POST /users/import
└─ FileNotFoundError (importers.py:45)
└─ ValidationError (validators.py:12)
GET /reports/{id}
└─ PermissionError (auth.py:89)
20 entrypoints fully covered by exception handlers
For a specific endpoint, see the full picture:
$ bubble escapes create_user
Exceptions that can escape from POST /users:
FRAMEWORK-HANDLED (converted to HTTP response):
HTTPException
└─ becomes: HTTP 404
└─ raised in: routes/users.py:45 (get_user) [high confidence]
CAUGHT BY GLOBAL HANDLER:
ValidationError (@errorhandler(AppError))
└─ raised in: validators.py:27 (validate_input) [high confidence]
UNCAUGHT (will propagate to caller):
ConnectionError
└─ raised in: db/client.py:45 (execute) [medium confidence]
└─ call path: create_user → save_user → db.execute
Visualize as a tree:
$ bubble trace create_user
POST /users → escapes: ValidationError, ConnectionError
├── validate_input() → ValidationError
│ └── raises ValidationError (validators.py:27)
└── save_user() → ConnectionError
└── db.execute() → ConnectionError
└── raises ConnectionError (db/client.py:45)
Features
- Extensible: Adapt to any framework or custom pattern (see extending guide)
- Entrypoint detection: Flask routes, FastAPI routes, CLI scripts (
if __name__ == "__main__") - Global handler awareness: Understands
@errorhandler,add_exception_handler - Exception hierarchy: Knows that catching
AppErroralso catchesValidationErrorif it's a subclass - Polymorphism: Expands abstract method calls to all concrete implementations
- Framework-handled exceptions: Detects HTTPException, ValidationError → HTTP responses
- Confidence levels: Shows high/medium/low confidence based on resolution quality
- Resolution modes:
--strictfor precision,--aggressivefor recall - Exception stubs: Built-in stubs for requests, sqlalchemy, httpx, redis, boto3
- JSON output: All commands support
-f jsonfor CI/automation - Caching: SQLite-based caching for fast repeated analysis
- Python 3.10+: Supports Python 3.10, 3.11, 3.12, and 3.13
Commands
Core Commands (framework-agnostic)
| Command | Description |
|---|---|
bubble raises <exception> |
Find all places an exception is raised |
bubble escapes <function> |
Show what can escape from a specific function |
bubble callers <function> |
Find all callers of a function |
bubble catches <exception> |
Find all places an exception is caught |
bubble trace <function> |
Visualize exception flow as a call tree |
bubble exceptions |
Show the exception class hierarchy |
bubble subclasses <class> |
Show class inheritance tree |
bubble stubs <action> |
Manage exception stubs (list, init, validate) |
bubble stats |
Show codebase statistics |
Framework-Specific Commands
| Command | Description |
|---|---|
bubble flask audit |
Check Flask routes for escaping exceptions |
bubble flask entrypoints |
List Flask HTTP routes |
bubble flask routes-to <exc> |
Which Flask routes can trigger this exception? |
bubble fastapi audit |
Check FastAPI routes for escaping exceptions |
bubble fastapi entrypoints |
List FastAPI HTTP routes |
bubble fastapi routes-to <exc> |
Which FastAPI routes can trigger this exception? |
bubble cli audit |
Check CLI scripts for escaping exceptions |
bubble cli entrypoints |
List CLI scripts |
bubble cli scripts-to <exc> |
Which CLI scripts can trigger this exception? |
All commands accept:
-d, --directory: Directory to analyze (default: current)-f, --format: Output format (textorjson)--no-cache: Disable caching
The escapes command accepts additional flags:
--strict: High precision mode - only includes precisely resolved calls--aggressive: High recall mode - includes fuzzy matches
Supported Frameworks
Detected automatically:
- Flask (
@app.route,@blueprint.route,@app.errorhandler) - FastAPI (
@router.get/post/put/delete,add_exception_handler) - CLI scripts (
if __name__ == "__main__")
Not yet supported:
- Django
- Celery tasks
- Scheduled jobs (APScheduler, etc.)
Custom patterns can be added via .flow/detectors/.
Extending Bubble
Bubble is designed to be adapted to your codebase. The core engine handles exception propagation—you just tell it where your entrypoints are.
Every codebase has its own patterns: internal RPC frameworks, custom decorators, queue handlers, scheduled jobs. Bubble's extension system lets you teach it your patterns without modifying the core.
Full guide: docs/EXTENDING.md
What You Can Extend
| Pattern | Example | How to add |
|---|---|---|
| Custom entrypoints | Celery tasks, Django views, gRPC handlers | EntrypointDetector |
| Custom error handlers | Middleware, decorators | GlobalHandlerDetector |
| Full framework | Django REST Framework, Airflow | Integration protocol |
Quick Example
Drop a detector in .flow/detectors/ and bubble auto-loads it:
# .flow/detectors/celery.py
from bubble.protocols import EntrypointDetector
from bubble.integrations.base import Entrypoint
from bubble.enums import EntrypointKind
class CeleryTaskDetector(EntrypointDetector):
def detect(self, source: str, file_path: str) -> list[Entrypoint]:
# Find @app.task decorators, return Entrypoint objects
...
AI-Friendly Design
The protocol is intentionally simple for LLMs. Give your agent:
Read bubble/protocols.py and bubble/integrations/flask/detector.py.
Implement a detector for [YOUR FRAMEWORK] that finds [PATTERNS].
Put it in .flow/detectors/[framework].py
See docs/EXTENDING.md for complete examples including Celery, Django REST Framework, and custom decorator patterns.
Configuration
Bubble can be configured via .flow/config.yaml:
resolution_mode: default # "strict", "default", or "aggressive"
exclude:
- vendor
- migrations
Exception Stubs
Bubble includes built-in stubs for common libraries (requests, sqlalchemy, httpx, redis, boto3). These declare what exceptions external library functions can raise.
Add custom stubs in .flow/stubs/:
# .flow/stubs/mylib.yaml
module: mylib
functions:
do_thing:
- MyLibError
- TimeoutError
Manage stubs with bubble stubs list and bubble stubs validate.
How It Works
- Parse: LibCST parses all Python files
- Extract: Find functions, classes, raise/catch sites, calls, entrypoints
- Build call graph: Track who calls whom, resolve method calls
- Propagate: Fixed-point iteration computes which exceptions escape each function
- Report: For each entrypoint, show caught vs uncaught exceptions
Limitations
- Over-approximation: May report more exceptions than actually possible (e.g., all implementations of an abstract method)
- Under-approximation: Dynamic dispatch,
eval(), and external libraries can't be fully traced - No runtime info: Analysis is purely static
Development
git clone https://github.com/ianm199/bubble-analysis
cd bubble-analysis
uv pip install -e ".[dev]"
uv run pytest
License
MIT
Metadata
Release files for bubble-analysis 0.3.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| bubble_analysis-0.3.5.tar.gz | 84.1 kB | Without provenance | Details for bubble_analysis-0.3.5.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| bubble_analysis-0.3.5-py3-none-any.whl | Python 3 | none | any | Details for bubble_analysis-0.3.5-py3-none-any.whl |
Total release size: 169.3 kB
Release files / bubble_analysis-0.3.5.tar.gz
| Download URL | bubble_analysis-0.3.5.tar.gz |
|---|---|
| Size | 84.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
065715c995153c0838e3269da378099dd88bcf0448f7673e9d4e30ac39b7b4b9
|
|
BLAKE2b-256 checksum How to use checksums |
07465f292c6876cf171127d2ea5df61b8bfe6613a6dec3755ecf62cf270d1877
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.2
|
Release files / bubble_analysis-0.3.5-py3-none-any.whl
| Download URL | bubble_analysis-0.3.5-py3-none-any.whl |
|---|---|
| Size | 85.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d5b59e74de3a1be149ac2481259b5ea41c737a312c3b4429bc891a585f088294
|
|
BLAKE2b-256 checksum How to use checksums |
efd877dcff6e981b6d95ccf6e797efd0e2cc13efeffdc44d72e756b487e4b90c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.2
|
Security for bubble-analysis 0.3.5
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.