Skip to main content

Stop guessing. Start understanding. Your Python errors, explained.

Project description

butwhy

Stop guessing. Start understanding.

Your Python errors, explained — in plain English.

pip install butwhyimport butwhy → done.

PyPI Python 3.9+ License: MIT CI Zero Dependencies


The 30-second pitch

You know how Python tracebacks look like this:

Traceback (most recent call last):
  File "demo.py", line 2, in <module>
    result = data.split(",")
AttributeError: 'NoneType' object has no attribute 'split'

And then you spend 5 minutes on Google figuring out what went wrong?

why turns that into this:

============================================================
  AttributeError: 'NoneType' object has no attribute 'split'
  at demo.py:2
============================================================

  [pattern match · 93%]

  Summary:
    Calling .split() on None

  Cause:
    The variable data is None, not a string.
    This usually means a function returned None (maybe it
    forgot a return statement), or a lookup failed silently.

  Variables at error:
    data = None  (NoneType)

  Fix:
    Add a None check: if data is not None: data.split(",")
    Or trace back to find why data is None.

============================================================

One import. Zero config. Instant understanding.


Quick start

pip install butwhy
import butwhy  # That's it. Errors are now explained.

data = None
result = data.split(",")  # Boom — why explains it

Want AI-powered deep explanations?

Set one environment variable and why upgrades automatically:

export OPENAI_API_KEY=sk-...   # or ANTHROPIC_API_KEY

No API key? why still works — it falls back to built-in pattern matching that covers 12+ common error types with zero dependencies.

Prefer a local model? (No API key, fully offline)

# Install Ollama: https://ollama.com
ollama pull qwen2.5-coder:7b

export BUTWHY_PROVIDER=ollama

Three ways to use why

1. Global (recommended) — import butwhy

import butwhy

# Every uncaught exception in your script is now explained
1 / 0

2. Context manager — with butwhy.trace()

import butwhy

with butwhy.trace():
    # Only errors inside this block are explained
    risky_operation()

3. Decorator — @butwhy.explain

import butwhy

@butwhy.explain
def divide(a, b):
    return a / b

divide(10, 0)  # Error is explained, then re-raised

butwhy.fix() — Don't just explain, fix

After an error, call butwhy.fix() to get an AI-generated patch:

import butwhy

data = None
result = data.split(",")  # crashes

# In interactive mode:
>>> butwhy.fix()

  Suggested fix for demo.py:2
  --------------------------------------------------
  - result = data.split(",")
  + if data is not None:
  +     result = data.split(",")
  + else:
  +     result = []
  --------------------------------------------------

  Apply this fix? [y/N] y
  Applied fix to demo.py:2

Requires an AI provider (OpenAI/Anthropic/Ollama).


butwhy.this — The Zen of Why

>>> import butwhy
>>> butwhy.this
The Zen of Why, by why

Errors are not failures, they are teachers.
The traceback tells you what; why tells you why.
A good message answers the question before you ask it.
Read the variables, not just the line numbers.
...

Jupyter / IPython support

# In a notebook:
%load_ext why

# Or just:
import butwhy

# Now all cell errors are explained inline

Configuration

All settings via environment variables — no config file needed:

Variable Default Description
OPENAI_API_KEY OpenAI API key (enables AI explanations)
ANTHROPIC_API_KEY Anthropic API key (enables AI explanations)
BUTWHY_PROVIDER auto auto / openai / anthropic / ollama / patterns
BUTWHY_LANGUAGE en en / zh (Chinese)
BUTWHY_MODEL Override the model name for the active provider
BUTWHY_TIMEOUT 30 API timeout in seconds
OLLAMA_URL http://localhost:11434 Ollama server URL
OLLAMA_MODEL qwen2.5-coder:7b Ollama model to use
BUTWHY_AUTOSTART 1 Set to 0 to disable auto-install of the hook
BUTWHY_NO_COLOR Set to disable colored output
NO_COLOR Standard no-color env var (respected)

