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,119 @@
1
+ """
2
+ Error patterns database.
3
+
4
+ This module contains predefined patterns for common Python exceptions
5
+ with Russian explanations for beginners.
6
+ """
7
+
8
+ from .models import ErrorPattern
9
+
10
+
11
+ # Error patterns for common Python exceptions
12
+ DEFAULT_PATTERNS = [
13
+ # TypeError patterns
14
+ ErrorPattern(
15
+ error_type=TypeError,
16
+ error_keywords=["unsupported operand type", "can't multiply", "can't add", "not supported between"],
17
+ explanation="Вы пытаетесь выполнить операцию между несовместимыми типами данных. Например, нельзя сложить число и строку.",
18
+ tip="Убедитесь, что все операнды имеют совместимые типы. Используйте функции преобразования типов (int(), str(), float()) если нужно.",
19
+ example="# Неправильно:\n# result = 5 + '3' # TypeError\n\n# Правильно:\nresult = 5 + int('3') # 8\n# или\nresult = str(5) + '3' # '53'",
20
+ common_causes=["смешивание чисел и строк", "неправильные типы аргументов функции", "операции с None"]
21
+ ),
22
+
23
+ ErrorPattern(
24
+ error_type=TypeError,
25
+ error_keywords=["takes", "positional argument", "got", "missing", "required positional argument"],
26
+ explanation="Функция вызвана с неправильным количеством аргументов. Либо передано слишком много, либо слишком мало параметров.",
27
+ tip="Проверьте определение функции и убедитесь, что передаете правильное количество аргументов в правильном порядке.",
28
+ example="# Неправильно:\n# def greet(name, age):\n# return f'Привет, {name}! Тебе {age} лет.'\n# greet('Анна') # TypeError - не хватает аргумента\n\n# Правильно:\ndef greet(name, age):\n return f'Привет, {name}! Тебе {age} лет.'\nresult = greet('Анна', 25)",
29
+ common_causes=["забыли передать аргумент", "передали лишний аргумент", "неправильный порядок аргументов"]
30
+ ),
31
+
32
+ ErrorPattern(
33
+ error_type=TypeError,
34
+ error_keywords=["not callable", "object is not callable"],
35
+ explanation="Вы пытаетесь вызвать как функцию объект, который функцией не является. Возможно, забыли скобки или перепутали переменную с функцией.",
36
+ tip="Проверьте, что вызываете именно функцию. Убедитесь, что не перезаписали имя функции переменной.",
37
+ example="# Неправильно:\n# my_list = [1, 2, 3]\n# result = my_list() # TypeError - список не функция\n\n# Правильно:\nmy_list = [1, 2, 3]\nresult = len(my_list) # вызываем функцию len()",
38
+ common_causes=["вызов переменной как функции", "перезапись имени функции", "опечатка в имени функции"]
39
+ ),
40
+
41
+ # ValueError patterns
42
+ ErrorPattern(
43
+ error_type=ValueError,
44
+ error_keywords=["invalid literal", "could not convert", "base"],
45
+ explanation="Не удалось преобразовать строку в число, потому что строка содержит недопустимые символы для числа.",
46
+ tip="Убедитесь, что строка содержит только цифры (и точку для float). Проверьте входные данные перед преобразованием.",
47
+ example="# Неправильно:\n# number = int('abc') # ValueError\n\n# Правильно:\nuser_input = '123'\nif user_input.isdigit():\n number = int(user_input)\nelse:\n print('Введите корректное число')",
48
+ common_causes=["ввод текста вместо числа", "лишние пробелы в строке", "неправильный формат числа"]
49
+ ),
50
+
51
+ ErrorPattern(
52
+ error_type=ValueError,
53
+ error_keywords=["not enough values to unpack", "too many values to unpack"],
54
+ explanation="Количество переменных не соответствует количеству значений при распаковке. Либо переменных больше чем значений, либо наоборот.",
55
+ tip="Убедитесь, что количество переменных слева от знака = равно количеству элементов в последовательности справа.",
56
+ example="# Неправильно:\n# a, b = [1, 2, 3] # слишком много значений\n# x, y, z = [1, 2] # не хватает значений\n\n# Правильно:\na, b, c = [1, 2, 3] # количество совпадает\n# или используйте индексы:\ndata = [1, 2, 3]\na = data[0]\nb = data[1]",
57
+ common_causes=["неправильное количество переменных", "изменился размер списка", "ошибка в логике программы"]
58
+ ),
59
+
60
+ # AttributeError patterns
61
+ ErrorPattern(
62
+ error_type=AttributeError,
63
+ error_keywords=["has no attribute", "object has no attribute"],
64
+ explanation="Вы пытаетесь обратиться к атрибуту или методу, которого не существует у данного объекта.",
65
+ tip="Проверьте правильность написания имени атрибута или метода. Убедитесь, что объект имеет нужный атрибут. Используйте dir() для просмотра доступных атрибутов.",
66
+ example="# Неправильно:\n# my_string = 'привет'\n# my_string.append('!') # у строк нет метода append\n\n# Правильно:\nmy_string = 'привет'\nmy_string = my_string + '!' # конкатенация строк\n# или для списков:\nmy_list = ['привет']\nmy_list.append('!')",
67
+ common_causes=["опечатка в имени метода", "неправильный тип объекта", "объект не инициализирован"]
68
+ ),
69
+
70
+ # IndexError patterns
71
+ ErrorPattern(
72
+ error_type=IndexError,
73
+ error_keywords=["list index out of range", "string index out of range"],
74
+ explanation="Вы пытаетесь обратиться к элементу списка или строки по индексу, который не существует. Индекс слишком большой или отрицательный.",
75
+ tip="Проверьте длину списка/строки с помощью len(). Помните, что индексы начинаются с 0 и заканчиваются на len()-1.",
76
+ example="# Неправильно:\n# my_list = [1, 2, 3]\n# print(my_list[5]) # IndexError - индекс 5 не существует\n\n# Правильно:\nmy_list = [1, 2, 3]\nif len(my_list) > 2:\n print(my_list[2]) # проверяем длину перед обращением\n# или используйте безопасный доступ:\nindex = 2\nif 0 <= index < len(my_list):\n print(my_list[index])",
77
+ common_causes=["неправильный расчет индекса", "пустой список", "цикл выходит за границы"]
78
+ ),
79
+
80
+ # KeyError patterns
81
+ ErrorPattern(
82
+ error_type=KeyError,
83
+ error_keywords=[""], # KeyError messages are often just the key name, so match any KeyError
84
+ explanation="Вы пытаетесь получить значение из словаря по ключу, которого в словаре не существует.",
85
+ tip="Проверьте, существует ли ключ в словаре с помощью 'in' или используйте метод get() с значением по умолчанию.",
86
+ example="# Неправильно:\n# my_dict = {'имя': 'Анна', 'возраст': 25}\n# print(my_dict['город']) # KeyError - ключа 'город' нет\n\n# Правильно:\nmy_dict = {'имя': 'Анна', 'возраст': 25}\n# Способ 1: проверка существования ключа\nif 'город' in my_dict:\n print(my_dict['город'])\nelse:\n print('Город не указан')\n# Способ 2: использование get()\nprint(my_dict.get('город', 'Не указан'))",
87
+ common_causes=["опечатка в имени ключа", "ключ не был добавлен в словарь", "неправильный тип ключа"]
88
+ ),
89
+
90
+ # ImportError patterns
91
+ ErrorPattern(
92
+ error_type=ImportError,
93
+ error_keywords=["No module named", "cannot import name"],
94
+ explanation="Python не может найти модуль или функцию, которую вы пытаетесь импортировать. Возможно, модуль не установлен или имя написано неправильно.",
95
+ tip="Убедитесь, что модуль установлен (pip install имя_модуля) и имя написано правильно. Проверьте, что файл находится в правильной папке.",
96
+ example="# Если модуль не установлен:\n# pip install requests\n\n# Правильный импорт:\nimport os # встроенный модуль\nfrom datetime import datetime # импорт конкретной функции\n\n# Для собственных модулей:\n# убедитесь, что файл my_module.py находится в той же папке\n# import my_module",
97
+ common_causes=["модуль не установлен", "опечатка в имени", "неправильный путь к файлу", "проблемы с виртуальным окружением"]
98
+ ),
99
+
100
+ # SyntaxError patterns
101
+ ErrorPattern(
102
+ error_type=SyntaxError,
103
+ error_keywords=["invalid syntax", "unexpected EOF", "unmatched", "expected"],
104
+ explanation="В коде есть синтаксическая ошибка - нарушены правила написания кода Python. Это может быть незакрытая скобка, неправильный отступ или опечатка.",
105
+ tip="Внимательно проверьте строку, указанную в ошибке. Убедитесь, что все скобки закрыты, отступы правильные, и нет опечаток в ключевых словах.",
106
+ example="# Неправильно:\n# if x > 5 # забыли двоеточие\n# print('больше 5')\n# print('привет' # незакрытая скобка\n\n# Правильно:\nif x > 5: # двоеточие обязательно\n print('больше 5') # правильный отступ\nprint('привет') # закрытая скобка",
107
+ common_causes=["забыли двоеточие после if/for/def", "незакрытые скобки", "неправильные отступы", "опечатки в ключевых словах"]
108
+ )
109
+ ]
110
+
111
+
112
+ def load_default_patterns():
113
+ """
114
+ Load default error patterns for common Python exceptions.
115
+
116
+ Returns:
117
+ List of ErrorPattern objects for common Python exceptions
118
+ """
119
+ return DEFAULT_PATTERNS.copy()
@@ -0,0 +1,467 @@
1
+ """
2
+ Error recovery manager for graceful degradation and error handling.
3
+ """
4
+
5
+ import logging
6
+ from typing import Optional, Dict, Any, List, Callable
7
+ from enum import Enum
8
+ from dataclasses import dataclass
9
+ from .exceptions import FishertoolsError
10
+
11
+
12
+ class ErrorSeverity(Enum):
13
+ """Severity levels for errors."""
14
+ LOW = "low"
15
+ MEDIUM = "medium"
16
+ HIGH = "high"
17
+ CRITICAL = "critical"
18
+
19
+
20
+ class RecoveryStrategy(Enum):
21
+ """Recovery strategies for different error types."""
22
+ RETRY = "retry"
23
+ FALLBACK = "fallback"
24
+ GRACEFUL_DEGRADATION = "graceful_degradation"
25
+ FAIL_FAST = "fail_fast"
26
+ IGNORE = "ignore"
27
+
28
+
29
+ @dataclass
30
+ class ErrorContext:
31
+ """Context information for an error."""
32
+ component: str
33
+ operation: str
34
+ error_type: str
35
+ error_message: str
36
+ severity: ErrorSeverity
37
+ metadata: Dict[str, Any]
38
+
39
+
40
+ @dataclass
41
+ class RecoveryAction:
42
+ """Action to take for error recovery."""
43
+ strategy: RecoveryStrategy
44
+ fallback_function: Optional[Callable] = None
45
+ retry_count: int = 0
46
+ max_retries: int = 3
47
+ message: str = ""
48
+ should_log: bool = True
49
+
50
+
51
+ class ErrorRecoveryManager:
52
+ """
53
+ Manages error recovery strategies and graceful degradation.
54
+
55
+ Provides centralized error handling with configurable recovery
56
+ strategies for different types of errors and components.
57
+ """
58
+
59
+ def __init__(self):
60
+ """Initialize the error recovery manager."""
61
+ self._recovery_strategies: Dict[str, RecoveryAction] = {}
62
+ self._error_handlers: Dict[str, Callable] = {}
63
+ self._fallback_functions: Dict[str, Callable] = {}
64
+ self._error_counts: Dict[str, int] = {}
65
+
66
+ # Set up default recovery strategies
67
+ self._setup_default_strategies()
68
+
69
+ # Set up logging
70
+ self.logger = logging.getLogger(__name__)
71
+
72
+ def _setup_default_strategies(self) -> None:
73
+ """Set up default recovery strategies for common error types."""
74
+
75
+ # Configuration errors - use defaults and continue
76
+ self._recovery_strategies['config_error'] = RecoveryAction(
77
+ strategy=RecoveryStrategy.FALLBACK,
78
+ message="Configuration error detected, using default settings",
79
+ should_log=True
80
+ )
81
+
82
+ # Learning content errors - graceful degradation
83
+ self._recovery_strategies['content_error'] = RecoveryAction(
84
+ strategy=RecoveryStrategy.GRACEFUL_DEGRADATION,
85
+ message="Learning content unavailable, providing basic functionality",
86
+ should_log=True
87
+ )
88
+
89
+ # Documentation generation errors - retry then fallback
90
+ self._recovery_strategies['documentation_error'] = RecoveryAction(
91
+ strategy=RecoveryStrategy.RETRY,
92
+ max_retries=2,
93
+ message="Documentation generation failed, retrying",
94
+ should_log=True
95
+ )
96
+
97
+ # Interactive session errors - graceful degradation
98
+ self._recovery_strategies['session_error'] = RecoveryAction(
99
+ strategy=RecoveryStrategy.GRACEFUL_DEGRADATION,
100
+ message="Interactive session error, continuing with limited functionality",
101
+ should_log=True
102
+ )
103
+
104
+ # Visual documentation errors - ignore and continue
105
+ self._recovery_strategies['visual_error'] = RecoveryAction(
106
+ strategy=RecoveryStrategy.IGNORE,
107
+ message="Visual documentation unavailable, continuing without visuals",
108
+ should_log=True
109
+ )
110
+
111
+ # Critical system errors - fail fast
112
+ self._recovery_strategies['system_error'] = RecoveryAction(
113
+ strategy=RecoveryStrategy.FAIL_FAST,
114
+ message="Critical system error detected",
115
+ should_log=True
116
+ )
117
+
118
+ def handle_error(self, error_context: ErrorContext) -> RecoveryAction:
119
+ """
120
+ Handle an error using the appropriate recovery strategy.
121
+
122
+ Args:
123
+ error_context: Context information about the error
124
+
125
+ Returns:
126
+ RecoveryAction: Action taken for recovery
127
+ """
128
+ error_key = f"{error_context.component}_{error_context.error_type}"
129
+
130
+ # Track error frequency
131
+ self._error_counts[error_key] = self._error_counts.get(error_key, 0) + 1
132
+
133
+ # Get recovery strategy
134
+ strategy = self._get_recovery_strategy(error_context)
135
+
136
+ # Log the error if required
137
+ if strategy.should_log:
138
+ self._log_error(error_context, strategy)
139
+
140
+ # Execute recovery action
141
+ return self._execute_recovery(error_context, strategy)
142
+
143
+ def register_fallback(self, component: str, operation: str, fallback_function: Callable) -> None:
144
+ """
145
+ Register a fallback function for a specific component and operation.
146
+
147
+ Args:
148
+ component: Component name
149
+ operation: Operation name
150
+ fallback_function: Function to call as fallback
151
+ """
152
+ key = f"{component}_{operation}"
153
+ self._fallback_functions[key] = fallback_function
154
+
155
+ def register_error_handler(self, error_type: str, handler: Callable) -> None:
156
+ """
157
+ Register a custom error handler for a specific error type.
158
+
159
+ Args:
160
+ error_type: Type of error to handle
161
+ handler: Handler function
162
+ """
163
+ self._error_handlers[error_type] = handler
164
+
165
+ def set_recovery_strategy(self, error_type: str, strategy: RecoveryAction) -> None:
166
+ """
167
+ Set a custom recovery strategy for an error type.
168
+
169
+ Args:
170
+ error_type: Type of error
171
+ strategy: Recovery strategy to use
172
+ """
173
+ self._recovery_strategies[error_type] = strategy
174
+
175
+ def handle_config_error(self, error: Exception, component: str = "config") -> RecoveryAction:
176
+ """
177
+ Handle configuration-related errors.
178
+
179
+ Args:
180
+ error: Configuration error
181
+ component: Component that had the error
182
+
183
+ Returns:
184
+ RecoveryAction: Recovery action taken
185
+ """
186
+ error_context = ErrorContext(
187
+ component=component,
188
+ operation="load_config",
189
+ error_type="config_error",
190
+ error_message=str(error),
191
+ severity=ErrorSeverity.MEDIUM,
192
+ metadata={"error_class": error.__class__.__name__}
193
+ )
194
+
195
+ return self.handle_error(error_context)
196
+
197
+ def handle_content_error(self, error: Exception, component: str = "learning") -> RecoveryAction:
198
+ """
199
+ Handle learning content-related errors.
200
+
201
+ Args:
202
+ error: Content error
203
+ component: Component that had the error
204
+
205
+ Returns:
206
+ RecoveryAction: Recovery action taken
207
+ """
208
+ error_context = ErrorContext(
209
+ component=component,
210
+ operation="load_content",
211
+ error_type="content_error",
212
+ error_message=str(error),
213
+ severity=ErrorSeverity.LOW,
214
+ metadata={"error_class": error.__class__.__name__}
215
+ )
216
+
217
+ return self.handle_error(error_context)
218
+
219
+ def handle_documentation_error(self, error: Exception, component: str = "documentation") -> RecoveryAction:
220
+ """
221
+ Handle documentation generation errors.
222
+
223
+ Args:
224
+ error: Documentation error
225
+ component: Component that had the error
226
+
227
+ Returns:
228
+ RecoveryAction: Recovery action taken
229
+ """
230
+ error_context = ErrorContext(
231
+ component=component,
232
+ operation="generate_docs",
233
+ error_type="documentation_error",
234
+ error_message=str(error),
235
+ severity=ErrorSeverity.MEDIUM,
236
+ metadata={"error_class": error.__class__.__name__}
237
+ )
238
+
239
+ return self.handle_error(error_context)
240
+
241
+ def handle_session_error(self, error: Exception, component: str = "session") -> RecoveryAction:
242
+ """
243
+ Handle interactive session errors.
244
+
245
+ Args:
246
+ error: Session error
247
+ component: Component that had the error
248
+
249
+ Returns:
250
+ RecoveryAction: Recovery action taken
251
+ """
252
+ error_context = ErrorContext(
253
+ component=component,
254
+ operation="manage_session",
255
+ error_type="session_error",
256
+ error_message=str(error),
257
+ severity=ErrorSeverity.LOW,
258
+ metadata={"error_class": error.__class__.__name__}
259
+ )
260
+
261
+ return self.handle_error(error_context)
262
+
263
+ def handle_visual_error(self, error: Exception, component: str = "visual") -> RecoveryAction:
264
+ """
265
+ Handle visual documentation errors.
266
+
267
+ Args:
268
+ error: Visual documentation error
269
+ component: Component that had the error
270
+
271
+ Returns:
272
+ RecoveryAction: Recovery action taken
273
+ """
274
+ error_context = ErrorContext(
275
+ component=component,
276
+ operation="create_visual",
277
+ error_type="visual_error",
278
+ error_message=str(error),
279
+ severity=ErrorSeverity.LOW,
280
+ metadata={"error_class": error.__class__.__name__}
281
+ )
282
+
283
+ return self.handle_error(error_context)
284
+
285
+ def _get_recovery_strategy(self, error_context: ErrorContext) -> RecoveryAction:
286
+ """Get the appropriate recovery strategy for an error."""
287
+ # Check for specific component + error type strategy
288
+ specific_key = f"{error_context.component}_{error_context.error_type}"
289
+ if specific_key in self._recovery_strategies:
290
+ return self._recovery_strategies[specific_key]
291
+
292
+ # Check for general error type strategy
293
+ if error_context.error_type in self._recovery_strategies:
294
+ return self._recovery_strategies[error_context.error_type]
295
+
296
+ # Default strategy based on severity
297
+ if error_context.severity == ErrorSeverity.CRITICAL:
298
+ return RecoveryAction(
299
+ strategy=RecoveryStrategy.FAIL_FAST,
300
+ message="Critical error - stopping execution"
301
+ )
302
+ elif error_context.severity == ErrorSeverity.HIGH:
303
+ return RecoveryAction(
304
+ strategy=RecoveryStrategy.RETRY,
305
+ max_retries=1,
306
+ message="High severity error - attempting recovery"
307
+ )
308
+ else:
309
+ return RecoveryAction(
310
+ strategy=RecoveryStrategy.GRACEFUL_DEGRADATION,
311
+ message="Error detected - continuing with reduced functionality"
312
+ )
313
+
314
+ def _execute_recovery(self, error_context: ErrorContext, strategy: RecoveryAction) -> RecoveryAction:
315
+ """Execute the recovery strategy."""
316
+ error_key = f"{error_context.component}_{error_context.error_type}"
317
+
318
+ if strategy.strategy == RecoveryStrategy.RETRY:
319
+ # Check if we've exceeded retry limit
320
+ if self._error_counts.get(error_key, 0) > strategy.max_retries:
321
+ # Switch to fallback strategy
322
+ strategy.strategy = RecoveryStrategy.GRACEFUL_DEGRADATION
323
+ strategy.message = f"Max retries exceeded, switching to graceful degradation"
324
+
325
+ elif strategy.strategy == RecoveryStrategy.FALLBACK:
326
+ # Try to find and execute fallback function
327
+ fallback_key = f"{error_context.component}_{error_context.operation}"
328
+ if fallback_key in self._fallback_functions:
329
+ try:
330
+ strategy.fallback_function = self._fallback_functions[fallback_key]
331
+ except Exception as e:
332
+ self.logger.warning(f"Fallback function failed: {e}")
333
+ strategy.strategy = RecoveryStrategy.GRACEFUL_DEGRADATION
334
+
335
+ elif strategy.strategy == RecoveryStrategy.FAIL_FAST:
336
+ # For critical errors, raise the original error
337
+ raise FishertoolsError(f"Critical error in {error_context.component}: {error_context.error_message}")
338
+
339
+ return strategy
340
+
341
+ def _log_error(self, error_context: ErrorContext, strategy: RecoveryAction) -> None:
342
+ """Log error information."""
343
+ log_level = self._get_log_level(error_context.severity)
344
+
345
+ message = (
346
+ f"Error in {error_context.component}.{error_context.operation}: "
347
+ f"{error_context.error_message}. "
348
+ f"Recovery: {strategy.strategy.value}. "
349
+ f"{strategy.message}"
350
+ )
351
+
352
+ self.logger.log(log_level, message, extra={
353
+ 'component': error_context.component,
354
+ 'operation': error_context.operation,
355
+ 'error_type': error_context.error_type,
356
+ 'severity': error_context.severity.value,
357
+ 'recovery_strategy': strategy.strategy.value
358
+ })
359
+
360
+ def _get_log_level(self, severity: ErrorSeverity) -> int:
361
+ """Get appropriate log level for error severity."""
362
+ severity_to_level = {
363
+ ErrorSeverity.LOW: logging.INFO,
364
+ ErrorSeverity.MEDIUM: logging.WARNING,
365
+ ErrorSeverity.HIGH: logging.ERROR,
366
+ ErrorSeverity.CRITICAL: logging.CRITICAL
367
+ }
368
+ return severity_to_level.get(severity, logging.WARNING)
369
+
370
+ def get_error_statistics(self) -> Dict[str, Any]:
371
+ """
372
+ Get statistics about errors and recovery actions.
373
+
374
+ Returns:
375
+ Dict[str, Any]: Error statistics
376
+ """
377
+ total_errors = sum(self._error_counts.values())
378
+
379
+ return {
380
+ 'total_errors': total_errors,
381
+ 'error_counts_by_type': dict(self._error_counts),
382
+ 'registered_fallbacks': len(self._fallback_functions),
383
+ 'registered_handlers': len(self._error_handlers),
384
+ 'recovery_strategies': len(self._recovery_strategies)
385
+ }
386
+
387
+ def reset_error_counts(self) -> None:
388
+ """Reset error count statistics."""
389
+ self._error_counts.clear()
390
+
391
+
392
+ # Global error recovery manager instance
393
+ _recovery_manager: Optional[ErrorRecoveryManager] = None
394
+
395
+
396
+ def get_recovery_manager() -> ErrorRecoveryManager:
397
+ """
398
+ Get or create the global error recovery manager.
399
+
400
+ Returns:
401
+ ErrorRecoveryManager: The global recovery manager instance
402
+ """
403
+ global _recovery_manager
404
+
405
+ if _recovery_manager is None:
406
+ _recovery_manager = ErrorRecoveryManager()
407
+
408
+ return _recovery_manager
409
+
410
+
411
+ def handle_error_with_recovery(error: Exception, component: str, operation: str, error_type: str = "general") -> RecoveryAction:
412
+ """
413
+ Convenience function to handle errors with recovery.
414
+
415
+ Args:
416
+ error: The exception that occurred
417
+ component: Component where error occurred
418
+ operation: Operation that failed
419
+ error_type: Type of error for recovery strategy selection
420
+
421
+ Returns:
422
+ RecoveryAction: Recovery action taken
423
+ """
424
+ recovery_manager = get_recovery_manager()
425
+
426
+ error_context = ErrorContext(
427
+ component=component,
428
+ operation=operation,
429
+ error_type=error_type,
430
+ error_message=str(error),
431
+ severity=ErrorSeverity.MEDIUM,
432
+ metadata={"error_class": error.__class__.__name__}
433
+ )
434
+
435
+ return recovery_manager.handle_error(error_context)
436
+
437
+
438
+ def with_error_recovery(component: str, operation: str, error_type: str = "general"):
439
+ """
440
+ Decorator to add error recovery to functions.
441
+
442
+ Args:
443
+ component: Component name
444
+ operation: Operation name
445
+ error_type: Error type for recovery strategy
446
+ """
447
+ def decorator(func):
448
+ def wrapper(*args, **kwargs):
449
+ try:
450
+ return func(*args, **kwargs)
451
+ except Exception as e:
452
+ recovery_action = handle_error_with_recovery(e, component, operation, error_type)
453
+
454
+ if recovery_action.fallback_function:
455
+ try:
456
+ return recovery_action.fallback_function(*args, **kwargs)
457
+ except Exception as fallback_error:
458
+ logging.warning(f"Fallback function failed: {fallback_error}")
459
+
460
+ if recovery_action.strategy == RecoveryStrategy.FAIL_FAST:
461
+ raise
462
+
463
+ # For other strategies, return None or appropriate default
464
+ return None
465
+
466
+ return wrapper
467
+ return decorator
@@ -0,0 +1,22 @@
1
+ """
2
+ Example Repository Module
3
+
4
+ Manages collections of examples and scenarios for Python beginners.
5
+ Provides categorized examples with step-by-step explanations.
6
+ """
7
+
8
+ from .repository import ExampleRepository
9
+ from .models import (
10
+ CodeExample,
11
+ Scenario,
12
+ ProjectTemplate,
13
+ LineByLineExplanation
14
+ )
15
+
16
+ __all__ = [
17
+ "ExampleRepository",
18
+ "CodeExample",
19
+ "Scenario",
20
+ "ProjectTemplate",
21
+ "LineByLineExplanation"
22
+ ]