Metadata-Version: 2.4
Name: bubble-analysis
Version: 0.2.0
Summary: Static analysis tool for tracing exception flow through Python codebases
Author-email: Ian McLaughlin <ianm199@github.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/ianm199/flow
Project-URL: Repository, https://github.com/ianm199/flow
Project-URL: Issues, https://github.com/ianm199/flow/issues
Keywords: static-analysis,exceptions,python,code-analysis,flask,fastapi
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Testing
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: libcst>=1.1.0
Requires-Dist: typer>=0.12.0
Requires-Dist: rich>=13.0.0
Requires-Dist: msgpack>=1.0.0
Requires-Dist: pyyaml>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Dynamic: license-file

# flow

Static analysis tool for tracing exception flow through Python codebases.

**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.

## Quick Start

```bash
pip install bubble-analysis
```

```bash
# 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

Flow finds your HTTP routes and CLI scripts, traces the call graph, and reports which exceptions can escape:

```
$ flow 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:

```
$ flow 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:

```
$ flow 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

- **Entrypoint detection**: Flask routes, FastAPI routes, CLI scripts (`if __name__ == "__main__"`)
- **Global handler awareness**: Understands `@errorhandler`, `add_exception_handler`
- **Exception hierarchy**: Knows that catching `AppError` also catches `ValidationError` if 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**: `--strict` for precision, `--aggressive` for recall
- **Exception stubs**: Declare what external libraries can raise (requests, sqlalchemy, etc.)
- **JSON output**: All commands support `-f json` for CI/automation
- **Caching**: SQLite-based caching for fast repeated analysis

## 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 (`text` or `json`)
- `--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/` (run `bubble init` to set up).

## Adding Custom Detectors

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.

To add support for a new framework (Django, Celery, your internal RPC layer, etc.):

1. Run `bubble init` to create the `.flow/` directory structure
2. Point your AI agent at `flow/protocols.py` to see the `EntrypointDetector` interface
3. Ask it to implement a detector for your framework in `.flow/detectors/`

Example prompt for an AI agent:

```
Read flow/protocols.py and flow/detectors.py to understand how entrypoint
detection works. Then implement a detector for Django that finds:
- Views decorated with @api_view
- Class-based views inheriting from APIView
- URL patterns in urls.py

Put the implementation in .flow/detectors/django.py
```

The detector just needs to implement:
- `detect_entrypoints(functions, classes, ...)` → list of `Entrypoint`
- `detect_global_handlers(...)` → list of `GlobalHandler`

Flow will automatically load any `.py` files in `.flow/detectors/` and use them alongside the built-in Flask/FastAPI detectors.

## Configuration

Flow can be configured via `.flow/config.yaml`:

```yaml
resolution_mode: default  # "strict", "default", or "aggressive"
exclude:
  - vendor
  - migrations
```

### Exception Stubs

Flow 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/`:

```yaml
# .flow/stubs/mylib.yaml
mylib:
  do_thing:
    - MyLibError
    - TimeoutError
```

Manage stubs with `bubble stubs list` and `bubble stubs validate`.

## How It Works

1. **Parse**: LibCST parses all Python files
2. **Extract**: Find functions, classes, raise/catch sites, calls, entrypoints
3. **Build call graph**: Track who calls whom, resolve method calls
4. **Propagate**: Fixed-point iteration computes which exceptions escape each function
5. **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

```bash
git clone https://github.com/ianm199/flow
cd flow
pip install -e ".[dev]"
pytest
```

## License

MIT
