fishertools 0.2.1__py3-none-any.whl → 0.4.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.
- fishertools/__init__.py +16 -5
- fishertools/errors/__init__.py +11 -3
- fishertools/errors/exception_types.py +282 -0
- fishertools/errors/explainer.py +87 -1
- fishertools/errors/models.py +73 -1
- fishertools/errors/patterns.py +40 -0
- fishertools/examples/cli_example.py +156 -0
- fishertools/examples/learn_example.py +65 -0
- fishertools/examples/logger_example.py +176 -0
- fishertools/examples/menu_example.py +101 -0
- fishertools/examples/storage_example.py +175 -0
- fishertools/input_utils.py +185 -0
- fishertools/learn/__init__.py +19 -2
- fishertools/learn/examples.py +88 -1
- fishertools/learn/knowledge_engine.py +321 -0
- fishertools/learn/repl/__init__.py +19 -0
- fishertools/learn/repl/cli.py +31 -0
- fishertools/learn/repl/code_sandbox.py +229 -0
- fishertools/learn/repl/command_handler.py +544 -0
- fishertools/learn/repl/command_parser.py +165 -0
- fishertools/learn/repl/engine.py +479 -0
- fishertools/learn/repl/models.py +121 -0
- fishertools/learn/repl/session_manager.py +284 -0
- fishertools/learn/repl/test_code_sandbox.py +261 -0
- fishertools/learn/repl/test_code_sandbox_pbt.py +148 -0
- fishertools/learn/repl/test_command_handler.py +224 -0
- fishertools/learn/repl/test_command_handler_pbt.py +189 -0
- fishertools/learn/repl/test_command_parser.py +160 -0
- fishertools/learn/repl/test_command_parser_pbt.py +100 -0
- fishertools/learn/repl/test_engine.py +190 -0
- fishertools/learn/repl/test_session_manager.py +310 -0
- fishertools/learn/repl/test_session_manager_pbt.py +182 -0
- fishertools/learn/test_knowledge_engine.py +241 -0
- fishertools/learn/test_knowledge_engine_pbt.py +180 -0
- fishertools/patterns/__init__.py +46 -0
- fishertools/patterns/cli.py +175 -0
- fishertools/patterns/logger.py +140 -0
- fishertools/patterns/menu.py +99 -0
- fishertools/patterns/storage.py +127 -0
- fishertools/readme_transformer.py +631 -0
- fishertools/safe/__init__.py +6 -1
- fishertools/safe/files.py +329 -1
- fishertools/transform_readme.py +105 -0
- fishertools-0.4.0.dist-info/METADATA +104 -0
- fishertools-0.4.0.dist-info/RECORD +131 -0
- {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/WHEEL +1 -1
- tests/test_documentation_properties.py +329 -0
- tests/test_documentation_structure.py +349 -0
- tests/test_errors/test_exception_types.py +446 -0
- tests/test_errors/test_exception_types_pbt.py +333 -0
- tests/test_errors/test_patterns.py +52 -0
- tests/test_input_utils/__init__.py +1 -0
- tests/test_input_utils/test_input_utils.py +65 -0
- tests/test_learn/test_examples.py +179 -1
- tests/test_learn/test_explain_properties.py +307 -0
- tests/test_patterns_cli.py +611 -0
- tests/test_patterns_docstrings.py +473 -0
- tests/test_patterns_logger.py +465 -0
- tests/test_patterns_menu.py +440 -0
- tests/test_patterns_storage.py +447 -0
- tests/test_readme_enhancements_v0_3_1.py +2036 -0
- tests/test_readme_transformer/__init__.py +1 -0
- tests/test_readme_transformer/test_readme_infrastructure.py +1023 -0
- tests/test_readme_transformer/test_transform_readme_integration.py +431 -0
- tests/test_safe/test_files.py +726 -1
- fishertools-0.2.1.dist-info/METADATA +0 -256
- fishertools-0.2.1.dist-info/RECORD +0 -81
- {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/top_level.txt +0 -0
fishertools/__init__.py
CHANGED
|
@@ -11,7 +11,7 @@ Fishertools - инструменты, которые делают Python удо
|
|
|
11
11
|
legacy - функции для обратной совместимости
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
__version__ = "0.
|
|
14
|
+
__version__ = "0.3.4"
|
|
15
15
|
__author__ = "f1sherFM"
|
|
16
16
|
|
|
17
17
|
# Primary API - main interface for users
|
|
@@ -19,7 +19,7 @@ from .errors import explain_error
|
|
|
19
19
|
|
|
20
20
|
# Exception classes for error handling
|
|
21
21
|
from .errors import (
|
|
22
|
-
FishertoolsError, ExplanationError, FormattingError,
|
|
22
|
+
FishertoolsError, ExceptionExplanation, ExplanationError, FormattingError,
|
|
23
23
|
ConfigurationError, PatternError, SafeUtilityError
|
|
24
24
|
)
|
|
25
25
|
|
|
@@ -27,7 +27,8 @@ from .errors import (
|
|
|
27
27
|
from .safe import (
|
|
28
28
|
safe_get, safe_divide, safe_max, safe_min, safe_sum,
|
|
29
29
|
safe_read_file, safe_write_file, safe_file_exists,
|
|
30
|
-
safe_get_file_size, safe_list_files
|
|
30
|
+
safe_get_file_size, safe_list_files,
|
|
31
|
+
safe_open, find_file, project_root
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
# Learning tools - educational functions
|
|
@@ -36,6 +37,11 @@ from .learn import (
|
|
|
36
37
|
list_available_concepts, list_available_topics
|
|
37
38
|
)
|
|
38
39
|
|
|
40
|
+
# Input validation functions
|
|
41
|
+
from .input_utils import (
|
|
42
|
+
ask_int, ask_float, ask_str, ask_choice
|
|
43
|
+
)
|
|
44
|
+
|
|
39
45
|
# Legacy imports for backward compatibility
|
|
40
46
|
from . import utils
|
|
41
47
|
from . import decorators
|
|
@@ -46,6 +52,7 @@ from . import errors
|
|
|
46
52
|
from . import safe
|
|
47
53
|
from . import learn
|
|
48
54
|
from . import legacy
|
|
55
|
+
from . import input_utils
|
|
49
56
|
|
|
50
57
|
# New enhancement modules (fishertools-enhancements)
|
|
51
58
|
from . import learning
|
|
@@ -59,13 +66,17 @@ __all__ = [
|
|
|
59
66
|
"explain_error",
|
|
60
67
|
|
|
61
68
|
# Exception classes for error handling
|
|
62
|
-
"FishertoolsError", "ExplanationError", "FormattingError",
|
|
69
|
+
"FishertoolsError", "ExceptionExplanation", "ExplanationError", "FormattingError",
|
|
63
70
|
"ConfigurationError", "PatternError", "SafeUtilityError",
|
|
64
71
|
|
|
65
72
|
# Safe utilities - direct access to commonly used functions
|
|
66
73
|
"safe_get", "safe_divide", "safe_max", "safe_min", "safe_sum",
|
|
67
74
|
"safe_read_file", "safe_write_file", "safe_file_exists",
|
|
68
75
|
"safe_get_file_size", "safe_list_files",
|
|
76
|
+
"safe_open", "find_file", "project_root",
|
|
77
|
+
|
|
78
|
+
# Input validation functions
|
|
79
|
+
"ask_int", "ask_float", "ask_str", "ask_choice",
|
|
69
80
|
|
|
70
81
|
# Learning tools - direct access to educational functions
|
|
71
82
|
"generate_example", "show_best_practice",
|
|
@@ -75,7 +86,7 @@ __all__ = [
|
|
|
75
86
|
"utils", "decorators", "helpers",
|
|
76
87
|
|
|
77
88
|
# New modules for advanced usage
|
|
78
|
-
"errors", "safe", "learn", "legacy",
|
|
89
|
+
"errors", "safe", "learn", "legacy", "input_utils",
|
|
79
90
|
|
|
80
91
|
# Enhancement modules (fishertools-enhancements)
|
|
81
92
|
"learning", "documentation", "examples", "config", "integration"
|
fishertools/errors/__init__.py
CHANGED
|
@@ -8,7 +8,7 @@ for beginners learning Python.
|
|
|
8
8
|
from .explainer import ErrorExplainer, explain_error
|
|
9
9
|
from .patterns import ErrorPattern
|
|
10
10
|
from .formatters import ConsoleFormatter, PlainFormatter, JsonFormatter, get_formatter
|
|
11
|
-
from .models import ErrorExplanation, ExplainerConfig
|
|
11
|
+
from .models import ErrorExplanation, ExplainerConfig, ExceptionExplanation
|
|
12
12
|
from .exceptions import (
|
|
13
13
|
FishertoolsError, ExplanationError, FormattingError,
|
|
14
14
|
ConfigurationError, PatternError, SafeUtilityError
|
|
@@ -17,13 +17,21 @@ from .recovery import (
|
|
|
17
17
|
ErrorRecoveryManager, ErrorSeverity, RecoveryStrategy, ErrorContext, RecoveryAction,
|
|
18
18
|
get_recovery_manager, handle_error_with_recovery, with_error_recovery
|
|
19
19
|
)
|
|
20
|
+
from .exception_types import (
|
|
21
|
+
identify_exception_type, get_exception_type_info, is_supported_exception_type,
|
|
22
|
+
get_exception_type_mapping, get_supported_exception_types, ExceptionTypeInfo,
|
|
23
|
+
EXCEPTION_TYPE_MAPPING
|
|
24
|
+
)
|
|
20
25
|
|
|
21
26
|
__all__ = [
|
|
22
27
|
"ErrorExplainer", "explain_error", "ErrorPattern",
|
|
23
28
|
"ConsoleFormatter", "PlainFormatter", "JsonFormatter", "get_formatter",
|
|
24
|
-
"ErrorExplanation", "ExplainerConfig",
|
|
29
|
+
"ErrorExplanation", "ExceptionExplanation", "ExplainerConfig",
|
|
25
30
|
"FishertoolsError", "ExplanationError", "FormattingError",
|
|
26
31
|
"ConfigurationError", "PatternError", "SafeUtilityError",
|
|
27
32
|
"ErrorRecoveryManager", "ErrorSeverity", "RecoveryStrategy", "ErrorContext", "RecoveryAction",
|
|
28
|
-
"get_recovery_manager", "handle_error_with_recovery", "with_error_recovery"
|
|
33
|
+
"get_recovery_manager", "handle_error_with_recovery", "with_error_recovery",
|
|
34
|
+
"identify_exception_type", "get_exception_type_info", "is_supported_exception_type",
|
|
35
|
+
"get_exception_type_mapping", "get_supported_exception_types", "ExceptionTypeInfo",
|
|
36
|
+
"EXCEPTION_TYPE_MAPPING"
|
|
29
37
|
]
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exception type identification module.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for identifying and mapping Python exception types
|
|
5
|
+
to explanation templates. It supports the following exception types:
|
|
6
|
+
- TypeError
|
|
7
|
+
- ValueError
|
|
8
|
+
- IndexError
|
|
9
|
+
- KeyError
|
|
10
|
+
- AttributeError
|
|
11
|
+
- FileNotFoundError
|
|
12
|
+
- PermissionError
|
|
13
|
+
- ZeroDivisionError
|
|
14
|
+
- NameError
|
|
15
|
+
- And provides fallback for unknown exception types
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Dict, Type, Optional, Callable
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ExceptionTypeInfo:
|
|
24
|
+
"""Information about an exception type."""
|
|
25
|
+
exception_class: Type[Exception]
|
|
26
|
+
name: str
|
|
27
|
+
description: str
|
|
28
|
+
common_causes: list
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Mapping of exception types to their information and explanation templates
|
|
32
|
+
EXCEPTION_TYPE_MAPPING: Dict[Type[Exception], ExceptionTypeInfo] = {
|
|
33
|
+
TypeError: ExceptionTypeInfo(
|
|
34
|
+
exception_class=TypeError,
|
|
35
|
+
name="TypeError",
|
|
36
|
+
description="Type mismatch or incompatible operation",
|
|
37
|
+
common_causes=[
|
|
38
|
+
"Mixing incompatible types (e.g., adding string and number)",
|
|
39
|
+
"Wrong number of function arguments",
|
|
40
|
+
"Calling a non-callable object",
|
|
41
|
+
"Invalid operation for the given type"
|
|
42
|
+
]
|
|
43
|
+
),
|
|
44
|
+
ValueError: ExceptionTypeInfo(
|
|
45
|
+
exception_class=ValueError,
|
|
46
|
+
name="ValueError",
|
|
47
|
+
description="Invalid value for the operation",
|
|
48
|
+
common_causes=[
|
|
49
|
+
"Invalid literal for type conversion",
|
|
50
|
+
"Unpacking wrong number of values",
|
|
51
|
+
"Invalid argument value",
|
|
52
|
+
"String format error"
|
|
53
|
+
]
|
|
54
|
+
),
|
|
55
|
+
IndexError: ExceptionTypeInfo(
|
|
56
|
+
exception_class=IndexError,
|
|
57
|
+
name="IndexError",
|
|
58
|
+
description="Sequence index out of range",
|
|
59
|
+
common_causes=[
|
|
60
|
+
"Accessing list/string index that doesn't exist",
|
|
61
|
+
"Index too large or negative",
|
|
62
|
+
"Empty sequence access",
|
|
63
|
+
"Off-by-one error in loop"
|
|
64
|
+
]
|
|
65
|
+
),
|
|
66
|
+
KeyError: ExceptionTypeInfo(
|
|
67
|
+
exception_class=KeyError,
|
|
68
|
+
name="KeyError",
|
|
69
|
+
description="Dictionary key not found",
|
|
70
|
+
common_causes=[
|
|
71
|
+
"Accessing non-existent dictionary key",
|
|
72
|
+
"Typo in key name",
|
|
73
|
+
"Key not added to dictionary",
|
|
74
|
+
"Wrong key type"
|
|
75
|
+
]
|
|
76
|
+
),
|
|
77
|
+
AttributeError: ExceptionTypeInfo(
|
|
78
|
+
exception_class=AttributeError,
|
|
79
|
+
name="AttributeError",
|
|
80
|
+
description="Attribute or method not found on object",
|
|
81
|
+
common_causes=[
|
|
82
|
+
"Typo in attribute/method name",
|
|
83
|
+
"Wrong object type",
|
|
84
|
+
"Object not initialized",
|
|
85
|
+
"Attribute doesn't exist on this type"
|
|
86
|
+
]
|
|
87
|
+
),
|
|
88
|
+
FileNotFoundError: ExceptionTypeInfo(
|
|
89
|
+
exception_class=FileNotFoundError,
|
|
90
|
+
name="FileNotFoundError",
|
|
91
|
+
description="File or directory not found",
|
|
92
|
+
common_causes=[
|
|
93
|
+
"File path is incorrect",
|
|
94
|
+
"File doesn't exist",
|
|
95
|
+
"Wrong working directory",
|
|
96
|
+
"Typo in filename"
|
|
97
|
+
]
|
|
98
|
+
),
|
|
99
|
+
PermissionError: ExceptionTypeInfo(
|
|
100
|
+
exception_class=PermissionError,
|
|
101
|
+
name="PermissionError",
|
|
102
|
+
description="Permission denied for file operation",
|
|
103
|
+
common_causes=[
|
|
104
|
+
"Insufficient file permissions",
|
|
105
|
+
"File is read-only",
|
|
106
|
+
"Directory is protected",
|
|
107
|
+
"Running without required privileges"
|
|
108
|
+
]
|
|
109
|
+
),
|
|
110
|
+
ZeroDivisionError: ExceptionTypeInfo(
|
|
111
|
+
exception_class=ZeroDivisionError,
|
|
112
|
+
name="ZeroDivisionError",
|
|
113
|
+
description="Division by zero",
|
|
114
|
+
common_causes=[
|
|
115
|
+
"Dividing by zero",
|
|
116
|
+
"Modulo by zero",
|
|
117
|
+
"Denominator is zero",
|
|
118
|
+
"Variable contains zero unexpectedly"
|
|
119
|
+
]
|
|
120
|
+
),
|
|
121
|
+
NameError: ExceptionTypeInfo(
|
|
122
|
+
exception_class=NameError,
|
|
123
|
+
name="NameError",
|
|
124
|
+
description="Name not defined",
|
|
125
|
+
common_causes=[
|
|
126
|
+
"Variable not defined",
|
|
127
|
+
"Typo in variable name",
|
|
128
|
+
"Variable used before definition",
|
|
129
|
+
"Variable out of scope"
|
|
130
|
+
]
|
|
131
|
+
),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def identify_exception_type(exception: Exception) -> str:
|
|
136
|
+
"""
|
|
137
|
+
Identify the type of an exception and return its name.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
exception: The exception object to identify
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
String name of the exception type (e.g., "TypeError", "ValueError")
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
TypeError: If the argument is not an Exception instance
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
>>> try:
|
|
150
|
+
... x = 1 / 0
|
|
151
|
+
... except Exception as e:
|
|
152
|
+
... exc_type = identify_exception_type(e)
|
|
153
|
+
... print(exc_type) # "ZeroDivisionError"
|
|
154
|
+
"""
|
|
155
|
+
if not isinstance(exception, Exception):
|
|
156
|
+
raise TypeError(f"Expected Exception instance, got {type(exception).__name__}")
|
|
157
|
+
|
|
158
|
+
exception_class = type(exception)
|
|
159
|
+
|
|
160
|
+
# Check if it's a known exception type
|
|
161
|
+
if exception_class in EXCEPTION_TYPE_MAPPING:
|
|
162
|
+
return EXCEPTION_TYPE_MAPPING[exception_class].name
|
|
163
|
+
|
|
164
|
+
# Check if it's a subclass of a known exception type
|
|
165
|
+
for known_type, info in EXCEPTION_TYPE_MAPPING.items():
|
|
166
|
+
if isinstance(exception, known_type):
|
|
167
|
+
return info.name
|
|
168
|
+
|
|
169
|
+
# Fallback for unknown exception types
|
|
170
|
+
return exception_class.__name__
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def get_exception_type_info(exception: Exception) -> ExceptionTypeInfo:
|
|
174
|
+
"""
|
|
175
|
+
Get detailed information about an exception type.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
exception: The exception object
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
ExceptionTypeInfo object with details about the exception type,
|
|
182
|
+
or a generic info object for unknown types
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
TypeError: If the argument is not an Exception instance
|
|
186
|
+
|
|
187
|
+
Example:
|
|
188
|
+
>>> try:
|
|
189
|
+
... d = {}
|
|
190
|
+
... value = d['missing_key']
|
|
191
|
+
... except Exception as e:
|
|
192
|
+
... info = get_exception_type_info(e)
|
|
193
|
+
... print(info.description) # "Dictionary key not found"
|
|
194
|
+
"""
|
|
195
|
+
if not isinstance(exception, Exception):
|
|
196
|
+
raise TypeError(f"Expected Exception instance, got {type(exception).__name__}")
|
|
197
|
+
|
|
198
|
+
exception_class = type(exception)
|
|
199
|
+
|
|
200
|
+
# Check if it's a known exception type
|
|
201
|
+
if exception_class in EXCEPTION_TYPE_MAPPING:
|
|
202
|
+
return EXCEPTION_TYPE_MAPPING[exception_class]
|
|
203
|
+
|
|
204
|
+
# Check if it's a subclass of a known exception type
|
|
205
|
+
for known_type, info in EXCEPTION_TYPE_MAPPING.items():
|
|
206
|
+
if isinstance(exception, known_type):
|
|
207
|
+
return info
|
|
208
|
+
|
|
209
|
+
# Fallback for unknown exception types
|
|
210
|
+
return ExceptionTypeInfo(
|
|
211
|
+
exception_class=exception_class,
|
|
212
|
+
name=exception_class.__name__,
|
|
213
|
+
description="An unexpected error occurred",
|
|
214
|
+
common_causes=["Unknown cause - check the error message for details"]
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def is_supported_exception_type(exception: Exception) -> bool:
|
|
219
|
+
"""
|
|
220
|
+
Check if an exception type is explicitly supported.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
exception: The exception object to check
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
True if the exception type is in the supported list, False otherwise
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
TypeError: If the argument is not an Exception instance
|
|
230
|
+
|
|
231
|
+
Example:
|
|
232
|
+
>>> try:
|
|
233
|
+
... x = 1 / 0
|
|
234
|
+
... except Exception as e:
|
|
235
|
+
... if is_supported_exception_type(e):
|
|
236
|
+
... print("This exception type is supported")
|
|
237
|
+
"""
|
|
238
|
+
if not isinstance(exception, Exception):
|
|
239
|
+
raise TypeError(f"Expected Exception instance, got {type(exception).__name__}")
|
|
240
|
+
|
|
241
|
+
exception_class = type(exception)
|
|
242
|
+
|
|
243
|
+
# Check if it's a known exception type
|
|
244
|
+
if exception_class in EXCEPTION_TYPE_MAPPING:
|
|
245
|
+
return True
|
|
246
|
+
|
|
247
|
+
# Check if it's a subclass of a known exception type
|
|
248
|
+
for known_type in EXCEPTION_TYPE_MAPPING.keys():
|
|
249
|
+
if isinstance(exception, known_type):
|
|
250
|
+
return True
|
|
251
|
+
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def get_exception_type_mapping() -> Dict[Type[Exception], ExceptionTypeInfo]:
|
|
256
|
+
"""
|
|
257
|
+
Get the complete exception type mapping.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Dictionary mapping exception classes to their information
|
|
261
|
+
|
|
262
|
+
Example:
|
|
263
|
+
>>> mapping = get_exception_type_mapping()
|
|
264
|
+
>>> for exc_class, info in mapping.items():
|
|
265
|
+
... print(f"{info.name}: {info.description}")
|
|
266
|
+
"""
|
|
267
|
+
return EXCEPTION_TYPE_MAPPING.copy()
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def get_supported_exception_types() -> list:
|
|
271
|
+
"""
|
|
272
|
+
Get a list of all supported exception types.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
List of supported exception type names
|
|
276
|
+
|
|
277
|
+
Example:
|
|
278
|
+
>>> types = get_supported_exception_types()
|
|
279
|
+
>>> print(types)
|
|
280
|
+
['TypeError', 'ValueError', 'IndexError', ...]
|
|
281
|
+
"""
|
|
282
|
+
return [info.name for info in EXCEPTION_TYPE_MAPPING.values()]
|
fishertools/errors/explainer.py
CHANGED
|
@@ -5,7 +5,7 @@ This module contains the ErrorExplainer class and explain_error function.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from typing import Optional, List
|
|
8
|
-
from .models import ErrorExplanation, ExplainerConfig, ErrorPattern
|
|
8
|
+
from .models import ErrorExplanation, ExplainerConfig, ErrorPattern, ExceptionExplanation
|
|
9
9
|
from .patterns import load_default_patterns
|
|
10
10
|
from .exceptions import ExplanationError, FormattingError, ConfigurationError, FishertoolsError
|
|
11
11
|
|
|
@@ -83,6 +83,92 @@ class ErrorExplainer:
|
|
|
83
83
|
# Graceful degradation - create a minimal explanation if all else fails
|
|
84
84
|
return self._create_emergency_explanation(exception, e)
|
|
85
85
|
|
|
86
|
+
def explain_structured(self, exception: Exception) -> ExceptionExplanation:
|
|
87
|
+
"""
|
|
88
|
+
Create a structured explanation for the given exception.
|
|
89
|
+
|
|
90
|
+
This method generates an ExceptionExplanation object with all required fields:
|
|
91
|
+
- exception_type: The type of the exception
|
|
92
|
+
- simple_explanation: Plain-language explanation of what went wrong
|
|
93
|
+
- fix_suggestions: List of ways to fix the problem
|
|
94
|
+
- code_example: Minimal code example showing correct usage
|
|
95
|
+
- traceback_context: Optional traceback information
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
exception: The exception to explain
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
ExceptionExplanation object with structured explanation
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
ExceptionError: If explanation creation fails
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
>>> try:
|
|
108
|
+
... x = 1 / 0
|
|
109
|
+
... except Exception as e:
|
|
110
|
+
... explanation = explainer.explain_structured(e)
|
|
111
|
+
... print(explanation.simple_explanation)
|
|
112
|
+
"""
|
|
113
|
+
if not isinstance(exception, Exception):
|
|
114
|
+
raise ExplanationError(f"Параметр должен быть экземпляром Exception, получен {type(exception).__name__}")
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
# Get the basic explanation
|
|
118
|
+
error_explanation = self.explain(exception)
|
|
119
|
+
|
|
120
|
+
# Convert to structured format
|
|
121
|
+
return ExceptionExplanation(
|
|
122
|
+
exception_type=error_explanation.error_type,
|
|
123
|
+
simple_explanation=error_explanation.simple_explanation,
|
|
124
|
+
fix_suggestions=[error_explanation.fix_tip], # Convert single tip to list
|
|
125
|
+
code_example=error_explanation.code_example,
|
|
126
|
+
traceback_context=error_explanation.additional_info
|
|
127
|
+
)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
if isinstance(e, ExplanationError):
|
|
130
|
+
raise
|
|
131
|
+
# Graceful degradation
|
|
132
|
+
return self._create_emergency_structured_explanation(exception, e)
|
|
133
|
+
|
|
134
|
+
def _create_emergency_structured_explanation(self, exception: Exception,
|
|
135
|
+
original_error: Exception) -> ExceptionExplanation:
|
|
136
|
+
"""
|
|
137
|
+
Create a minimal structured explanation when all other methods fail.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
exception: The original exception to explain
|
|
141
|
+
original_error: The error that prevented normal explanation
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Minimal ExceptionExplanation that should always work
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
error_type = getattr(type(exception), '__name__', 'Unknown')
|
|
148
|
+
error_message = str(exception) if exception else 'Unknown error'
|
|
149
|
+
|
|
150
|
+
return ExceptionExplanation(
|
|
151
|
+
exception_type=error_type,
|
|
152
|
+
simple_explanation="An error occurred in your code. Unfortunately, a detailed explanation could not be generated.",
|
|
153
|
+
fix_suggestions=[
|
|
154
|
+
"Check the error message above and try to find the problem in your code.",
|
|
155
|
+
"Search for information about this error type in Python documentation.",
|
|
156
|
+
"Ask for help if you cannot solve the problem yourself."
|
|
157
|
+
],
|
|
158
|
+
code_example=f"# General error handling:\ntry:\n # your code\n pass\nexcept {error_type} as e:\n print(f'Error: {{e}}')",
|
|
159
|
+
traceback_context=f"Internal fishertools error: {original_error}"
|
|
160
|
+
)
|
|
161
|
+
except Exception:
|
|
162
|
+
# Absolute last resort
|
|
163
|
+
return ExceptionExplanation(
|
|
164
|
+
exception_type="Critical",
|
|
165
|
+
simple_explanation="A critical error occurred in the error explanation system.",
|
|
166
|
+
fix_suggestions=["Contact fishertools developers with a description of the problem."],
|
|
167
|
+
code_example="# Please contact support",
|
|
168
|
+
traceback_context="Critical system error"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
86
172
|
def _match_pattern(self, exception: Exception) -> Optional[ErrorPattern]:
|
|
87
173
|
"""
|
|
88
174
|
Find the best matching pattern for the given exception.
|
fishertools/errors/models.py
CHANGED
|
@@ -225,4 +225,76 @@ class ExplainerConfig:
|
|
|
225
225
|
return cls(**data)
|
|
226
226
|
except Exception as e:
|
|
227
227
|
from .exceptions import ConfigurationError
|
|
228
|
-
raise ConfigurationError(f"Не удалось создать ExplainerConfig из словаря: {e}", original_error=e)
|
|
228
|
+
raise ConfigurationError(f"Не удалось создать ExplainerConfig из словаря: {e}", original_error=e)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@dataclass
|
|
233
|
+
class ExceptionExplanation:
|
|
234
|
+
"""
|
|
235
|
+
Structured explanation of an exception.
|
|
236
|
+
|
|
237
|
+
This dataclass provides a comprehensive explanation of a Python exception,
|
|
238
|
+
including the exception type, a simple explanation, fix suggestions,
|
|
239
|
+
a code example, and optional traceback context.
|
|
240
|
+
"""
|
|
241
|
+
exception_type: str
|
|
242
|
+
simple_explanation: str
|
|
243
|
+
fix_suggestions: List[str]
|
|
244
|
+
code_example: str
|
|
245
|
+
traceback_context: Optional[str] = None
|
|
246
|
+
|
|
247
|
+
def __post_init__(self):
|
|
248
|
+
"""Validate the explanation after initialization."""
|
|
249
|
+
from .exceptions import ExplanationError
|
|
250
|
+
|
|
251
|
+
if not self.exception_type.strip():
|
|
252
|
+
raise ExplanationError("exception_type cannot be empty")
|
|
253
|
+
if not self.simple_explanation.strip():
|
|
254
|
+
raise ExplanationError("simple_explanation cannot be empty")
|
|
255
|
+
if not self.fix_suggestions:
|
|
256
|
+
raise ExplanationError("fix_suggestions cannot be empty")
|
|
257
|
+
if not all(isinstance(s, str) and s.strip() for s in self.fix_suggestions):
|
|
258
|
+
raise ExplanationError("All fix_suggestions must be non-empty strings")
|
|
259
|
+
if not self.code_example.strip():
|
|
260
|
+
raise ExplanationError("code_example cannot be empty")
|
|
261
|
+
|
|
262
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
263
|
+
"""
|
|
264
|
+
Convert explanation to dictionary for serialization.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Dictionary representation of the explanation
|
|
268
|
+
"""
|
|
269
|
+
return asdict(self)
|
|
270
|
+
|
|
271
|
+
def to_json(self) -> str:
|
|
272
|
+
"""
|
|
273
|
+
Convert explanation to JSON string.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
JSON representation of the explanation
|
|
277
|
+
"""
|
|
278
|
+
try:
|
|
279
|
+
return json.dumps(self.to_dict(), ensure_ascii=False, indent=2)
|
|
280
|
+
except Exception as e:
|
|
281
|
+
from .exceptions import FormattingError
|
|
282
|
+
raise FormattingError(f"Could not convert explanation to JSON: {e}",
|
|
283
|
+
formatter_type="json", original_error=e)
|
|
284
|
+
|
|
285
|
+
@classmethod
|
|
286
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'ExceptionExplanation':
|
|
287
|
+
"""
|
|
288
|
+
Create ExceptionExplanation from dictionary.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
data: Dictionary containing explanation data
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
ExceptionExplanation instance
|
|
295
|
+
"""
|
|
296
|
+
try:
|
|
297
|
+
return cls(**data)
|
|
298
|
+
except Exception as e:
|
|
299
|
+
from .exceptions import ExplanationError
|
|
300
|
+
raise ExplanationError(f"Could not create ExceptionExplanation from dictionary: {e}", original_error=e)
|
fishertools/errors/patterns.py
CHANGED
|
@@ -105,6 +105,46 @@ DEFAULT_PATTERNS = [
|
|
|
105
105
|
tip="Внимательно проверьте строку, указанную в ошибке. Убедитесь, что все скобки закрыты, отступы правильные, и нет опечаток в ключевых словах.",
|
|
106
106
|
example="# Неправильно:\n# if x > 5 # забыли двоеточие\n# print('больше 5')\n# print('привет' # незакрытая скобка\n\n# Правильно:\nif x > 5: # двоеточие обязательно\n print('больше 5') # правильный отступ\nprint('привет') # закрытая скобка",
|
|
107
107
|
common_causes=["забыли двоеточие после if/for/def", "незакрытые скобки", "неправильные отступы", "опечатки в ключевых словах"]
|
|
108
|
+
),
|
|
109
|
+
|
|
110
|
+
# FileNotFoundError patterns
|
|
111
|
+
ErrorPattern(
|
|
112
|
+
error_type=FileNotFoundError,
|
|
113
|
+
error_keywords=["No such file or directory", "cannot find the file"],
|
|
114
|
+
explanation="Файл, который вы пытаетесь открыть, не существует. Возможно, неправильно указан путь к файлу или файл находится в другой папке.",
|
|
115
|
+
tip="Проверьте правильность пути к файлу. Убедитесь, что файл существует в указанной папке. Используйте абсолютный путь или проверьте текущую рабочую папку.",
|
|
116
|
+
example="# Неправильно:\n# with open('data.txt', 'r') as f: # файл может не существовать\n# data = f.read()\n\n# Правильно:\nimport os\nfilepath = 'data.txt'\nif os.path.exists(filepath):\n with open(filepath, 'r') as f:\n data = f.read()\nelse:\n print(f'Файл {filepath} не найден')",
|
|
117
|
+
common_causes=["неправильный путь к файлу", "файл не существует", "неправильная рабочая папка", "опечатка в имени файла"]
|
|
118
|
+
),
|
|
119
|
+
|
|
120
|
+
# PermissionError patterns
|
|
121
|
+
ErrorPattern(
|
|
122
|
+
error_type=PermissionError,
|
|
123
|
+
error_keywords=["Permission denied", "access denied"],
|
|
124
|
+
explanation="У вас нет прав доступа для выполнения этой операции с файлом. Это может быть файл, защищенный от записи, или папка, к которой нет доступа.",
|
|
125
|
+
tip="Проверьте права доступа к файлу. Попробуйте запустить программу с правами администратора. Убедитесь, что файл не открыт в другой программе.",
|
|
126
|
+
example="# Неправильно:\n# with open('protected_file.txt', 'w') as f: # может быть защищен\n# f.write('data')\n\n# Правильно:\nimport os\nfilepath = 'protected_file.txt'\nif os.access(filepath, os.W_OK):\n with open(filepath, 'w') as f:\n f.write('data')\nelse:\n print(f'Нет прав доступа к файлу {filepath}')",
|
|
127
|
+
common_causes=["файл защищен от записи", "недостаточно прав доступа", "файл открыт в другой программе", "папка защищена"]
|
|
128
|
+
),
|
|
129
|
+
|
|
130
|
+
# ZeroDivisionError patterns
|
|
131
|
+
ErrorPattern(
|
|
132
|
+
error_type=ZeroDivisionError,
|
|
133
|
+
error_keywords=["division by zero", "integer division or modulo by zero"],
|
|
134
|
+
explanation="Вы пытаетесь разделить число на ноль, что математически невозможно. Это может быть деление или операция модуля (%).",
|
|
135
|
+
tip="Проверьте, что делитель не равен нулю перед выполнением операции деления. Используйте условие if для проверки.",
|
|
136
|
+
example="# Неправильно:\n# result = 10 / 0 # ZeroDivisionError\n\n# Правильно:\ndivisor = 0\nif divisor != 0:\n result = 10 / divisor\nelse:\n print('Нельзя делить на ноль')\n result = None",
|
|
137
|
+
common_causes=["деление на ноль", "модуль по нулю", "переменная содержит ноль неожиданно", "ошибка в расчетах"]
|
|
138
|
+
),
|
|
139
|
+
|
|
140
|
+
# NameError patterns
|
|
141
|
+
ErrorPattern(
|
|
142
|
+
error_type=NameError,
|
|
143
|
+
error_keywords=["is not defined", "name"],
|
|
144
|
+
explanation="Вы используете переменную или функцию, которая не была определена. Возможно, опечатка в имени или переменная определена в другой области видимости.",
|
|
145
|
+
tip="Проверьте правильность написания имени переменной. Убедитесь, что переменная определена перед использованием. Проверьте область видимости переменной.",
|
|
146
|
+
example="# Неправильно:\n# print(my_variable) # NameError - переменная не определена\n\n# Правильно:\nmy_variable = 'Hello' # определяем переменную\nprint(my_variable) # теперь можно использовать\n\n# Или проверяем область видимости:\nif True:\n local_var = 'local'\nprint(local_var) # NameError - переменная локальная",
|
|
147
|
+
common_causes=["опечатка в имени переменной", "переменная не определена", "переменная в другой области видимости", "забыли импортировать модуль"]
|
|
108
148
|
)
|
|
109
149
|
]
|
|
110
150
|
|