WhyCrash 1.0.0__py3-none-any.whl
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.
- WhyCrash/__init__.py +243 -0
- whycrash-1.0.0.dist-info/METADATA +140 -0
- whycrash-1.0.0.dist-info/RECORD +5 -0
- whycrash-1.0.0.dist-info/WHEEL +5 -0
- whycrash-1.0.0.dist-info/top_level.txt +1 -0
WhyCrash/__init__.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import traceback
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
def debug():
|
|
7
|
+
"""Эту функцию нужно вызвать в начале кода: WhyCrash.debug() (ГЛОБАЛЬНЫЙ ПЕРЕХВАТ)"""
|
|
8
|
+
start_debug()
|
|
9
|
+
|
|
10
|
+
def start_debug():
|
|
11
|
+
"""Включает AI-анализ ошибок для всего последующего кода"""
|
|
12
|
+
if not hasattr(sys, 'ps1'):
|
|
13
|
+
sys.excepthook = _ai_excepthook
|
|
14
|
+
|
|
15
|
+
def end_debug():
|
|
16
|
+
"""Выключает AI-анализ ошибок (возвращает стандартное поведение Python)"""
|
|
17
|
+
if sys.excepthook == _ai_excepthook:
|
|
18
|
+
sys.excepthook = sys.__excepthook__
|
|
19
|
+
|
|
20
|
+
import contextlib
|
|
21
|
+
import functools
|
|
22
|
+
|
|
23
|
+
@contextlib.contextmanager
|
|
24
|
+
def catch_block():
|
|
25
|
+
"""Контекстный менеджер для перехвата ошибок только в определенном блоке кода"""
|
|
26
|
+
try:
|
|
27
|
+
yield
|
|
28
|
+
except Exception:
|
|
29
|
+
_ai_excepthook(*sys.exc_info())
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
def catch_errors(func):
|
|
33
|
+
"""Декоратор для перехвата ошибок только в конкретной функции"""
|
|
34
|
+
@functools.wraps(func)
|
|
35
|
+
def wrapper(*args, **kwargs):
|
|
36
|
+
try:
|
|
37
|
+
return func(*args, **kwargs)
|
|
38
|
+
except Exception:
|
|
39
|
+
_ai_excepthook(*sys.exc_info())
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
return wrapper
|
|
42
|
+
|
|
43
|
+
def _ai_excepthook(exc_type, exc_value, exc_traceback):
|
|
44
|
+
try:
|
|
45
|
+
import requests
|
|
46
|
+
import json
|
|
47
|
+
except ImportError:
|
|
48
|
+
print("Для работы WhyCrash необходимо установить пакет 'requests': pip install requests")
|
|
49
|
+
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
from rich.console import Console
|
|
54
|
+
from rich.markdown import Markdown
|
|
55
|
+
from rich.panel import Panel
|
|
56
|
+
RICH = True
|
|
57
|
+
console = Console()
|
|
58
|
+
except ImportError:
|
|
59
|
+
RICH = False
|
|
60
|
+
|
|
61
|
+
RED = '\033[91m'
|
|
62
|
+
GREEN = '\033[92m'
|
|
63
|
+
YELLOW = '\033[93m'
|
|
64
|
+
CYAN = '\033[96m'
|
|
65
|
+
RESET = '\033[0m'
|
|
66
|
+
|
|
67
|
+
if RICH:
|
|
68
|
+
console.print(Panel("Oops! Произошла ошибка. WhyCrash собирает контекст и анализирует проблему...", border_style="bold red", expand=False))
|
|
69
|
+
else:
|
|
70
|
+
print(f"\n{RED}Oops! Произошла ошибка. WhyCrash собирает контекст и анализирует проблему...{RESET}\n")
|
|
71
|
+
|
|
72
|
+
tb_lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
|
73
|
+
tb_text = "".join(tb_lines)
|
|
74
|
+
|
|
75
|
+
local_files = {}
|
|
76
|
+
deepest_file = None
|
|
77
|
+
deepest_line = 0
|
|
78
|
+
|
|
79
|
+
tb = exc_traceback
|
|
80
|
+
while tb:
|
|
81
|
+
filename = tb.tb_frame.f_code.co_filename
|
|
82
|
+
lineno = tb.tb_lineno
|
|
83
|
+
deepest_file = filename
|
|
84
|
+
deepest_line = lineno
|
|
85
|
+
|
|
86
|
+
if isinstance(filename, str) and os.path.exists(filename):
|
|
87
|
+
if 'site-packages' not in filename and 'lib\\python' not in filename.lower() and 'lib/python' not in filename.lower():
|
|
88
|
+
if filename not in local_files:
|
|
89
|
+
try:
|
|
90
|
+
with open(filename, 'r', encoding='utf-8') as f:
|
|
91
|
+
local_files[filename] = f.read()
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
94
|
+
tb = tb.tb_next
|
|
95
|
+
|
|
96
|
+
context_str = ""
|
|
97
|
+
for fpath, code in local_files.items():
|
|
98
|
+
context_str += f"### Файл: {fpath} ###\n```python\n{code}\n```\n\n"
|
|
99
|
+
|
|
100
|
+
first_prompt = f"""Произошла ошибка в Python приложении.
|
|
101
|
+
Исключение вызвано в файле '{deepest_file}' на строке {deepest_line}.
|
|
102
|
+
|
|
103
|
+
Traceback:
|
|
104
|
+
{tb_text}
|
|
105
|
+
|
|
106
|
+
Исходный код контекстных файлов (только те, что относятся к проекту):
|
|
107
|
+
{context_str}
|
|
108
|
+
|
|
109
|
+
Пожалуйста, проанализируй эту ошибку и детально объясни на русском языке, почему она произошла.
|
|
110
|
+
Пока что НЕ пиши исправленный код, только проанализируй и объясни причину проблемы."""
|
|
111
|
+
|
|
112
|
+
API_KEY = "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"
|
|
113
|
+
|
|
114
|
+
# ========= ПЕРВЫЙ ЗАПРОС К OPENROUTER =========
|
|
115
|
+
messages = [{"role": "user", "content": first_prompt}]
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
response = requests.post(
|
|
119
|
+
url="https://openrouter.ai/api/v1/chat/completions",
|
|
120
|
+
headers={
|
|
121
|
+
"Authorization": f"Bearer {API_KEY}",
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
},
|
|
124
|
+
data=json.dumps({
|
|
125
|
+
"model": "minimax/minimax-m2.5",
|
|
126
|
+
"messages": messages,
|
|
127
|
+
"reasoning": {"enabled": True}
|
|
128
|
+
})
|
|
129
|
+
)
|
|
130
|
+
response.raise_for_status()
|
|
131
|
+
resp_json = response.json()
|
|
132
|
+
assistant_message = resp_json['choices'][0]['message']
|
|
133
|
+
|
|
134
|
+
reasoning = assistant_message.get('reasoning_details') or ""
|
|
135
|
+
content = assistant_message.get('content') or ""
|
|
136
|
+
|
|
137
|
+
if RICH:
|
|
138
|
+
if reasoning:
|
|
139
|
+
console.print(Panel(Markdown(f"**Мысли (Reasoning):**\n\n{reasoning}"), title="AI Обдумывает", border_style="cyan"))
|
|
140
|
+
console.print(Panel(Markdown(content), title="Анализ ошибки", border_style="yellow"))
|
|
141
|
+
else:
|
|
142
|
+
print(f"{CYAN}============== Знания и Анализ (Minimax) =============={RESET}\n")
|
|
143
|
+
if reasoning:
|
|
144
|
+
print(f"{CYAN}--- Размышления ---{RESET}\n{reasoning}\n")
|
|
145
|
+
print(f"{YELLOW}--- Объяснение ---{RESET}\n{content}\n")
|
|
146
|
+
print(f"{CYAN}======================================================={RESET}\n")
|
|
147
|
+
|
|
148
|
+
# Просим у пользователя подтверждение
|
|
149
|
+
try:
|
|
150
|
+
import questionary
|
|
151
|
+
answer = questionary.select(
|
|
152
|
+
"Хотите, чтобы WhyCrash исправил эту ошибку?",
|
|
153
|
+
choices=["Да", "Нет"]
|
|
154
|
+
).ask()
|
|
155
|
+
if answer != "Да":
|
|
156
|
+
print(f"{YELLOW}Отменено. Выходим...{RESET}")
|
|
157
|
+
sys.exit(1)
|
|
158
|
+
except ImportError:
|
|
159
|
+
answer = input(f"{GREEN}Хотите, чтобы WhyCrash исправил эту ошибку? (y/n, enter=yes): {RESET}")
|
|
160
|
+
if answer.strip().lower() not in ('', 'y', 'yes', 'да', 'д'):
|
|
161
|
+
print(f"{YELLOW}Отменено. Выходим...{RESET}")
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
|
|
164
|
+
# ========= ВТОРОЙ ЗАПРОС К OPENROUTER (ПРОДОЛЖАЕМ РАЗМЫШЛЕНИЯ) =========
|
|
165
|
+
if RICH:
|
|
166
|
+
console.print(f"[bold green]Генерируем исправление...[/bold green]")
|
|
167
|
+
else:
|
|
168
|
+
print(f"\n{GREEN}Генерируем исправление...{RESET}")
|
|
169
|
+
|
|
170
|
+
messages.append({
|
|
171
|
+
"role": "assistant",
|
|
172
|
+
"content": content,
|
|
173
|
+
"reasoning_details": reasoning
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
second_prompt = """Напиши ПОЛНЫЙ исправленный код для файла, в котором нужно сделать изменения.
|
|
177
|
+
ВАЖНО: Выведи исправленный код внутри одного блока ```python ... ```.
|
|
178
|
+
Непосредственно перед блоком кода напиши пустой комментарий или строку, указывающую какой файл ты исправляешь, в таком точном формате:
|
|
179
|
+
FILE_TO_FIX: <полный_путь_к_файлу>
|
|
180
|
+
Выводи весь файл целиком, чтобы я мог полностью заменить старый файл."""
|
|
181
|
+
|
|
182
|
+
messages.append({"role": "user", "content": second_prompt})
|
|
183
|
+
|
|
184
|
+
response2 = requests.post(
|
|
185
|
+
url="https://openrouter.ai/api/v1/chat/completions",
|
|
186
|
+
headers={
|
|
187
|
+
"Authorization": f"Bearer {API_KEY}",
|
|
188
|
+
"Content-Type": "application/json",
|
|
189
|
+
},
|
|
190
|
+
data=json.dumps({
|
|
191
|
+
"model": "minimax/minimax-m2.5",
|
|
192
|
+
"messages": messages,
|
|
193
|
+
"reasoning": {"enabled": True}
|
|
194
|
+
})
|
|
195
|
+
)
|
|
196
|
+
response2.raise_for_status()
|
|
197
|
+
resp_json2 = response2.json()
|
|
198
|
+
assistant_message2 = resp_json2['choices'][0]['message']
|
|
199
|
+
|
|
200
|
+
content2 = assistant_message2.get('content') or ""
|
|
201
|
+
|
|
202
|
+
# Парсим ответ
|
|
203
|
+
file_to_fix = deepest_file
|
|
204
|
+
match_file = re.search(r"FILE_TO_FIX:\s*(.*)", content2)
|
|
205
|
+
if match_file:
|
|
206
|
+
file_to_fix = match_file.group(1).strip()
|
|
207
|
+
|
|
208
|
+
parts = content2.split("```python")
|
|
209
|
+
if len(parts) > 1:
|
|
210
|
+
last_block = parts[-1].split("```")[0]
|
|
211
|
+
fixed_code = last_block.strip()
|
|
212
|
+
|
|
213
|
+
if os.path.exists(file_to_fix):
|
|
214
|
+
try:
|
|
215
|
+
with open(file_to_fix, 'w', encoding='utf-8') as f:
|
|
216
|
+
f.write(fixed_code + '\n')
|
|
217
|
+
if RICH:
|
|
218
|
+
console.print(f"[bold green][+] Файл '{file_to_fix}' успешно исправлен! Запустите скрипт заново.[/bold green]")
|
|
219
|
+
else:
|
|
220
|
+
print(f"{GREEN}\n[+] Файл '{file_to_fix}' успешно исправлен! Запустите скрипт заново.{RESET}")
|
|
221
|
+
except Exception as e:
|
|
222
|
+
if RICH:
|
|
223
|
+
console.print(f"[bold red][-] Не удалось записать файл: {e}[/bold red]")
|
|
224
|
+
else:
|
|
225
|
+
print(f"{RED}\n[-] Не удалось записать файл: {e}{RESET}")
|
|
226
|
+
else:
|
|
227
|
+
if RICH:
|
|
228
|
+
console.print(f"[bold red]Не найден файл для исправления: {file_to_fix}[/bold red]")
|
|
229
|
+
else:
|
|
230
|
+
print(f"\n{RED}Не найден файл для исправления: {file_to_fix}{RESET}")
|
|
231
|
+
else:
|
|
232
|
+
if RICH:
|
|
233
|
+
console.print(f"[bold yellow]Код для исправления не найден в ответе.[/bold yellow]")
|
|
234
|
+
else:
|
|
235
|
+
print(f"\n{YELLOW}Код для исправления не найден в ответе.{RESET}")
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
if RICH:
|
|
239
|
+
console.print(f"[bold red]ОШИБКА WhyCrash API: {e}[/bold red]")
|
|
240
|
+
else:
|
|
241
|
+
print(f"{RED}ОШИБКА WhyCrash API: {e}{RESET}")
|
|
242
|
+
print("\nОригинальный Traceback:")
|
|
243
|
+
print(tb_text)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: WhyCrash
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A highly automatic AI error handler and code fixer using OpenRouter and Minimax.
|
|
5
|
+
Home-page: https://github.com/yourusername/WhyCrash
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: requests
|
|
12
|
+
Requires-Dist: rich
|
|
13
|
+
Requires-Dist: questionary
|
|
14
|
+
Dynamic: classifier
|
|
15
|
+
Dynamic: description
|
|
16
|
+
Dynamic: description-content-type
|
|
17
|
+
Dynamic: home-page
|
|
18
|
+
Dynamic: requires-dist
|
|
19
|
+
Dynamic: requires-python
|
|
20
|
+
Dynamic: summary
|
|
21
|
+
|
|
22
|
+
# 🚀 WhyCrash
|
|
23
|
+
**WhyCrash** is a fully automatic AI assistant for error handling in Python. When your code crashes, WhyCrash intercepts the error, analyzes it using neural networks (OpenRouter + Minimax), gathers context from your local project files, and provides the cause along with an **AUTOMATIC CODE FIX**.
|
|
24
|
+
|
|
25
|
+
Did your code crash? The AI will explain why and automatically replace the broken file with the fixed one (if you allow it).
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
## ✨ Main Features
|
|
31
|
+
- 🧠 **Smart Traceback Analysis**: Understands not just the line with the error but also gathers imported local project files.
|
|
32
|
+
- 🛠️ **Auto-Fixing**: Proposes a ready-made fix and can rewrite the target Python files itself.
|
|
33
|
+
- 🎯 **Precise Control**: You decide where to catch errors: in the entire project, in a single function, or in a specific block of code.
|
|
34
|
+
- 🎨 **Beautiful Interface**: Uses the `rich` library for nice windows and terminal formatting.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 📦 Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install WhyCrash
|
|
42
|
+
```
|
|
43
|
+
> *(Requires `requests`, `rich`, and `questionary` — they will install automatically)*
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 🛠️ How to Use
|
|
48
|
+
|
|
49
|
+
You have 4 ways to control which errors WhyCrash should catch. Choose the one that fits best!
|
|
50
|
+
|
|
51
|
+
### 1. Global Intercept (Easiest)
|
|
52
|
+
If you want **any** unhandled error in your program to be analyzed by the AI:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import WhyCrash
|
|
56
|
+
|
|
57
|
+
# Enable error catching for the whole script
|
|
58
|
+
WhyCrash.debug()
|
|
59
|
+
|
|
60
|
+
# If the code crashes below, WhyCrash comes to the rescue!
|
|
61
|
+
print(1 / 0)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 2. Dynamic Toggle (start & end)
|
|
65
|
+
If you have a large block of code and want to turn on smart analysis right before it, and turn it off right after:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import WhyCrash
|
|
69
|
+
|
|
70
|
+
# ... normal code without WhyCrash ...
|
|
71
|
+
|
|
72
|
+
WhyCrash.start_debug() # Turn on the interceptor
|
|
73
|
+
|
|
74
|
+
a = "text"
|
|
75
|
+
b = int(a) # <-- This error will go to the AI!
|
|
76
|
+
|
|
77
|
+
WhyCrash.end_debug() # Turn off the interceptor (returns to standard behavior)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. Decorator for Specific Functions `@catch_errors`
|
|
81
|
+
If you are only concerned about the reliability of a specific function, you can wrap it in a decorator. If the function crashes, WhyCrash will trigger, while system errors outside of it remain untouched.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from WhyCrash import catch_errors
|
|
85
|
+
|
|
86
|
+
@catch_errors
|
|
87
|
+
def my_danger_function():
|
|
88
|
+
# If it breaks here — WhyCrash will trigger
|
|
89
|
+
file = open("no_exist.txt", "r")
|
|
90
|
+
|
|
91
|
+
def normal_function():
|
|
92
|
+
# And if it breaks here — standard Python traceback
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
my_danger_function()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 4. Context Manager `with catch_block()`
|
|
99
|
+
For the most precise control, if you expect a failure in literally 2 specific lines of code:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from WhyCrash import catch_block
|
|
103
|
+
|
|
104
|
+
print("Starting work...")
|
|
105
|
+
text = "100"
|
|
106
|
+
|
|
107
|
+
with catch_block():
|
|
108
|
+
# Only code inside this block is monitored
|
|
109
|
+
number = int(text)
|
|
110
|
+
result = number / 0 # This will trigger an error sent to WhyCrash!
|
|
111
|
+
|
|
112
|
+
print("This code will not execute if there was an error above.")
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 🛑 How to Ignore Error Catching?
|
|
118
|
+
WhyCrash only analyzes **unhandled** exceptions. If you want an error in your code **not** to reach WhyCrash and the script to keep running, simply use a standard `try...except` block:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import WhyCrash
|
|
122
|
+
WhyCrash.debug()
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
int("letter")
|
|
126
|
+
except ValueError:
|
|
127
|
+
print("Error caught, it won't reach WhyCrash. Moving on!")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## ⚙️ Under the Hood
|
|
131
|
+
- **OpenRouter & Minimax** — Responsible for code analysis, "Reasoning," and generating fix files.
|
|
132
|
+
- **Traceback Walking** — The script automatically follows the error chain, finds all your `.py` files involved, reads them, and sends them to the AI as context.
|
|
133
|
+
- **Rich** — Beautiful console UI (colors, panels, Markdown formatting).
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
Made with ❤️ to save developers' nerves!
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
🌍 **Languages:** [Русский](docs/README_ru.md) | [Deutsch](docs/README_de.md)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
WhyCrash/__init__.py,sha256=WkCN9yNCFHlyNfcaY-CBJaXcpPZO7xCSz_sd9PF4WR0,10895
|
|
2
|
+
whycrash-1.0.0.dist-info/METADATA,sha256=q8BqaPT71Cwg3K24gI17mFHQxCuChWLA42oR5-XAEf0,4709
|
|
3
|
+
whycrash-1.0.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
4
|
+
whycrash-1.0.0.dist-info/top_level.txt,sha256=okvyfU74gtCu42DIh6YyGTqz-vJroxd2XX6JHKf9rUs,9
|
|
5
|
+
whycrash-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
WhyCrash
|