butwhy 0.1.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.
- butwhy-0.1.0/LICENSE +21 -0
- butwhy-0.1.0/PKG-INFO +367 -0
- butwhy-0.1.0/README.md +331 -0
- butwhy-0.1.0/butwhy/__init__.py +337 -0
- butwhy-0.1.0/butwhy/__main__.py +27 -0
- butwhy-0.1.0/butwhy/cache.py +105 -0
- butwhy-0.1.0/butwhy/cli.py +88 -0
- butwhy-0.1.0/butwhy/config.py +134 -0
- butwhy-0.1.0/butwhy/context.py +131 -0
- butwhy-0.1.0/butwhy/explainer.py +259 -0
- butwhy-0.1.0/butwhy/formatter.py +190 -0
- butwhy-0.1.0/butwhy/hook.py +165 -0
- butwhy-0.1.0/butwhy/patterns.py +508 -0
- butwhy-0.1.0/butwhy/providers/__init__.py +30 -0
- butwhy-0.1.0/butwhy/providers/anthropic.py +52 -0
- butwhy-0.1.0/butwhy/providers/ollama.py +69 -0
- butwhy-0.1.0/butwhy/providers/openai.py +59 -0
- butwhy-0.1.0/butwhy.egg-info/PKG-INFO +367 -0
- butwhy-0.1.0/butwhy.egg-info/SOURCES.txt +26 -0
- butwhy-0.1.0/butwhy.egg-info/dependency_links.txt +1 -0
- butwhy-0.1.0/butwhy.egg-info/entry_points.txt +2 -0
- butwhy-0.1.0/butwhy.egg-info/requires.txt +6 -0
- butwhy-0.1.0/butwhy.egg-info/top_level.txt +1 -0
- butwhy-0.1.0/pyproject.toml +73 -0
- butwhy-0.1.0/setup.cfg +4 -0
- butwhy-0.1.0/tests/test_core.py +247 -0
- butwhy-0.1.0/tests/test_patterns.py +152 -0
- butwhy-0.1.0/tests/test_providers.py +101 -0
butwhy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 why contributors
|
|
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.
|
butwhy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: butwhy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Stop guessing. Start understanding. Your Python errors, explained.
|
|
5
|
+
Author: butwhy contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourname/butwhy
|
|
8
|
+
Project-URL: Repository, https://github.com/yourname/butwhy
|
|
9
|
+
Project-URL: Issues, https://github.com/yourname/butwhy/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/yourname/butwhy/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: error,debugging,traceback,ai,explanation,developer-tools,cli,llm,openai,anthropic,ollama,butwhy
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
23
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Topic :: Utilities
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
33
|
+
Requires-Dist: build; extra == "dev"
|
|
34
|
+
Requires-Dist: twine; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
<div align="center">
|
|
38
|
+
|
|
39
|
+
# butwhy
|
|
40
|
+
|
|
41
|
+
**Stop guessing. Start understanding.**
|
|
42
|
+
|
|
43
|
+
Your Python errors, explained — in plain English.
|
|
44
|
+
|
|
45
|
+
`pip install butwhy` → `import butwhy` → done.
|
|
46
|
+
|
|
47
|
+
[](https://pypi.org/project/butwhy/)
|
|
48
|
+
[](https://www.python.org/downloads/)
|
|
49
|
+
[](LICENSE)
|
|
50
|
+
[](https://github.com/yourname/butwhy/actions)
|
|
51
|
+
[](#)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## The 30-second pitch
|
|
58
|
+
|
|
59
|
+
You know how Python tracebacks look like this:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
Traceback (most recent call last):
|
|
63
|
+
File "demo.py", line 2, in <module>
|
|
64
|
+
result = data.split(",")
|
|
65
|
+
AttributeError: 'NoneType' object has no attribute 'split'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
And then you spend 5 minutes on Google figuring out what went wrong?
|
|
69
|
+
|
|
70
|
+
**`why` turns that into this:**
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
============================================================
|
|
74
|
+
AttributeError: 'NoneType' object has no attribute 'split'
|
|
75
|
+
at demo.py:2
|
|
76
|
+
============================================================
|
|
77
|
+
|
|
78
|
+
[pattern match · 93%]
|
|
79
|
+
|
|
80
|
+
Summary:
|
|
81
|
+
Calling .split() on None
|
|
82
|
+
|
|
83
|
+
Cause:
|
|
84
|
+
The variable data is None, not a string.
|
|
85
|
+
This usually means a function returned None (maybe it
|
|
86
|
+
forgot a return statement), or a lookup failed silently.
|
|
87
|
+
|
|
88
|
+
Variables at error:
|
|
89
|
+
data = None (NoneType)
|
|
90
|
+
|
|
91
|
+
Fix:
|
|
92
|
+
Add a None check: if data is not None: data.split(",")
|
|
93
|
+
Or trace back to find why data is None.
|
|
94
|
+
|
|
95
|
+
============================================================
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**One import. Zero config. Instant understanding.**
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Quick start
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pip install butwhy
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import butwhy # That's it. Errors are now explained.
|
|
110
|
+
|
|
111
|
+
data = None
|
|
112
|
+
result = data.split(",") # Boom — why explains it
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
### Want AI-powered deep explanations?
|
|
118
|
+
|
|
119
|
+
Set one environment variable and why upgrades automatically:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
export OPENAI_API_KEY=sk-... # or ANTHROPIC_API_KEY
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
No API key? **why still works** — it falls back to built-in pattern matching that covers 12+ common error types with zero dependencies.
|
|
126
|
+
|
|
127
|
+
### Prefer a local model? (No API key, fully offline)
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Install Ollama: https://ollama.com
|
|
131
|
+
ollama pull qwen2.5-coder:7b
|
|
132
|
+
|
|
133
|
+
export BUTWHY_PROVIDER=ollama
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Three ways to use why
|
|
139
|
+
|
|
140
|
+
### 1. Global (recommended) — `import butwhy`
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
import butwhy
|
|
144
|
+
|
|
145
|
+
# Every uncaught exception in your script is now explained
|
|
146
|
+
1 / 0
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 2. Context manager — `with butwhy.trace()`
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
import butwhy
|
|
153
|
+
|
|
154
|
+
with butwhy.trace():
|
|
155
|
+
# Only errors inside this block are explained
|
|
156
|
+
risky_operation()
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 3. Decorator — `@butwhy.explain`
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
import butwhy
|
|
163
|
+
|
|
164
|
+
@butwhy.explain
|
|
165
|
+
def divide(a, b):
|
|
166
|
+
return a / b
|
|
167
|
+
|
|
168
|
+
divide(10, 0) # Error is explained, then re-raised
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## butwhy.fix() — Don't just explain, *fix*
|
|
174
|
+
|
|
175
|
+
After an error, call `butwhy.fix()` to get an AI-generated patch:
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
import butwhy
|
|
179
|
+
|
|
180
|
+
data = None
|
|
181
|
+
result = data.split(",") # crashes
|
|
182
|
+
|
|
183
|
+
# In interactive mode:
|
|
184
|
+
>>> butwhy.fix()
|
|
185
|
+
|
|
186
|
+
Suggested fix for demo.py:2
|
|
187
|
+
--------------------------------------------------
|
|
188
|
+
- result = data.split(",")
|
|
189
|
+
+ if data is not None:
|
|
190
|
+
+ result = data.split(",")
|
|
191
|
+
+ else:
|
|
192
|
+
+ result = []
|
|
193
|
+
--------------------------------------------------
|
|
194
|
+
|
|
195
|
+
Apply this fix? [y/N] y
|
|
196
|
+
Applied fix to demo.py:2
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
*Requires an AI provider (OpenAI/Anthropic/Ollama).*
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## butwhy.this — The Zen of Why
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
>>> import butwhy
|
|
207
|
+
>>> butwhy.this
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
The Zen of Why, by why
|
|
212
|
+
|
|
213
|
+
Errors are not failures, they are teachers.
|
|
214
|
+
The traceback tells you what; why tells you why.
|
|
215
|
+
A good message answers the question before you ask it.
|
|
216
|
+
Read the variables, not just the line numbers.
|
|
217
|
+
...
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Jupyter / IPython support
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
# In a notebook:
|
|
226
|
+
%load_ext why
|
|
227
|
+
|
|
228
|
+
# Or just:
|
|
229
|
+
import butwhy
|
|
230
|
+
|
|
231
|
+
# Now all cell errors are explained inline
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Configuration
|
|
237
|
+
|
|
238
|
+
All settings via environment variables — no config file needed:
|
|
239
|
+
|
|
240
|
+
| Variable | Default | Description |
|
|
241
|
+
|---|---|---|
|
|
242
|
+
| `OPENAI_API_KEY` | — | OpenAI API key (enables AI explanations) |
|
|
243
|
+
| `ANTHROPIC_API_KEY` | — | Anthropic API key (enables AI explanations) |
|
|
244
|
+
| `BUTWHY_PROVIDER` | `auto` | `auto` / `openai` / `anthropic` / `ollama` / `patterns` |
|
|
245
|
+
| `BUTWHY_LANGUAGE` | `en` | `en` / `zh` (Chinese) |
|
|
246
|
+
| `BUTWHY_MODEL` | — | Override the model name for the active provider |
|
|
247
|
+
| `BUTWHY_TIMEOUT` | `30` | API timeout in seconds |
|
|
248
|
+
| `OLLAMA_URL` | `http://localhost:11434` | Ollama server URL |
|
|
249
|
+
| `OLLAMA_MODEL` | `qwen2.5-coder:7b` | Ollama model to use |
|
|
250
|
+
| `BUTWHY_AUTOSTART` | `1` | Set to `0` to disable auto-install of the hook |
|
|
251
|
+
| `BUTWHY_NO_COLOR` | — | Set to disable colored output |
|
|
252
|
+
| `NO_COLOR` | — | Standard no-color env var (respected) |
|
|
253
|
+
|
|
254
|
+
### Programmatic config
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
import butwhy
|
|
258
|
+
|
|
259
|
+
butwhy.set_config(butwhy.WhyConfig(
|
|
260
|
+
provider="openai",
|
|
261
|
+
openai_api_key="sk-...",
|
|
262
|
+
language="zh", # Chinese explanations
|
|
263
|
+
show_source=True,
|
|
264
|
+
show_vars=True,
|
|
265
|
+
cache=True,
|
|
266
|
+
))
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## How it works
|
|
272
|
+
|
|
273
|
+
`why` uses a **three-layer fallback** to ensure you always get a useful explanation:
|
|
274
|
+
|
|
275
|
+
| Layer | When | How | Quality |
|
|
276
|
+
|---|---|---|---|
|
|
277
|
+
| **AI explanation** | API key available | Sends error + source context + variables to LLM | Best — covers everything, gives specific fixes |
|
|
278
|
+
| **Pattern matching** | No API key (or AI failed) | Heuristic matching on 12+ common error types | Good — covers the errors you hit daily |
|
|
279
|
+
| **Enhanced traceback** | No pattern matched | Colored output with variables and source context | Fallback — still better than default |
|
|
280
|
+
|
|
281
|
+
**Key design principles:**
|
|
282
|
+
|
|
283
|
+
- **Zero dependencies** — pure Python stdlib. No rich, no httpx, no openai SDK. Just `urllib` and friends.
|
|
284
|
+
- **Never crashes** — if the AI fails, pattern matching takes over. If that fails, you still get a prettier traceback.
|
|
285
|
+
- **Privacy-aware** — your code only goes to an LLM if you explicitly set an API key. Pattern matching is 100% local.
|
|
286
|
+
- **Caching** — repeated errors don't re-call the API. Cached for 7 days.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Comparison
|
|
291
|
+
|
|
292
|
+
| Feature | `why` | rich | better-exceptions | stackprinter | Copilot |
|
|
293
|
+
|---|---|---|---|---|---|
|
|
294
|
+
| Works without API key | ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
295
|
+
| AI-powered explanations | ✅ | ❌ | ❌ | ❌ | ✅ |
|
|
296
|
+
| `import` and done | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
297
|
+
| Local model support | ✅ Ollama | — | — | — | ❌ |
|
|
298
|
+
| Zero dependencies | ✅ | ❌ | ❌ | ❌ | — |
|
|
299
|
+
| Variable values in output | ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
300
|
+
| Auto-fix (`butwhy.fix()`) | ✅ | ❌ | ❌ | ❌ | ✅ |
|
|
301
|
+
| Works in CI/CD & servers | ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
302
|
+
| Chinese explanations | ✅ | ❌ | ❌ | ❌ | ✅ |
|
|
303
|
+
| Jupyter magic | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Supported error types (pattern matching)
|
|
308
|
+
|
|
309
|
+
The built-in pattern matcher covers these error types without any API key:
|
|
310
|
+
|
|
311
|
+
- `NameError` — undefined variable
|
|
312
|
+
- `TypeError` — wrong type operations
|
|
313
|
+
- `ValueError` — invalid values (int conversion, unpacking)
|
|
314
|
+
- `IndexError` — list index out of range
|
|
315
|
+
- `KeyError` — missing dictionary key
|
|
316
|
+
- `AttributeError` — wrong attribute/method (including NoneType)
|
|
317
|
+
- `ZeroDivisionError` — division by zero
|
|
318
|
+
- `ImportError` / `ModuleNotFoundError` — missing modules (with install hints)
|
|
319
|
+
- `FileNotFoundError` — file path issues
|
|
320
|
+
- `SyntaxError` — common syntax mistakes
|
|
321
|
+
- `RecursionError` — infinite recursion
|
|
322
|
+
- `UnicodeDecodeError` — encoding issues
|
|
323
|
+
- `IndentationError` / `TabError` — indentation problems
|
|
324
|
+
|
|
325
|
+
More patterns are added regularly. With an AI provider, *all* error types are covered.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Examples
|
|
330
|
+
|
|
331
|
+
See the [`examples/`](examples/) directory for runnable demos:
|
|
332
|
+
|
|
333
|
+
- `basic.py` — common errors with pattern matching
|
|
334
|
+
- `with_ai.py` — AI-powered deep explanations
|
|
335
|
+
- `jupyter_demo.ipynb` — notebook usage
|
|
336
|
+
- `fix_demo.py` — auto-fix workflow
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Contributing
|
|
341
|
+
|
|
342
|
+
Contributions welcome! Especially:
|
|
343
|
+
|
|
344
|
+
- New pattern matchers for error types not yet covered
|
|
345
|
+
- Translations for `BUTWHY_LANGUAGE`
|
|
346
|
+
- Bug reports and edge cases
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
git clone https://github.com/yourname/butwhy.git
|
|
350
|
+
cd butwhy
|
|
351
|
+
pip install -e ".[dev]"
|
|
352
|
+
pytest
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## License
|
|
358
|
+
|
|
359
|
+
MIT — see [LICENSE](LICENSE).
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
<div align="center">
|
|
364
|
+
|
|
365
|
+
**`import butwhy`** — because every error deserves an explanation.
|
|
366
|
+
|
|
367
|
+
</div>
|
butwhy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# butwhy
|
|
4
|
+
|
|
5
|
+
**Stop guessing. Start understanding.**
|
|
6
|
+
|
|
7
|
+
Your Python errors, explained — in plain English.
|
|
8
|
+
|
|
9
|
+
`pip install butwhy` → `import butwhy` → done.
|
|
10
|
+
|
|
11
|
+
[](https://pypi.org/project/butwhy/)
|
|
12
|
+
[](https://www.python.org/downloads/)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
[](https://github.com/yourname/butwhy/actions)
|
|
15
|
+
[](#)
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## The 30-second pitch
|
|
22
|
+
|
|
23
|
+
You know how Python tracebacks look like this:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Traceback (most recent call last):
|
|
27
|
+
File "demo.py", line 2, in <module>
|
|
28
|
+
result = data.split(",")
|
|
29
|
+
AttributeError: 'NoneType' object has no attribute 'split'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
And then you spend 5 minutes on Google figuring out what went wrong?
|
|
33
|
+
|
|
34
|
+
**`why` turns that into this:**
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
============================================================
|
|
38
|
+
AttributeError: 'NoneType' object has no attribute 'split'
|
|
39
|
+
at demo.py:2
|
|
40
|
+
============================================================
|
|
41
|
+
|
|
42
|
+
[pattern match · 93%]
|
|
43
|
+
|
|
44
|
+
Summary:
|
|
45
|
+
Calling .split() on None
|
|
46
|
+
|
|
47
|
+
Cause:
|
|
48
|
+
The variable data is None, not a string.
|
|
49
|
+
This usually means a function returned None (maybe it
|
|
50
|
+
forgot a return statement), or a lookup failed silently.
|
|
51
|
+
|
|
52
|
+
Variables at error:
|
|
53
|
+
data = None (NoneType)
|
|
54
|
+
|
|
55
|
+
Fix:
|
|
56
|
+
Add a None check: if data is not None: data.split(",")
|
|
57
|
+
Or trace back to find why data is None.
|
|
58
|
+
|
|
59
|
+
============================================================
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**One import. Zero config. Instant understanding.**
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Quick start
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install butwhy
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import butwhy # That's it. Errors are now explained.
|
|
74
|
+
|
|
75
|
+
data = None
|
|
76
|
+
result = data.split(",") # Boom — why explains it
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
### Want AI-powered deep explanations?
|
|
82
|
+
|
|
83
|
+
Set one environment variable and why upgrades automatically:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
export OPENAI_API_KEY=sk-... # or ANTHROPIC_API_KEY
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
No API key? **why still works** — it falls back to built-in pattern matching that covers 12+ common error types with zero dependencies.
|
|
90
|
+
|
|
91
|
+
### Prefer a local model? (No API key, fully offline)
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Install Ollama: https://ollama.com
|
|
95
|
+
ollama pull qwen2.5-coder:7b
|
|
96
|
+
|
|
97
|
+
export BUTWHY_PROVIDER=ollama
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Three ways to use why
|
|
103
|
+
|
|
104
|
+
### 1. Global (recommended) — `import butwhy`
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import butwhy
|
|
108
|
+
|
|
109
|
+
# Every uncaught exception in your script is now explained
|
|
110
|
+
1 / 0
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 2. Context manager — `with butwhy.trace()`
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
import butwhy
|
|
117
|
+
|
|
118
|
+
with butwhy.trace():
|
|
119
|
+
# Only errors inside this block are explained
|
|
120
|
+
risky_operation()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. Decorator — `@butwhy.explain`
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
import butwhy
|
|
127
|
+
|
|
128
|
+
@butwhy.explain
|
|
129
|
+
def divide(a, b):
|
|
130
|
+
return a / b
|
|
131
|
+
|
|
132
|
+
divide(10, 0) # Error is explained, then re-raised
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## butwhy.fix() — Don't just explain, *fix*
|
|
138
|
+
|
|
139
|
+
After an error, call `butwhy.fix()` to get an AI-generated patch:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import butwhy
|
|
143
|
+
|
|
144
|
+
data = None
|
|
145
|
+
result = data.split(",") # crashes
|
|
146
|
+
|
|
147
|
+
# In interactive mode:
|
|
148
|
+
>>> butwhy.fix()
|
|
149
|
+
|
|
150
|
+
Suggested fix for demo.py:2
|
|
151
|
+
--------------------------------------------------
|
|
152
|
+
- result = data.split(",")
|
|
153
|
+
+ if data is not None:
|
|
154
|
+
+ result = data.split(",")
|
|
155
|
+
+ else:
|
|
156
|
+
+ result = []
|
|
157
|
+
--------------------------------------------------
|
|
158
|
+
|
|
159
|
+
Apply this fix? [y/N] y
|
|
160
|
+
Applied fix to demo.py:2
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
*Requires an AI provider (OpenAI/Anthropic/Ollama).*
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## butwhy.this — The Zen of Why
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
>>> import butwhy
|
|
171
|
+
>>> butwhy.this
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
The Zen of Why, by why
|
|
176
|
+
|
|
177
|
+
Errors are not failures, they are teachers.
|
|
178
|
+
The traceback tells you what; why tells you why.
|
|
179
|
+
A good message answers the question before you ask it.
|
|
180
|
+
Read the variables, not just the line numbers.
|
|
181
|
+
...
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Jupyter / IPython support
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
# In a notebook:
|
|
190
|
+
%load_ext why
|
|
191
|
+
|
|
192
|
+
# Or just:
|
|
193
|
+
import butwhy
|
|
194
|
+
|
|
195
|
+
# Now all cell errors are explained inline
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Configuration
|
|
201
|
+
|
|
202
|
+
All settings via environment variables — no config file needed:
|
|
203
|
+
|
|
204
|
+
| Variable | Default | Description |
|
|
205
|
+
|---|---|---|
|
|
206
|
+
| `OPENAI_API_KEY` | — | OpenAI API key (enables AI explanations) |
|
|
207
|
+
| `ANTHROPIC_API_KEY` | — | Anthropic API key (enables AI explanations) |
|
|
208
|
+
| `BUTWHY_PROVIDER` | `auto` | `auto` / `openai` / `anthropic` / `ollama` / `patterns` |
|
|
209
|
+
| `BUTWHY_LANGUAGE` | `en` | `en` / `zh` (Chinese) |
|
|
210
|
+
| `BUTWHY_MODEL` | — | Override the model name for the active provider |
|
|
211
|
+
| `BUTWHY_TIMEOUT` | `30` | API timeout in seconds |
|
|
212
|
+
| `OLLAMA_URL` | `http://localhost:11434` | Ollama server URL |
|
|
213
|
+
| `OLLAMA_MODEL` | `qwen2.5-coder:7b` | Ollama model to use |
|
|
214
|
+
| `BUTWHY_AUTOSTART` | `1` | Set to `0` to disable auto-install of the hook |
|
|
215
|
+
| `BUTWHY_NO_COLOR` | — | Set to disable colored output |
|
|
216
|
+
| `NO_COLOR` | — | Standard no-color env var (respected) |
|
|
217
|
+
|
|
218
|
+
### Programmatic config
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
import butwhy
|
|
222
|
+
|
|
223
|
+
butwhy.set_config(butwhy.WhyConfig(
|
|
224
|
+
provider="openai",
|
|
225
|
+
openai_api_key="sk-...",
|
|
226
|
+
language="zh", # Chinese explanations
|
|
227
|
+
show_source=True,
|
|
228
|
+
show_vars=True,
|
|
229
|
+
cache=True,
|
|
230
|
+
))
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## How it works
|
|
236
|
+
|
|
237
|
+
`why` uses a **three-layer fallback** to ensure you always get a useful explanation:
|
|
238
|
+
|
|
239
|
+
| Layer | When | How | Quality |
|
|
240
|
+
|---|---|---|---|
|
|
241
|
+
| **AI explanation** | API key available | Sends error + source context + variables to LLM | Best — covers everything, gives specific fixes |
|
|
242
|
+
| **Pattern matching** | No API key (or AI failed) | Heuristic matching on 12+ common error types | Good — covers the errors you hit daily |
|
|
243
|
+
| **Enhanced traceback** | No pattern matched | Colored output with variables and source context | Fallback — still better than default |
|
|
244
|
+
|
|
245
|
+
**Key design principles:**
|
|
246
|
+
|
|
247
|
+
- **Zero dependencies** — pure Python stdlib. No rich, no httpx, no openai SDK. Just `urllib` and friends.
|
|
248
|
+
- **Never crashes** — if the AI fails, pattern matching takes over. If that fails, you still get a prettier traceback.
|
|
249
|
+
- **Privacy-aware** — your code only goes to an LLM if you explicitly set an API key. Pattern matching is 100% local.
|
|
250
|
+
- **Caching** — repeated errors don't re-call the API. Cached for 7 days.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Comparison
|
|
255
|
+
|
|
256
|
+
| Feature | `why` | rich | better-exceptions | stackprinter | Copilot |
|
|
257
|
+
|---|---|---|---|---|---|
|
|
258
|
+
| Works without API key | ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
259
|
+
| AI-powered explanations | ✅ | ❌ | ❌ | ❌ | ✅ |
|
|
260
|
+
| `import` and done | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
261
|
+
| Local model support | ✅ Ollama | — | — | — | ❌ |
|
|
262
|
+
| Zero dependencies | ✅ | ❌ | ❌ | ❌ | — |
|
|
263
|
+
| Variable values in output | ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
264
|
+
| Auto-fix (`butwhy.fix()`) | ✅ | ❌ | ❌ | ❌ | ✅ |
|
|
265
|
+
| Works in CI/CD & servers | ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
266
|
+
| Chinese explanations | ✅ | ❌ | ❌ | ❌ | ✅ |
|
|
267
|
+
| Jupyter magic | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Supported error types (pattern matching)
|
|
272
|
+
|
|
273
|
+
The built-in pattern matcher covers these error types without any API key:
|
|
274
|
+
|
|
275
|
+
- `NameError` — undefined variable
|
|
276
|
+
- `TypeError` — wrong type operations
|
|
277
|
+
- `ValueError` — invalid values (int conversion, unpacking)
|
|
278
|
+
- `IndexError` — list index out of range
|
|
279
|
+
- `KeyError` — missing dictionary key
|
|
280
|
+
- `AttributeError` — wrong attribute/method (including NoneType)
|
|
281
|
+
- `ZeroDivisionError` — division by zero
|
|
282
|
+
- `ImportError` / `ModuleNotFoundError` — missing modules (with install hints)
|
|
283
|
+
- `FileNotFoundError` — file path issues
|
|
284
|
+
- `SyntaxError` — common syntax mistakes
|
|
285
|
+
- `RecursionError` — infinite recursion
|
|
286
|
+
- `UnicodeDecodeError` — encoding issues
|
|
287
|
+
- `IndentationError` / `TabError` — indentation problems
|
|
288
|
+
|
|
289
|
+
More patterns are added regularly. With an AI provider, *all* error types are covered.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Examples
|
|
294
|
+
|
|
295
|
+
See the [`examples/`](examples/) directory for runnable demos:
|
|
296
|
+
|
|
297
|
+
- `basic.py` — common errors with pattern matching
|
|
298
|
+
- `with_ai.py` — AI-powered deep explanations
|
|
299
|
+
- `jupyter_demo.ipynb` — notebook usage
|
|
300
|
+
- `fix_demo.py` — auto-fix workflow
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Contributing
|
|
305
|
+
|
|
306
|
+
Contributions welcome! Especially:
|
|
307
|
+
|
|
308
|
+
- New pattern matchers for error types not yet covered
|
|
309
|
+
- Translations for `BUTWHY_LANGUAGE`
|
|
310
|
+
- Bug reports and edge cases
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
git clone https://github.com/yourname/butwhy.git
|
|
314
|
+
cd butwhy
|
|
315
|
+
pip install -e ".[dev]"
|
|
316
|
+
pytest
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## License
|
|
322
|
+
|
|
323
|
+
MIT — see [LICENSE](LICENSE).
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
<div align="center">
|
|
328
|
+
|
|
329
|
+
**`import butwhy`** — because every error deserves an explanation.
|
|
330
|
+
|
|
331
|
+
</div>
|