Programmatic config

import butwhy

butwhy.set_config(butwhy.WhyConfig(
    provider="openai",
    openai_api_key="sk-...",
    language="zh",        # Chinese explanations
    show_source=True,
    show_vars=True,
    cache=True,
))

How it works

why uses a three-layer fallback to ensure you always get a useful explanation:

Layer When How Quality
AI explanation API key available Sends error + source context + variables to LLM Best — covers everything, gives specific fixes
Pattern matching No API key (or AI failed) Heuristic matching on 12+ common error types Good — covers the errors you hit daily
Enhanced traceback No pattern matched Colored output with variables and source context Fallback — still better than default

Key design principles:

  • Zero dependencies — pure Python stdlib. No rich, no httpx, no openai SDK. Just urllib and friends.
  • Never crashes — if the AI fails, pattern matching takes over. If that fails, you still get a prettier traceback.
  • Privacy-aware — your code only goes to an LLM if you explicitly set an API key. Pattern matching is 100% local.
  • Caching — repeated errors don't re-call the API. Cached for 7 days.

Comparison

Feature why rich better-exceptions stackprinter Copilot
Works without API key
AI-powered explanations
import and done
Local model support ✅ Ollama
Zero dependencies
Variable values in output
Auto-fix (butwhy.fix())
Works in CI/CD & servers
Chinese explanations
Jupyter magic

Supported error types (pattern matching)

The built-in pattern matcher covers these error types without any API key:

  • NameError — undefined variable
  • TypeError — wrong type operations
  • ValueError — invalid values (int conversion, unpacking)
  • IndexError — list index out of range
  • KeyError — missing dictionary key
  • AttributeError — wrong attribute/method (including NoneType)
  • ZeroDivisionError — division by zero
  • ImportError / ModuleNotFoundError — missing modules (with install hints)
  • FileNotFoundError — file path issues
  • SyntaxError — common syntax mistakes
  • RecursionError — infinite recursion
  • UnicodeDecodeError — encoding issues
  • IndentationError / TabError — indentation problems

More patterns are added regularly. With an AI provider, all error types are covered.


Examples

See the examples/ directory for runnable demos:

  • basic.py — common errors with pattern matching
  • with_ai.py — AI-powered deep explanations
  • jupyter_demo.ipynb — notebook usage
  • fix_demo.py — auto-fix workflow

Contributing

Contributions welcome! Especially:

  • New pattern matchers for error types not yet covered
  • Translations for BUTWHY_LANGUAGE
  • Bug reports and edge cases
git clone https://github.com/yourname/butwhy.git
cd butwhy
pip install -e ".[dev]"
pytest

License

MIT — see LICENSE.


import butwhy — because every error deserves an explanation.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

butwhy-0.1.0.tar.gz (32.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

butwhy-0.1.0-py3-none-any.whl (31.2 kB view details)

Uploaded Python 3

File details

Details for the file butwhy-0.1.0.tar.gz.

File metadata

  • Download URL: butwhy-0.1.0.tar.gz
  • Upload date:
  • Size: 32.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for butwhy-0.1.0.tar.gz
Algorithm Hash digest
SHA256 601f69ff33898557cc55c4d9cde693cd8a7b155d0b54610f4986049b5a065bab
MD5 31656f99d9ced66add194d122d0d87f4
BLAKE2b-256 1760df5d4a09bf4f8a195f83c8305989a749a226272e89f57bcc243b30810b10

See more details on using hashes here.

File details

Details for the file butwhy-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: butwhy-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 31.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for butwhy-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 635f50318b7746f34bd4a2c625712abd61db7b19cfc46193a38166373e038060
MD5 fb2ade0c41d3819d00dd98a717ecfd39
BLAKE2b-256 4a43b8510f4d6726501513c1b8d4df8935cee8ad2076422cc60292e66ea6fec2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page