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,242 @@
1
+ """
2
+ Safe collection operations for beginners.
3
+
4
+ This module provides safe versions of common collection operations
5
+ that prevent typical mistakes and provide helpful error messages.
6
+ """
7
+
8
+ from typing import Any, Optional, Union, List, Dict, Tuple
9
+
10
+
11
+ def safe_get(collection: Union[List, Tuple, Dict, str], index: Union[int, str], default: Any = None) -> Any:
12
+ """
13
+ Safely get an element from a collection by index or key.
14
+
15
+ Предотвращает ошибки IndexError и KeyError, возвращая значение по умолчанию
16
+ вместо исключения. Подходит для списков, кортежей, словарей и строк.
17
+
18
+ Args:
19
+ collection: Коллекция (список, кортеж, словарь или строка)
20
+ index: Индекс (для списков/кортежей/строк) или ключ (для словарей)
21
+ default: Значение по умолчанию, если элемент не найден
22
+
23
+ Returns:
24
+ Элемент коллекции или значение по умолчанию
25
+
26
+ Raises:
27
+ SafeUtilityError: If collection is None or unsupported type
28
+
29
+ Examples:
30
+ >>> safe_get([1, 2, 3], 1)
31
+ 2
32
+ >>> safe_get([1, 2, 3], 10, "не найдено")
33
+ 'не найдено'
34
+ >>> safe_get({"name": "Иван"}, "name")
35
+ 'Иван'
36
+ >>> safe_get({"name": "Иван"}, "age", 0)
37
+ 0
38
+ """
39
+ from ..errors.exceptions import SafeUtilityError
40
+
41
+ if collection is None:
42
+ raise SafeUtilityError("Коллекция не может быть None. Передайте список, кортеж, словарь или строку.",
43
+ utility_name="safe_get")
44
+
45
+ # Для словарей используем get()
46
+ if isinstance(collection, dict):
47
+ return collection.get(index, default)
48
+
49
+ # Для списков, кортежей и строк проверяем индекс
50
+ if isinstance(collection, (list, tuple, str)):
51
+ if not isinstance(index, int):
52
+ raise SafeUtilityError(f"Для {type(collection).__name__} индекс должен быть числом, получен {type(index).__name__}",
53
+ utility_name="safe_get")
54
+
55
+ if 0 <= index < len(collection):
56
+ return collection[index]
57
+ else:
58
+ return default
59
+
60
+ # Неподдерживаемый тип коллекции
61
+ raise SafeUtilityError(f"Неподдерживаемый тип коллекции: {type(collection).__name__}. "
62
+ f"Поддерживаются: list, tuple, dict, str",
63
+ utility_name="safe_get")
64
+
65
+
66
+ def safe_divide(a: Union[int, float], b: Union[int, float], default: Union[int, float] = 0) -> Union[int, float]:
67
+ """
68
+ Safely divide two numbers with zero division handling.
69
+
70
+ Предотвращает ошибку ZeroDivisionError, возвращая значение по умолчанию
71
+ при делении на ноль.
72
+
73
+ Args:
74
+ a: Делимое (число)
75
+ b: Делитель (число)
76
+ default: Значение по умолчанию при делении на ноль
77
+
78
+ Returns:
79
+ Результат деления или значение по умолчанию
80
+
81
+ Raises:
82
+ SafeUtilityError: If arguments are not numbers
83
+
84
+ Examples:
85
+ >>> safe_divide(10, 2)
86
+ 5.0
87
+ >>> safe_divide(10, 0)
88
+ 0
89
+ >>> safe_divide(10, 0, -1)
90
+ -1
91
+ """
92
+ from ..errors.exceptions import SafeUtilityError
93
+ import math
94
+
95
+ # Проверяем типы входных данных
96
+ if not isinstance(a, (int, float)):
97
+ raise SafeUtilityError(f"Делимое должно быть числом, получен {type(a).__name__}",
98
+ utility_name="safe_divide")
99
+
100
+ if not isinstance(b, (int, float)):
101
+ raise SafeUtilityError(f"Делитель должен быть числом, получен {type(b).__name__}",
102
+ utility_name="safe_divide")
103
+
104
+ if not isinstance(default, (int, float)):
105
+ raise SafeUtilityError(f"Значение по умолчанию должно быть числом, получен {type(default).__name__}",
106
+ utility_name="safe_divide")
107
+
108
+ # Проверяем деление на ноль
109
+ if b == 0:
110
+ return default
111
+
112
+ # Выполняем деление
113
+ result = a / b
114
+
115
+ # Проверяем на бесконечность или NaN
116
+ if math.isinf(result) or math.isnan(result):
117
+ return default
118
+
119
+ return result
120
+
121
+
122
+ def safe_max(collection: Union[List, Tuple], default: Any = None) -> Any:
123
+ """
124
+ Safely find maximum value in a collection.
125
+
126
+ Предотвращает ошибку ValueError при пустой коллекции.
127
+
128
+ Args:
129
+ collection: Коллекция чисел
130
+ default: Значение по умолчанию для пустой коллекции
131
+
132
+ Returns:
133
+ Максимальное значение или значение по умолчанию
134
+
135
+ Raises:
136
+ SafeUtilityError: If collection is not a list or tuple, or elements are not comparable
137
+
138
+ Examples:
139
+ >>> safe_max([1, 5, 3])
140
+ 5
141
+ >>> safe_max([])
142
+ None
143
+ >>> safe_max([], 0)
144
+ 0
145
+ """
146
+ from ..errors.exceptions import SafeUtilityError
147
+
148
+ if not isinstance(collection, (list, tuple)):
149
+ raise SafeUtilityError(f"Коллекция должна быть списком или кортежем, получен {type(collection).__name__}",
150
+ utility_name="safe_max")
151
+
152
+ if len(collection) == 0:
153
+ return default
154
+
155
+ try:
156
+ return max(collection)
157
+ except TypeError as e:
158
+ raise SafeUtilityError(f"Не удалось найти максимум: {str(e)}. "
159
+ f"Убедитесь, что все элементы коллекции сравнимы.",
160
+ utility_name="safe_max", original_error=e)
161
+
162
+
163
+ def safe_min(collection: Union[List, Tuple], default: Any = None) -> Any:
164
+ """
165
+ Safely find minimum value in a collection.
166
+
167
+ Предотвращает ошибку ValueError при пустой коллекции.
168
+
169
+ Args:
170
+ collection: Коллекция чисел
171
+ default: Значение по умолчанию для пустой коллекции
172
+
173
+ Returns:
174
+ Минимальное значение или значение по умолчанию
175
+
176
+ Raises:
177
+ SafeUtilityError: If collection is not a list or tuple, or elements are not comparable
178
+
179
+ Examples:
180
+ >>> safe_min([1, 5, 3])
181
+ 1
182
+ >>> safe_min([])
183
+ None
184
+ >>> safe_min([], 0)
185
+ 0
186
+ """
187
+ from ..errors.exceptions import SafeUtilityError
188
+
189
+ if not isinstance(collection, (list, tuple)):
190
+ raise SafeUtilityError(f"Коллекция должна быть списком или кортежем, получен {type(collection).__name__}",
191
+ utility_name="safe_min")
192
+
193
+ if len(collection) == 0:
194
+ return default
195
+
196
+ try:
197
+ return min(collection)
198
+ except TypeError as e:
199
+ raise SafeUtilityError(f"Не удалось найти минимум: {str(e)}. "
200
+ f"Убедитесь, что все элементы коллекции сравнимы.",
201
+ utility_name="safe_min", original_error=e)
202
+
203
+
204
+ def safe_sum(collection: Union[List, Tuple], default: Union[int, float] = 0) -> Union[int, float]:
205
+ """
206
+ Safely calculate sum of a collection.
207
+
208
+ Предотвращает ошибки при пустой коллекции или несовместимых типах.
209
+
210
+ Args:
211
+ collection: Коллекция чисел
212
+ default: Значение по умолчанию для пустой коллекции
213
+
214
+ Returns:
215
+ Сумма элементов или значение по умолчанию
216
+
217
+ Raises:
218
+ SafeUtilityError: If collection is not a list or tuple, or elements are not numbers
219
+
220
+ Examples:
221
+ >>> safe_sum([1, 2, 3])
222
+ 6
223
+ >>> safe_sum([])
224
+ 0
225
+ >>> safe_sum([], 10)
226
+ 10
227
+ """
228
+ from ..errors.exceptions import SafeUtilityError
229
+
230
+ if not isinstance(collection, (list, tuple)):
231
+ raise SafeUtilityError(f"Коллекция должна быть списком или кортежем, получен {type(collection).__name__}",
232
+ utility_name="safe_sum")
233
+
234
+ if len(collection) == 0:
235
+ return default
236
+
237
+ try:
238
+ return sum(collection)
239
+ except TypeError as e:
240
+ raise SafeUtilityError(f"Не удалось вычислить сумму: {str(e)}. "
241
+ f"Убедитесь, что все элементы коллекции являются числами.",
242
+ utility_name="safe_sum", original_error=e)
@@ -0,0 +1,240 @@
1
+ """
2
+ Safe file operations for beginners.
3
+
4
+ This module provides safe file handling utilities that prevent common
5
+ file-related errors and provide helpful error messages.
6
+ """
7
+
8
+ import os
9
+ from pathlib import Path
10
+ from typing import Union, Optional, List
11
+
12
+
13
+ def safe_read_file(filepath: Union[str, Path], encoding: str = 'utf-8', default: str = '') -> str:
14
+ """
15
+ Safely read a file with comprehensive error handling.
16
+
17
+ Предотвращает ошибки FileNotFoundError, PermissionError и UnicodeDecodeError,
18
+ возвращая значение по умолчанию вместо исключения.
19
+
20
+ Args:
21
+ filepath: Путь к файлу (строка или Path объект)
22
+ encoding: Кодировка файла (по умолчанию utf-8)
23
+ default: Значение по умолчанию при ошибке чтения
24
+
25
+ Returns:
26
+ Содержимое файла или значение по умолчанию
27
+
28
+ Raises:
29
+ SafeUtilityError: If filepath is None or invalid type
30
+
31
+ Examples:
32
+ >>> safe_read_file("example.txt")
33
+ 'содержимое файла'
34
+ >>> safe_read_file("несуществующий.txt", default="файл не найден")
35
+ 'файл не найден'
36
+ """
37
+ from ..errors.exceptions import SafeUtilityError
38
+
39
+ if filepath is None:
40
+ raise SafeUtilityError("Путь к файлу не может быть None", utility_name="safe_read_file")
41
+
42
+ if not isinstance(filepath, (str, Path)):
43
+ raise SafeUtilityError(f"Путь к файлу должен быть строкой или Path объектом, получен {type(filepath).__name__}",
44
+ utility_name="safe_read_file")
45
+
46
+ if not isinstance(encoding, str):
47
+ raise SafeUtilityError(f"Кодировка должна быть строкой, получен {type(encoding).__name__}",
48
+ utility_name="safe_read_file")
49
+
50
+ try:
51
+ with open(filepath, 'r', encoding=encoding) as file:
52
+ return file.read()
53
+ except FileNotFoundError:
54
+ return default
55
+ except PermissionError:
56
+ return default
57
+ except UnicodeDecodeError:
58
+ return default
59
+ except OSError:
60
+ # Covers other OS-related errors
61
+ return default
62
+
63
+
64
+ def safe_write_file(filepath: Union[str, Path], content: str, encoding: str = 'utf-8',
65
+ create_dirs: bool = True) -> bool:
66
+ """
67
+ Safely write content to a file with error handling.
68
+
69
+ Предотвращает ошибки при записи файла и может создавать директории.
70
+
71
+ Args:
72
+ filepath: Путь к файлу
73
+ content: Содержимое для записи
74
+ encoding: Кодировка файла
75
+ create_dirs: Создавать ли директории если они не существуют
76
+
77
+ Returns:
78
+ True если запись успешна, False при ошибке
79
+
80
+ Raises:
81
+ SafeUtilityError: If arguments have invalid types
82
+
83
+ Examples:
84
+ >>> safe_write_file("output.txt", "Hello World")
85
+ True
86
+ >>> safe_write_file("/invalid/path/file.txt", "content", create_dirs=False)
87
+ False
88
+ """
89
+ from ..errors.exceptions import SafeUtilityError
90
+
91
+ if filepath is None:
92
+ raise SafeUtilityError("Путь к файлу не может быть None", utility_name="safe_write_file")
93
+
94
+ if not isinstance(filepath, (str, Path)):
95
+ raise SafeUtilityError(f"Путь к файлу должен быть строкой или Path объектом, получен {type(filepath).__name__}",
96
+ utility_name="safe_write_file")
97
+
98
+ if not isinstance(content, str):
99
+ raise SafeUtilityError(f"Содержимое должно быть строкой, получен {type(content).__name__}",
100
+ utility_name="safe_write_file")
101
+
102
+ if not isinstance(encoding, str):
103
+ raise SafeUtilityError(f"Кодировка должна быть строкой, получен {type(encoding).__name__}",
104
+ utility_name="safe_write_file")
105
+
106
+ try:
107
+ filepath = Path(filepath)
108
+
109
+ # Create directories if requested
110
+ if create_dirs and filepath.parent != filepath:
111
+ filepath.parent.mkdir(parents=True, exist_ok=True)
112
+
113
+ with open(filepath, 'w', encoding=encoding) as file:
114
+ file.write(content)
115
+ return True
116
+ except (PermissionError, OSError, UnicodeEncodeError):
117
+ return False
118
+
119
+
120
+ def safe_file_exists(filepath: Union[str, Path]) -> bool:
121
+ """
122
+ Safely check if a file exists.
123
+
124
+ Предотвращает ошибки при проверке существования файла.
125
+
126
+ Args:
127
+ filepath: Путь к файлу
128
+
129
+ Returns:
130
+ True если файл существует, False иначе
131
+
132
+ Raises:
133
+ SafeUtilityError: If filepath is None or invalid type
134
+
135
+ Examples:
136
+ >>> safe_file_exists("example.txt")
137
+ True
138
+ >>> safe_file_exists("несуществующий.txt")
139
+ False
140
+ """
141
+ from ..errors.exceptions import SafeUtilityError
142
+
143
+ if filepath is None:
144
+ raise SafeUtilityError("Путь к файлу не может быть None", utility_name="safe_file_exists")
145
+
146
+ if not isinstance(filepath, (str, Path)):
147
+ raise SafeUtilityError(f"Путь к файлу должен быть строкой или Path объектом, получен {type(filepath).__name__}",
148
+ utility_name="safe_file_exists")
149
+
150
+ try:
151
+ return Path(filepath).exists() and Path(filepath).is_file()
152
+ except (OSError, ValueError):
153
+ return False
154
+
155
+
156
+ def safe_get_file_size(filepath: Union[str, Path], default: int = 0) -> int:
157
+ """
158
+ Safely get file size in bytes.
159
+
160
+ Предотвращает ошибки при получении размера файла.
161
+
162
+ Args:
163
+ filepath: Путь к файлу
164
+ default: Значение по умолчанию при ошибке
165
+
166
+ Returns:
167
+ Размер файла в байтах или значение по умолчанию
168
+
169
+ Raises:
170
+ SafeUtilityError: If filepath is None or invalid type
171
+
172
+ Examples:
173
+ >>> safe_get_file_size("example.txt")
174
+ 1024
175
+ >>> safe_get_file_size("несуществующий.txt")
176
+ 0
177
+ """
178
+ from ..errors.exceptions import SafeUtilityError
179
+
180
+ if filepath is None:
181
+ raise SafeUtilityError("Путь к файлу не может быть None", utility_name="safe_get_file_size")
182
+
183
+ if not isinstance(filepath, (str, Path)):
184
+ raise SafeUtilityError(f"Путь к файлу должен быть строкой или Path объектом, получен {type(filepath).__name__}",
185
+ utility_name="safe_get_file_size")
186
+
187
+ try:
188
+ return Path(filepath).stat().st_size
189
+ except (OSError, FileNotFoundError):
190
+ return default
191
+
192
+
193
+ def safe_list_files(directory: Union[str, Path], pattern: str = "*", default: Optional[List[str]] = None) -> List[str]:
194
+ """
195
+ Safely list files in a directory.
196
+
197
+ Предотвращает ошибки при чтении содержимого директории.
198
+
199
+ Args:
200
+ directory: Путь к директории
201
+ pattern: Паттерн для фильтрации файлов (например, "*.txt")
202
+ default: Значение по умолчанию при ошибке
203
+
204
+ Returns:
205
+ Список имен файлов или значение по умолчанию
206
+
207
+ Raises:
208
+ SafeUtilityError: If directory is None or invalid type
209
+
210
+ Examples:
211
+ >>> safe_list_files(".")
212
+ ['file1.txt', 'file2.py']
213
+ >>> safe_list_files("несуществующая_папка")
214
+ []
215
+ """
216
+ from ..errors.exceptions import SafeUtilityError
217
+
218
+ if default is None:
219
+ default = []
220
+
221
+ if directory is None:
222
+ raise SafeUtilityError("Путь к директории не может быть None", utility_name="safe_list_files")
223
+
224
+ if not isinstance(directory, (str, Path)):
225
+ raise SafeUtilityError(f"Путь к директории должен быть строкой или Path объектом, получен {type(directory).__name__}",
226
+ utility_name="safe_list_files")
227
+
228
+ if not isinstance(pattern, str):
229
+ raise SafeUtilityError(f"Паттерн должен быть строкой, получен {type(pattern).__name__}",
230
+ utility_name="safe_list_files")
231
+
232
+ try:
233
+ directory_path = Path(directory)
234
+ if not directory_path.exists() or not directory_path.is_dir():
235
+ return default
236
+
237
+ files = [f.name for f in directory_path.glob(pattern) if f.is_file()]
238
+ return sorted(files)
239
+ except (OSError, ValueError):
240
+ return default
@@ -0,0 +1,15 @@
1
+ """
2
+ Safe string operations for beginners.
3
+
4
+ This module will contain safe string handling utilities.
5
+ Implementation will be completed in task 7.
6
+ """
7
+
8
+
9
+ def safe_string_operations():
10
+ """
11
+ Placeholder for safe string operations.
12
+
13
+ Implementation will be completed in task 7.
14
+ """
15
+ pass
fishertools/utils.py ADDED
@@ -0,0 +1,57 @@
1
+ """
2
+ Утилиты для работы с файлами, данными и другими частыми задачами
3
+ """
4
+
5
+ import json
6
+ import os
7
+ import time
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List, Optional
10
+
11
+
12
+ def read_json(filepath: str) -> Dict[str, Any]:
13
+ """Читает JSON файл и возвращает словарь"""
14
+ with open(filepath, 'r', encoding='utf-8') as f:
15
+ return json.load(f)
16
+
17
+
18
+ def write_json(data: Dict[str, Any], filepath: str, indent: int = 2) -> None:
19
+ """Записывает данные в JSON файл"""
20
+ with open(filepath, 'w', encoding='utf-8') as f:
21
+ json.dump(data, f, ensure_ascii=False, indent=indent)
22
+
23
+
24
+ def ensure_dir(path: str) -> None:
25
+ """Создает директорию если она не существует"""
26
+ Path(path).mkdir(parents=True, exist_ok=True)
27
+
28
+
29
+ def get_file_size(filepath: str) -> int:
30
+ """Возвращает размер файла в байтах"""
31
+ return os.path.getsize(filepath)
32
+
33
+
34
+ def list_files(directory: str, extension: Optional[str] = None) -> List[str]:
35
+ """Возвращает список файлов в директории с опциональной фильтрацией по расширению"""
36
+ path = Path(directory)
37
+ if extension:
38
+ pattern = f"*.{extension.lstrip('.')}"
39
+ return [str(f) for f in path.glob(pattern)]
40
+ return [str(f) for f in path.iterdir() if f.is_file()]
41
+
42
+
43
+ def timestamp() -> str:
44
+ """Возвращает текущую временную метку в читаемом формате"""
45
+ return time.strftime("%Y-%m-%d %H:%M:%S")
46
+
47
+
48
+ def flatten_dict(d: Dict[str, Any], parent_key: str = '', sep: str = '.') -> Dict[str, Any]:
49
+ """Превращает вложенный словарь в плоский"""
50
+ items = []
51
+ for k, v in d.items():
52
+ new_key = f"{parent_key}{sep}{k}" if parent_key else k
53
+ if isinstance(v, dict):
54
+ items.extend(flatten_dict(v, new_key, sep=sep).items())
55
+ else:
56
+ items.append((new_key, v))
57
+ return dict(items)