fishertools 0.2.1__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.
- fishertools/__init__.py +82 -0
- fishertools/config/__init__.py +24 -0
- fishertools/config/manager.py +247 -0
- fishertools/config/models.py +96 -0
- fishertools/config/parser.py +265 -0
- fishertools/decorators.py +93 -0
- fishertools/documentation/__init__.py +38 -0
- fishertools/documentation/api.py +242 -0
- fishertools/documentation/generator.py +502 -0
- fishertools/documentation/models.py +126 -0
- fishertools/documentation/visual.py +583 -0
- fishertools/errors/__init__.py +29 -0
- fishertools/errors/exceptions.py +191 -0
- fishertools/errors/explainer.py +303 -0
- fishertools/errors/formatters.py +386 -0
- fishertools/errors/models.py +228 -0
- fishertools/errors/patterns.py +119 -0
- fishertools/errors/recovery.py +467 -0
- fishertools/examples/__init__.py +22 -0
- fishertools/examples/models.py +118 -0
- fishertools/examples/repository.py +770 -0
- fishertools/helpers.py +116 -0
- fishertools/integration.py +451 -0
- fishertools/learn/__init__.py +18 -0
- fishertools/learn/examples.py +550 -0
- fishertools/learn/tips.py +281 -0
- fishertools/learning/__init__.py +32 -0
- fishertools/learning/core.py +349 -0
- fishertools/learning/models.py +112 -0
- fishertools/learning/progress.py +314 -0
- fishertools/learning/session.py +500 -0
- fishertools/learning/tutorial.py +626 -0
- fishertools/legacy/__init__.py +76 -0
- fishertools/legacy/deprecated.py +261 -0
- fishertools/legacy/deprecation.py +149 -0
- fishertools/safe/__init__.py +16 -0
- fishertools/safe/collections.py +242 -0
- fishertools/safe/files.py +240 -0
- fishertools/safe/strings.py +15 -0
- fishertools/utils.py +57 -0
- fishertools-0.2.1.dist-info/METADATA +256 -0
- fishertools-0.2.1.dist-info/RECORD +81 -0
- fishertools-0.2.1.dist-info/WHEEL +5 -0
- fishertools-0.2.1.dist-info/licenses/LICENSE +21 -0
- fishertools-0.2.1.dist-info/top_level.txt +2 -0
- tests/__init__.py +6 -0
- tests/conftest.py +25 -0
- tests/test_config/__init__.py +3 -0
- tests/test_config/test_basic_config.py +57 -0
- tests/test_config/test_config_error_handling.py +287 -0
- tests/test_config/test_config_properties.py +435 -0
- tests/test_documentation/__init__.py +3 -0
- tests/test_documentation/test_documentation_properties.py +253 -0
- tests/test_documentation/test_visual_documentation_properties.py +444 -0
- tests/test_errors/__init__.py +3 -0
- tests/test_errors/test_api.py +301 -0
- tests/test_errors/test_error_handling.py +354 -0
- tests/test_errors/test_explainer.py +173 -0
- tests/test_errors/test_formatters.py +338 -0
- tests/test_errors/test_models.py +248 -0
- tests/test_errors/test_patterns.py +270 -0
- tests/test_examples/__init__.py +3 -0
- tests/test_examples/test_example_repository_properties.py +204 -0
- tests/test_examples/test_specific_examples.py +303 -0
- tests/test_integration.py +298 -0
- tests/test_integration_enhancements.py +462 -0
- tests/test_learn/__init__.py +3 -0
- tests/test_learn/test_examples.py +221 -0
- tests/test_learn/test_tips.py +285 -0
- tests/test_learning/__init__.py +3 -0
- tests/test_learning/test_interactive_learning_properties.py +337 -0
- tests/test_learning/test_learning_system_properties.py +194 -0
- tests/test_learning/test_progress_tracking_properties.py +279 -0
- tests/test_legacy/__init__.py +3 -0
- tests/test_legacy/test_backward_compatibility.py +236 -0
- tests/test_legacy/test_deprecation_warnings.py +208 -0
- tests/test_safe/__init__.py +3 -0
- tests/test_safe/test_collections_properties.py +189 -0
- tests/test_safe/test_files.py +104 -0
- tests/test_structure.py +58 -0
- tests/test_structure_enhancements.py +115 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Output formatters for error explanations.
|
|
3
|
+
|
|
4
|
+
This module contains formatters for different output types including console
|
|
5
|
+
output with color support and structured formatting.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Dict, Any
|
|
10
|
+
from .models import ErrorExplanation
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Colors:
|
|
14
|
+
"""ANSI color codes for terminal output."""
|
|
15
|
+
|
|
16
|
+
# Text colors
|
|
17
|
+
RED = '\033[91m'
|
|
18
|
+
GREEN = '\033[92m'
|
|
19
|
+
YELLOW = '\033[93m'
|
|
20
|
+
BLUE = '\033[94m'
|
|
21
|
+
MAGENTA = '\033[95m'
|
|
22
|
+
CYAN = '\033[96m'
|
|
23
|
+
WHITE = '\033[97m'
|
|
24
|
+
|
|
25
|
+
# Text styles
|
|
26
|
+
BOLD = '\033[1m'
|
|
27
|
+
DIM = '\033[2m'
|
|
28
|
+
UNDERLINE = '\033[4m'
|
|
29
|
+
|
|
30
|
+
# Reset
|
|
31
|
+
RESET = '\033[0m'
|
|
32
|
+
|
|
33
|
+
# Background colors
|
|
34
|
+
BG_RED = '\033[101m'
|
|
35
|
+
BG_GREEN = '\033[102m'
|
|
36
|
+
BG_YELLOW = '\033[103m'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ConsoleFormatter:
|
|
40
|
+
"""
|
|
41
|
+
Formatter for console output with color support and structured sections.
|
|
42
|
+
|
|
43
|
+
Provides clear, readable formatting for error explanations with optional
|
|
44
|
+
color coding to improve readability for beginners.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, use_colors: bool = True):
|
|
48
|
+
"""
|
|
49
|
+
Initialize formatter with color support option.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
use_colors: Whether to use ANSI color codes in output
|
|
53
|
+
"""
|
|
54
|
+
self.use_colors = use_colors and self._supports_color()
|
|
55
|
+
|
|
56
|
+
def _supports_color(self) -> bool:
|
|
57
|
+
"""
|
|
58
|
+
Check if the current terminal supports color output.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
True if colors are supported, False otherwise
|
|
62
|
+
"""
|
|
63
|
+
# Check if we're in a terminal that supports colors
|
|
64
|
+
if not hasattr(sys.stdout, 'isatty') or not sys.stdout.isatty():
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
# Check for common environment variables that indicate color support
|
|
68
|
+
import os
|
|
69
|
+
term = os.environ.get('TERM', '').lower()
|
|
70
|
+
colorterm = os.environ.get('COLORTERM', '').lower()
|
|
71
|
+
|
|
72
|
+
# Most modern terminals support colors
|
|
73
|
+
if 'color' in term or 'color' in colorterm:
|
|
74
|
+
return True
|
|
75
|
+
if term in ['xterm', 'xterm-256color', 'screen', 'linux']:
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
# Windows Command Prompt and PowerShell support colors in newer versions
|
|
79
|
+
if sys.platform == 'win32':
|
|
80
|
+
return True
|
|
81
|
+
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
def _colorize(self, text: str, color: str) -> str:
|
|
85
|
+
"""
|
|
86
|
+
Apply color to text if colors are enabled.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
text: Text to colorize
|
|
90
|
+
color: ANSI color code
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Colorized text or plain text if colors disabled
|
|
94
|
+
"""
|
|
95
|
+
if self.use_colors:
|
|
96
|
+
return f"{color}{text}{Colors.RESET}"
|
|
97
|
+
return text
|
|
98
|
+
|
|
99
|
+
def _format_section_header(self, title: str) -> str:
|
|
100
|
+
"""
|
|
101
|
+
Format a section header with styling.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
title: Section title
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Formatted section header
|
|
108
|
+
"""
|
|
109
|
+
if self.use_colors:
|
|
110
|
+
return f"\n{Colors.BOLD}{Colors.BLUE}═══ {title} ═══{Colors.RESET}\n"
|
|
111
|
+
else:
|
|
112
|
+
return f"\n=== {title} ===\n"
|
|
113
|
+
|
|
114
|
+
def _format_code_block(self, code: str) -> str:
|
|
115
|
+
"""
|
|
116
|
+
Format code examples with proper indentation and highlighting.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
code: Code to format
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Formatted code block
|
|
123
|
+
"""
|
|
124
|
+
lines = code.strip().split('\n')
|
|
125
|
+
formatted_lines = []
|
|
126
|
+
|
|
127
|
+
for line in lines:
|
|
128
|
+
# Add consistent indentation
|
|
129
|
+
if line.strip():
|
|
130
|
+
formatted_line = f" {line}"
|
|
131
|
+
if self.use_colors:
|
|
132
|
+
# Simple syntax highlighting for Python keywords
|
|
133
|
+
formatted_line = self._highlight_python_syntax(formatted_line)
|
|
134
|
+
formatted_lines.append(formatted_line)
|
|
135
|
+
else:
|
|
136
|
+
formatted_lines.append("")
|
|
137
|
+
|
|
138
|
+
code_block = '\n'.join(formatted_lines)
|
|
139
|
+
|
|
140
|
+
if self.use_colors:
|
|
141
|
+
return f"{Colors.DIM}┌─ Пример кода ─┐{Colors.RESET}\n{code_block}\n{Colors.DIM}└────────────────┘{Colors.RESET}"
|
|
142
|
+
else:
|
|
143
|
+
return f"┌─ Пример кода ─┐\n{code_block}\n└────────────────┘"
|
|
144
|
+
|
|
145
|
+
def _highlight_python_syntax(self, line: str) -> str:
|
|
146
|
+
"""
|
|
147
|
+
Apply basic Python syntax highlighting to a line of code.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
line: Line of code to highlight
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Line with syntax highlighting
|
|
154
|
+
"""
|
|
155
|
+
# Keywords
|
|
156
|
+
keywords = ['def', 'class', 'if', 'else', 'elif', 'for', 'while', 'try', 'except',
|
|
157
|
+
'finally', 'import', 'from', 'as', 'return', 'yield', 'pass', 'break',
|
|
158
|
+
'continue', 'and', 'or', 'not', 'in', 'is', 'True', 'False', 'None']
|
|
159
|
+
|
|
160
|
+
for keyword in keywords:
|
|
161
|
+
# Use word boundaries to avoid partial matches
|
|
162
|
+
import re
|
|
163
|
+
pattern = r'\b' + re.escape(keyword) + r'\b'
|
|
164
|
+
line = re.sub(pattern, f"{Colors.MAGENTA}{keyword}{Colors.RESET}", line)
|
|
165
|
+
|
|
166
|
+
# Strings (simple detection)
|
|
167
|
+
line = re.sub(r'(["\'])([^"\']*)\1', f"{Colors.GREEN}\\1\\2\\1{Colors.RESET}", line)
|
|
168
|
+
|
|
169
|
+
# Comments
|
|
170
|
+
line = re.sub(r'(#.*)', f"{Colors.DIM}\\1{Colors.RESET}", line)
|
|
171
|
+
|
|
172
|
+
return line
|
|
173
|
+
|
|
174
|
+
def _wrap_text(self, text: str, width: int = 70) -> str:
|
|
175
|
+
"""
|
|
176
|
+
Wrap text to specified width while preserving formatting.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
text: Text to wrap
|
|
180
|
+
width: Maximum line width
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Wrapped text
|
|
184
|
+
"""
|
|
185
|
+
import textwrap
|
|
186
|
+
return textwrap.fill(text, width=width, subsequent_indent=" ")
|
|
187
|
+
|
|
188
|
+
def format(self, explanation: ErrorExplanation) -> str:
|
|
189
|
+
"""
|
|
190
|
+
Format error explanation for console output with structured sections.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
explanation: The error explanation to format
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Formatted string for console output with clear sections
|
|
197
|
+
"""
|
|
198
|
+
sections = []
|
|
199
|
+
|
|
200
|
+
# Header with error type
|
|
201
|
+
header = f"🚨 Ошибка Python: {explanation.error_type}"
|
|
202
|
+
sections.append(self._colorize(header, Colors.BOLD + Colors.RED))
|
|
203
|
+
|
|
204
|
+
# Original error section
|
|
205
|
+
if explanation.original_error.strip():
|
|
206
|
+
sections.append(self._format_section_header("Сообщение об ошибке"))
|
|
207
|
+
error_msg = self._colorize(explanation.original_error, Colors.RED)
|
|
208
|
+
sections.append(f" {error_msg}")
|
|
209
|
+
|
|
210
|
+
# Simple explanation section
|
|
211
|
+
sections.append(self._format_section_header("Что это означает"))
|
|
212
|
+
explanation_text = self._wrap_text(explanation.simple_explanation)
|
|
213
|
+
sections.append(f" {explanation_text}")
|
|
214
|
+
|
|
215
|
+
# Fix tip section
|
|
216
|
+
sections.append(self._format_section_header("Как исправить"))
|
|
217
|
+
tip_text = self._wrap_text(explanation.fix_tip)
|
|
218
|
+
tip_formatted = self._colorize(tip_text, Colors.YELLOW)
|
|
219
|
+
sections.append(f" {tip_formatted}")
|
|
220
|
+
|
|
221
|
+
# Code example section
|
|
222
|
+
if explanation.code_example.strip():
|
|
223
|
+
sections.append(self._format_section_header("Пример"))
|
|
224
|
+
code_block = self._format_code_block(explanation.code_example)
|
|
225
|
+
sections.append(code_block)
|
|
226
|
+
|
|
227
|
+
# Additional info section
|
|
228
|
+
if explanation.additional_info and explanation.additional_info.strip():
|
|
229
|
+
sections.append(self._format_section_header("Дополнительная информация"))
|
|
230
|
+
info_text = self._wrap_text(explanation.additional_info)
|
|
231
|
+
info_formatted = self._colorize(info_text, Colors.CYAN)
|
|
232
|
+
sections.append(f" {info_formatted}")
|
|
233
|
+
|
|
234
|
+
# Footer
|
|
235
|
+
footer = "💡 Совет: Внимательно читайте сообщения об ошибках - они содержат важную информацию!"
|
|
236
|
+
sections.append(f"\n{self._colorize(footer, Colors.DIM)}")
|
|
237
|
+
|
|
238
|
+
return '\n'.join(sections)
|
|
239
|
+
|
|
240
|
+
def format_simple(self, explanation: ErrorExplanation) -> str:
|
|
241
|
+
"""
|
|
242
|
+
Format error explanation in a simple, compact format.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
explanation: The error explanation to format
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Simple formatted string
|
|
249
|
+
"""
|
|
250
|
+
parts = []
|
|
251
|
+
|
|
252
|
+
if explanation.original_error.strip():
|
|
253
|
+
parts.append(f"Ошибка: {explanation.original_error}")
|
|
254
|
+
|
|
255
|
+
parts.append(f"Объяснение: {explanation.simple_explanation}")
|
|
256
|
+
parts.append(f"Совет: {explanation.fix_tip}")
|
|
257
|
+
|
|
258
|
+
if explanation.code_example.strip():
|
|
259
|
+
parts.append(f"Пример:\n{explanation.code_example}")
|
|
260
|
+
|
|
261
|
+
return '\n'.join(parts)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class PlainFormatter:
|
|
265
|
+
"""
|
|
266
|
+
Plain text formatter without colors or special formatting.
|
|
267
|
+
|
|
268
|
+
Useful for logging, file output, or environments that don't support colors.
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
def _strip_ansi_codes(self, text: str) -> str:
|
|
272
|
+
"""
|
|
273
|
+
Remove ANSI escape codes from text.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
text: Text that may contain ANSI codes
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Text with ANSI codes removed
|
|
280
|
+
"""
|
|
281
|
+
import re
|
|
282
|
+
# Remove ANSI escape sequences - both complete and incomplete
|
|
283
|
+
# This pattern matches \x1b[ or \033[ followed by any characters until a letter
|
|
284
|
+
ansi_escape = re.compile(r'(\x1b\[|\033\[)[0-9;]*[a-zA-Z]?')
|
|
285
|
+
# Also remove standalone \x1b[ or \033[ sequences
|
|
286
|
+
standalone_escape = re.compile(r'(\x1b\[|\033\[)')
|
|
287
|
+
|
|
288
|
+
# First remove complete sequences, then standalone ones
|
|
289
|
+
text = ansi_escape.sub('', text)
|
|
290
|
+
text = standalone_escape.sub('', text)
|
|
291
|
+
return text
|
|
292
|
+
|
|
293
|
+
def format(self, explanation: ErrorExplanation) -> str:
|
|
294
|
+
"""
|
|
295
|
+
Format error explanation as plain text.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
explanation: The error explanation to format
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Plain text formatted string
|
|
302
|
+
"""
|
|
303
|
+
sections = []
|
|
304
|
+
|
|
305
|
+
sections.append(f"Ошибка Python: {self._strip_ansi_codes(explanation.error_type)}")
|
|
306
|
+
sections.append("=" * 50)
|
|
307
|
+
|
|
308
|
+
if explanation.original_error.strip():
|
|
309
|
+
clean_error = self._strip_ansi_codes(explanation.original_error)
|
|
310
|
+
sections.append(f"\nСообщение об ошибке:\n{clean_error}")
|
|
311
|
+
|
|
312
|
+
clean_explanation = self._strip_ansi_codes(explanation.simple_explanation)
|
|
313
|
+
sections.append(f"\nЧто это означает:\n{clean_explanation}")
|
|
314
|
+
|
|
315
|
+
clean_tip = self._strip_ansi_codes(explanation.fix_tip)
|
|
316
|
+
sections.append(f"\nКак исправить:\n{clean_tip}")
|
|
317
|
+
|
|
318
|
+
if explanation.code_example.strip():
|
|
319
|
+
clean_code = self._strip_ansi_codes(explanation.code_example)
|
|
320
|
+
sections.append(f"\nПример:\n{clean_code}")
|
|
321
|
+
|
|
322
|
+
if explanation.additional_info and explanation.additional_info.strip():
|
|
323
|
+
clean_info = self._strip_ansi_codes(explanation.additional_info)
|
|
324
|
+
sections.append(f"\nДополнительная информация:\n{clean_info}")
|
|
325
|
+
|
|
326
|
+
return '\n'.join(sections)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class JsonFormatter:
|
|
330
|
+
"""
|
|
331
|
+
JSON formatter for structured output.
|
|
332
|
+
|
|
333
|
+
Useful for programmatic processing or integration with other tools.
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
def format(self, explanation: ErrorExplanation) -> str:
|
|
337
|
+
"""
|
|
338
|
+
Format error explanation as JSON.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
explanation: The error explanation to format
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
JSON formatted string
|
|
345
|
+
"""
|
|
346
|
+
return explanation.to_json()
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def get_formatter(format_type: str, **kwargs) -> Any:
|
|
350
|
+
"""
|
|
351
|
+
Factory function to get appropriate formatter.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
format_type: Type of formatter ('console', 'plain', 'json')
|
|
355
|
+
**kwargs: Additional arguments for formatter initialization
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Formatter instance
|
|
359
|
+
|
|
360
|
+
Raises:
|
|
361
|
+
FormattingError: If format_type is not supported or formatter creation fails
|
|
362
|
+
"""
|
|
363
|
+
from .exceptions import FormattingError
|
|
364
|
+
|
|
365
|
+
formatters = {
|
|
366
|
+
'console': ConsoleFormatter,
|
|
367
|
+
'plain': PlainFormatter,
|
|
368
|
+
'json': JsonFormatter
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if format_type not in formatters:
|
|
372
|
+
raise FormattingError(f"Неподдерживаемый тип форматтера: {format_type}. "
|
|
373
|
+
f"Поддерживаемые типы: {list(formatters.keys())}",
|
|
374
|
+
formatter_type=format_type)
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
formatter_class = formatters[format_type]
|
|
378
|
+
|
|
379
|
+
# Only pass kwargs that the formatter accepts
|
|
380
|
+
if format_type == 'console':
|
|
381
|
+
return formatter_class(use_colors=kwargs.get('use_colors', True))
|
|
382
|
+
else:
|
|
383
|
+
return formatter_class()
|
|
384
|
+
except Exception as e:
|
|
385
|
+
raise FormattingError(f"Не удалось создать форматтер типа {format_type}: {e}",
|
|
386
|
+
formatter_type=format_type, original_error=e)
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core data models for the error explanation system.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, asdict
|
|
6
|
+
from typing import List, Optional, Any, Dict
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class ErrorPattern:
|
|
12
|
+
"""
|
|
13
|
+
Model for error explanation patterns.
|
|
14
|
+
|
|
15
|
+
Defines how to match and explain specific types of Python exceptions.
|
|
16
|
+
"""
|
|
17
|
+
error_type: type
|
|
18
|
+
error_keywords: List[str] # Keywords in error message to match
|
|
19
|
+
explanation: str # Simple explanation in Russian
|
|
20
|
+
tip: str # Advice on how to fix
|
|
21
|
+
example: str # Code example
|
|
22
|
+
common_causes: List[str] # Common causes of this error
|
|
23
|
+
|
|
24
|
+
def __post_init__(self):
|
|
25
|
+
"""Validate the pattern after initialization."""
|
|
26
|
+
from .exceptions import PatternError
|
|
27
|
+
|
|
28
|
+
# Allow empty keywords for patterns that match any exception of a type (like KeyError)
|
|
29
|
+
if not self.error_keywords and not (len(self.error_keywords) == 1 and self.error_keywords[0] == ""):
|
|
30
|
+
raise PatternError("error_keywords cannot be empty unless it contains a single empty string")
|
|
31
|
+
if not self.explanation.strip():
|
|
32
|
+
raise PatternError("explanation cannot be empty")
|
|
33
|
+
if not self.tip.strip():
|
|
34
|
+
raise PatternError("tip cannot be empty")
|
|
35
|
+
if not self.example.strip():
|
|
36
|
+
raise PatternError("example cannot be empty")
|
|
37
|
+
|
|
38
|
+
def matches(self, exception: Exception) -> bool:
|
|
39
|
+
"""
|
|
40
|
+
Check if this pattern matches the given exception.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
exception: The exception to check
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
True if pattern matches, False otherwise
|
|
47
|
+
"""
|
|
48
|
+
if not isinstance(exception, self.error_type):
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
# If no keywords specified, match any exception of this type
|
|
52
|
+
if not self.error_keywords or (len(self.error_keywords) == 1 and self.error_keywords[0] == ""):
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
error_message = str(exception).lower()
|
|
56
|
+
return any(keyword.lower() in error_message for keyword in self.error_keywords)
|
|
57
|
+
|
|
58
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
59
|
+
"""
|
|
60
|
+
Convert pattern to dictionary for serialization.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Dictionary representation of the pattern
|
|
64
|
+
"""
|
|
65
|
+
result = asdict(self)
|
|
66
|
+
# Convert type to string for JSON serialization
|
|
67
|
+
result['error_type'] = self.error_type.__name__
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'ErrorPattern':
|
|
72
|
+
"""
|
|
73
|
+
Create ErrorPattern from dictionary.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
data: Dictionary containing pattern data
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
ErrorPattern instance
|
|
80
|
+
"""
|
|
81
|
+
from .exceptions import PatternError
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
# Convert string back to type
|
|
85
|
+
error_type_name = data.pop('error_type')
|
|
86
|
+
error_type = getattr(__builtins__, error_type_name, Exception)
|
|
87
|
+
return cls(error_type=error_type, **data)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
raise PatternError(f"Не удалось создать ErrorPattern из словаря: {e}", original_error=e)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class ErrorExplanation:
|
|
94
|
+
"""
|
|
95
|
+
Model for structured error explanations.
|
|
96
|
+
|
|
97
|
+
Contains all information needed to present a helpful error explanation.
|
|
98
|
+
"""
|
|
99
|
+
original_error: str
|
|
100
|
+
error_type: str
|
|
101
|
+
simple_explanation: str
|
|
102
|
+
fix_tip: str
|
|
103
|
+
code_example: str
|
|
104
|
+
additional_info: Optional[str] = None
|
|
105
|
+
|
|
106
|
+
def __post_init__(self):
|
|
107
|
+
"""Validate the explanation after initialization."""
|
|
108
|
+
from .exceptions import ExplanationError
|
|
109
|
+
|
|
110
|
+
# Allow empty or whitespace-only original_error since exceptions can have empty messages
|
|
111
|
+
if self.original_error is None:
|
|
112
|
+
raise ExplanationError("original_error cannot be None")
|
|
113
|
+
if not self.simple_explanation.strip():
|
|
114
|
+
raise ExplanationError("simple_explanation cannot be empty")
|
|
115
|
+
if not self.fix_tip.strip():
|
|
116
|
+
raise ExplanationError("fix_tip cannot be empty")
|
|
117
|
+
if not self.code_example.strip():
|
|
118
|
+
raise ExplanationError("code_example cannot be empty")
|
|
119
|
+
|
|
120
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
121
|
+
"""
|
|
122
|
+
Convert explanation to dictionary for serialization.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Dictionary representation of the explanation
|
|
126
|
+
"""
|
|
127
|
+
return asdict(self)
|
|
128
|
+
|
|
129
|
+
def to_json(self) -> str:
|
|
130
|
+
"""
|
|
131
|
+
Convert explanation to JSON string.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
JSON representation of the explanation
|
|
135
|
+
"""
|
|
136
|
+
try:
|
|
137
|
+
return json.dumps(self.to_dict(), ensure_ascii=False, indent=2)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
from .exceptions import FormattingError
|
|
140
|
+
raise FormattingError(f"Не удалось преобразовать объяснение в JSON: {e}",
|
|
141
|
+
formatter_type="json", original_error=e)
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'ErrorExplanation':
|
|
145
|
+
"""
|
|
146
|
+
Create ErrorExplanation from dictionary.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
data: Dictionary containing explanation data
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
ErrorExplanation instance
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
return cls(**data)
|
|
156
|
+
except Exception as e:
|
|
157
|
+
from .exceptions import ExplanationError
|
|
158
|
+
raise ExplanationError(f"Не удалось создать ErrorExplanation из словаря: {e}", original_error=e)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@dataclass
|
|
162
|
+
class ExplainerConfig:
|
|
163
|
+
"""
|
|
164
|
+
Configuration for the error explanation system.
|
|
165
|
+
|
|
166
|
+
Controls how errors are explained and formatted.
|
|
167
|
+
"""
|
|
168
|
+
language: str = 'ru'
|
|
169
|
+
format_type: str = 'console'
|
|
170
|
+
show_original_error: bool = True
|
|
171
|
+
show_traceback: bool = False
|
|
172
|
+
use_colors: bool = True
|
|
173
|
+
max_explanation_length: int = 200
|
|
174
|
+
|
|
175
|
+
def __post_init__(self):
|
|
176
|
+
"""Validate the configuration after initialization."""
|
|
177
|
+
from .exceptions import ConfigurationError
|
|
178
|
+
|
|
179
|
+
if self.language not in ['ru', 'en']:
|
|
180
|
+
raise ConfigurationError("language must be 'ru' or 'en'",
|
|
181
|
+
config_field="language", config_value=self.language)
|
|
182
|
+
if self.format_type not in ['console', 'json', 'plain']:
|
|
183
|
+
raise ConfigurationError("format_type must be 'console', 'json', or 'plain'",
|
|
184
|
+
config_field="format_type", config_value=self.format_type)
|
|
185
|
+
if self.max_explanation_length <= 0:
|
|
186
|
+
raise ConfigurationError("max_explanation_length must be positive",
|
|
187
|
+
config_field="max_explanation_length",
|
|
188
|
+
config_value=str(self.max_explanation_length))
|
|
189
|
+
|
|
190
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
191
|
+
"""
|
|
192
|
+
Convert configuration to dictionary.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Dictionary representation of the configuration
|
|
196
|
+
"""
|
|
197
|
+
return asdict(self)
|
|
198
|
+
|
|
199
|
+
def to_json(self) -> str:
|
|
200
|
+
"""
|
|
201
|
+
Convert configuration to JSON string.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
JSON representation of the configuration
|
|
205
|
+
"""
|
|
206
|
+
try:
|
|
207
|
+
return json.dumps(self.to_dict(), ensure_ascii=False, indent=2)
|
|
208
|
+
except Exception as e:
|
|
209
|
+
from .exceptions import FormattingError
|
|
210
|
+
raise FormattingError(f"Не удалось преобразовать конфигурацию в JSON: {e}",
|
|
211
|
+
formatter_type="json", original_error=e)
|
|
212
|
+
|
|
213
|
+
@classmethod
|
|
214
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'ExplainerConfig':
|
|
215
|
+
"""
|
|
216
|
+
Create ExplainerConfig from dictionary.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
data: Dictionary containing configuration data
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
ExplainerConfig instance
|
|
223
|
+
"""
|
|
224
|
+
try:
|
|
225
|
+
return cls(**data)
|
|
226
|
+
except Exception as e:
|
|
227
|
+
from .exceptions import ConfigurationError
|
|
228
|
+
raise ConfigurationError(f"Не удалось создать ExplainerConfig из словаря: {e}", original_error=e)
|