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.
Files changed (81) hide show
  1. fishertools/__init__.py +82 -0
  2. fishertools/config/__init__.py +24 -0
  3. fishertools/config/manager.py +247 -0
  4. fishertools/config/models.py +96 -0
  5. fishertools/config/parser.py +265 -0
  6. fishertools/decorators.py +93 -0
  7. fishertools/documentation/__init__.py +38 -0
  8. fishertools/documentation/api.py +242 -0
  9. fishertools/documentation/generator.py +502 -0
  10. fishertools/documentation/models.py +126 -0
  11. fishertools/documentation/visual.py +583 -0
  12. fishertools/errors/__init__.py +29 -0
  13. fishertools/errors/exceptions.py +191 -0
  14. fishertools/errors/explainer.py +303 -0
  15. fishertools/errors/formatters.py +386 -0
  16. fishertools/errors/models.py +228 -0
  17. fishertools/errors/patterns.py +119 -0
  18. fishertools/errors/recovery.py +467 -0
  19. fishertools/examples/__init__.py +22 -0
  20. fishertools/examples/models.py +118 -0
  21. fishertools/examples/repository.py +770 -0
  22. fishertools/helpers.py +116 -0
  23. fishertools/integration.py +451 -0
  24. fishertools/learn/__init__.py +18 -0
  25. fishertools/learn/examples.py +550 -0
  26. fishertools/learn/tips.py +281 -0
  27. fishertools/learning/__init__.py +32 -0
  28. fishertools/learning/core.py +349 -0
  29. fishertools/learning/models.py +112 -0
  30. fishertools/learning/progress.py +314 -0
  31. fishertools/learning/session.py +500 -0
  32. fishertools/learning/tutorial.py +626 -0
  33. fishertools/legacy/__init__.py +76 -0
  34. fishertools/legacy/deprecated.py +261 -0
  35. fishertools/legacy/deprecation.py +149 -0
  36. fishertools/safe/__init__.py +16 -0
  37. fishertools/safe/collections.py +242 -0
  38. fishertools/safe/files.py +240 -0
  39. fishertools/safe/strings.py +15 -0
  40. fishertools/utils.py +57 -0
  41. fishertools-0.2.1.dist-info/METADATA +256 -0
  42. fishertools-0.2.1.dist-info/RECORD +81 -0
  43. fishertools-0.2.1.dist-info/WHEEL +5 -0
  44. fishertools-0.2.1.dist-info/licenses/LICENSE +21 -0
  45. fishertools-0.2.1.dist-info/top_level.txt +2 -0
  46. tests/__init__.py +6 -0
  47. tests/conftest.py +25 -0
  48. tests/test_config/__init__.py +3 -0
  49. tests/test_config/test_basic_config.py +57 -0
  50. tests/test_config/test_config_error_handling.py +287 -0
  51. tests/test_config/test_config_properties.py +435 -0
  52. tests/test_documentation/__init__.py +3 -0
  53. tests/test_documentation/test_documentation_properties.py +253 -0
  54. tests/test_documentation/test_visual_documentation_properties.py +444 -0
  55. tests/test_errors/__init__.py +3 -0
  56. tests/test_errors/test_api.py +301 -0
  57. tests/test_errors/test_error_handling.py +354 -0
  58. tests/test_errors/test_explainer.py +173 -0
  59. tests/test_errors/test_formatters.py +338 -0
  60. tests/test_errors/test_models.py +248 -0
  61. tests/test_errors/test_patterns.py +270 -0
  62. tests/test_examples/__init__.py +3 -0
  63. tests/test_examples/test_example_repository_properties.py +204 -0
  64. tests/test_examples/test_specific_examples.py +303 -0
  65. tests/test_integration.py +298 -0
  66. tests/test_integration_enhancements.py +462 -0
  67. tests/test_learn/__init__.py +3 -0
  68. tests/test_learn/test_examples.py +221 -0
  69. tests/test_learn/test_tips.py +285 -0
  70. tests/test_learning/__init__.py +3 -0
  71. tests/test_learning/test_interactive_learning_properties.py +337 -0
  72. tests/test_learning/test_learning_system_properties.py +194 -0
  73. tests/test_learning/test_progress_tracking_properties.py +279 -0
  74. tests/test_legacy/__init__.py +3 -0
  75. tests/test_legacy/test_backward_compatibility.py +236 -0
  76. tests/test_legacy/test_deprecation_warnings.py +208 -0
  77. tests/test_safe/__init__.py +3 -0
  78. tests/test_safe/test_collections_properties.py +189 -0
  79. tests/test_safe/test_files.py +104 -0
  80. tests/test_structure.py +58 -0
  81. 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)