fishertools 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fishertools/__init__.py +82 -0
- fishertools/config/__init__.py +24 -0
- fishertools/config/manager.py +247 -0
- fishertools/config/models.py +96 -0
- fishertools/config/parser.py +265 -0
- fishertools/decorators.py +93 -0
- fishertools/documentation/__init__.py +38 -0
- fishertools/documentation/api.py +242 -0
- fishertools/documentation/generator.py +502 -0
- fishertools/documentation/models.py +126 -0
- fishertools/documentation/visual.py +583 -0
- fishertools/errors/__init__.py +29 -0
- fishertools/errors/exceptions.py +191 -0
- fishertools/errors/explainer.py +303 -0
- fishertools/errors/formatters.py +386 -0
- fishertools/errors/models.py +228 -0
- fishertools/errors/patterns.py +119 -0
- fishertools/errors/recovery.py +467 -0
- fishertools/examples/__init__.py +22 -0
- fishertools/examples/models.py +118 -0
- fishertools/examples/repository.py +770 -0
- fishertools/helpers.py +116 -0
- fishertools/integration.py +451 -0
- fishertools/learn/__init__.py +18 -0
- fishertools/learn/examples.py +550 -0
- fishertools/learn/tips.py +281 -0
- fishertools/learning/__init__.py +32 -0
- fishertools/learning/core.py +349 -0
- fishertools/learning/models.py +112 -0
- fishertools/learning/progress.py +314 -0
- fishertools/learning/session.py +500 -0
- fishertools/learning/tutorial.py +626 -0
- fishertools/legacy/__init__.py +76 -0
- fishertools/legacy/deprecated.py +261 -0
- fishertools/legacy/deprecation.py +149 -0
- fishertools/safe/__init__.py +16 -0
- fishertools/safe/collections.py +242 -0
- fishertools/safe/files.py +240 -0
- fishertools/safe/strings.py +15 -0
- fishertools/utils.py +57 -0
- fishertools-0.2.1.dist-info/METADATA +256 -0
- fishertools-0.2.1.dist-info/RECORD +81 -0
- fishertools-0.2.1.dist-info/WHEEL +5 -0
- fishertools-0.2.1.dist-info/licenses/LICENSE +21 -0
- fishertools-0.2.1.dist-info/top_level.txt +2 -0
- tests/__init__.py +6 -0
- tests/conftest.py +25 -0
- tests/test_config/__init__.py +3 -0
- tests/test_config/test_basic_config.py +57 -0
- tests/test_config/test_config_error_handling.py +287 -0
- tests/test_config/test_config_properties.py +435 -0
- tests/test_documentation/__init__.py +3 -0
- tests/test_documentation/test_documentation_properties.py +253 -0
- tests/test_documentation/test_visual_documentation_properties.py +444 -0
- tests/test_errors/__init__.py +3 -0
- tests/test_errors/test_api.py +301 -0
- tests/test_errors/test_error_handling.py +354 -0
- tests/test_errors/test_explainer.py +173 -0
- tests/test_errors/test_formatters.py +338 -0
- tests/test_errors/test_models.py +248 -0
- tests/test_errors/test_patterns.py +270 -0
- tests/test_examples/__init__.py +3 -0
- tests/test_examples/test_example_repository_properties.py +204 -0
- tests/test_examples/test_specific_examples.py +303 -0
- tests/test_integration.py +298 -0
- tests/test_integration_enhancements.py +462 -0
- tests/test_learn/__init__.py +3 -0
- tests/test_learn/test_examples.py +221 -0
- tests/test_learn/test_tips.py +285 -0
- tests/test_learning/__init__.py +3 -0
- tests/test_learning/test_interactive_learning_properties.py +337 -0
- tests/test_learning/test_learning_system_properties.py +194 -0
- tests/test_learning/test_progress_tracking_properties.py +279 -0
- tests/test_legacy/__init__.py +3 -0
- tests/test_legacy/test_backward_compatibility.py +236 -0
- tests/test_legacy/test_deprecation_warnings.py +208 -0
- tests/test_safe/__init__.py +3 -0
- tests/test_safe/test_collections_properties.py +189 -0
- tests/test_safe/test_files.py +104 -0
- tests/test_structure.py +58 -0
- tests/test_structure_enhancements.py +115 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Best practices and tips for learning Python.
|
|
3
|
+
|
|
4
|
+
This module contains functions to show Python best practices
|
|
5
|
+
for common programming concepts that beginners encounter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, List
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Database of best practices for different Python topics
|
|
12
|
+
BEST_PRACTICES: Dict[str, Dict[str, str]] = {
|
|
13
|
+
"variables": {
|
|
14
|
+
"title": "Переменные в Python",
|
|
15
|
+
"practices": """
|
|
16
|
+
🔹 Используйте описательные имена переменных:
|
|
17
|
+
❌ Плохо: x = 25
|
|
18
|
+
✅ Хорошо: age = 25
|
|
19
|
+
|
|
20
|
+
🔹 Используйте snake_case для имен переменных:
|
|
21
|
+
❌ Плохо: firstName = "Иван"
|
|
22
|
+
✅ Хорошо: first_name = "Иван"
|
|
23
|
+
|
|
24
|
+
🔹 Избегайте зарезервированных слов:
|
|
25
|
+
❌ Плохо: list = [1, 2, 3]
|
|
26
|
+
✅ Хорошо: numbers = [1, 2, 3]
|
|
27
|
+
|
|
28
|
+
🔹 Используйте константы для неизменяемых значений:
|
|
29
|
+
✅ MAX_ATTEMPTS = 3
|
|
30
|
+
✅ PI = 3.14159
|
|
31
|
+
""",
|
|
32
|
+
"example": """
|
|
33
|
+
# Хороший пример использования переменных
|
|
34
|
+
user_name = "Анна"
|
|
35
|
+
user_age = 28
|
|
36
|
+
is_active = True
|
|
37
|
+
MAX_LOGIN_ATTEMPTS = 3
|
|
38
|
+
|
|
39
|
+
print(f"Пользователь {user_name}, возраст {user_age}")
|
|
40
|
+
"""
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
"functions": {
|
|
44
|
+
"title": "Функции в Python",
|
|
45
|
+
"practices": """
|
|
46
|
+
🔹 Используйте docstring для документации:
|
|
47
|
+
def calculate_area(radius):
|
|
48
|
+
\"\"\"Вычисляет площадь круга по радиусу.\"\"\"
|
|
49
|
+
return 3.14159 * radius ** 2
|
|
50
|
+
|
|
51
|
+
🔹 Используйте type hints для ясности:
|
|
52
|
+
def greet(name: str) -> str:
|
|
53
|
+
return f"Привет, {name}!"
|
|
54
|
+
|
|
55
|
+
🔹 Функции должны делать одну вещь хорошо:
|
|
56
|
+
❌ Плохо: функция, которая и читает файл, и обрабатывает данные
|
|
57
|
+
✅ Хорошо: отдельные функции для чтения и обработки
|
|
58
|
+
|
|
59
|
+
🔹 Используйте значения по умолчанию разумно:
|
|
60
|
+
def create_user(name: str, role: str = "user"):
|
|
61
|
+
return {"name": name, "role": role}
|
|
62
|
+
""",
|
|
63
|
+
"example": """
|
|
64
|
+
def calculate_discount(price: float, discount_percent: float = 10.0) -> float:
|
|
65
|
+
\"\"\"
|
|
66
|
+
Вычисляет цену со скидкой.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
price: Исходная цена товара
|
|
70
|
+
discount_percent: Процент скидки (по умолчанию 10%)
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Цена со скидкой
|
|
74
|
+
\"\"\"
|
|
75
|
+
if price < 0:
|
|
76
|
+
raise ValueError("Цена не может быть отрицательной")
|
|
77
|
+
|
|
78
|
+
discount_amount = price * (discount_percent / 100)
|
|
79
|
+
return price - discount_amount
|
|
80
|
+
|
|
81
|
+
# Использование
|
|
82
|
+
final_price = calculate_discount(1000.0, 15.0)
|
|
83
|
+
print(f"Цена со скидкой: {final_price}")
|
|
84
|
+
"""
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
"lists": {
|
|
88
|
+
"title": "Работа со списками",
|
|
89
|
+
"practices": """
|
|
90
|
+
🔹 Используйте list comprehensions для простых операций:
|
|
91
|
+
✅ squares = [x**2 for x in range(10)]
|
|
92
|
+
❌ Избегайте сложных comprehensions
|
|
93
|
+
|
|
94
|
+
🔹 Проверяйте границы при доступе к элементам:
|
|
95
|
+
if 0 <= index < len(my_list):
|
|
96
|
+
value = my_list[index]
|
|
97
|
+
|
|
98
|
+
🔹 Используйте enumerate() для индекса и значения:
|
|
99
|
+
for i, item in enumerate(items):
|
|
100
|
+
print(f"{i}: {item}")
|
|
101
|
+
|
|
102
|
+
🔹 Используйте методы списков эффективно:
|
|
103
|
+
✅ items.append(new_item) # Добавить в конец
|
|
104
|
+
✅ items.extend(other_list) # Добавить несколько элементов
|
|
105
|
+
""",
|
|
106
|
+
"example": """
|
|
107
|
+
# Хорошие практики работы со списками
|
|
108
|
+
fruits = ["яблоко", "банан", "апельсин"]
|
|
109
|
+
|
|
110
|
+
# Безопасный доступ к элементам
|
|
111
|
+
def get_fruit(fruits_list: list, index: int) -> str:
|
|
112
|
+
if 0 <= index < len(fruits_list):
|
|
113
|
+
return fruits_list[index]
|
|
114
|
+
return "Фрукт не найден"
|
|
115
|
+
|
|
116
|
+
# Использование enumerate
|
|
117
|
+
print("Список фруктов:")
|
|
118
|
+
for i, fruit in enumerate(fruits, 1):
|
|
119
|
+
print(f"{i}. {fruit}")
|
|
120
|
+
|
|
121
|
+
# List comprehension для преобразования
|
|
122
|
+
uppercase_fruits = [fruit.upper() for fruit in fruits]
|
|
123
|
+
print(f"Заглавными буквами: {uppercase_fruits}")
|
|
124
|
+
"""
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
"dictionaries": {
|
|
128
|
+
"title": "Работа со словарями",
|
|
129
|
+
"practices": """
|
|
130
|
+
🔹 Используйте get() для безопасного доступа:
|
|
131
|
+
✅ value = my_dict.get("key", "default")
|
|
132
|
+
❌ value = my_dict["key"] # Может вызвать KeyError
|
|
133
|
+
|
|
134
|
+
🔹 Проверяйте наличие ключей:
|
|
135
|
+
if "key" in my_dict:
|
|
136
|
+
# Безопасно использовать my_dict["key"]
|
|
137
|
+
|
|
138
|
+
🔹 Используйте items() для итерации:
|
|
139
|
+
for key, value in my_dict.items():
|
|
140
|
+
print(f"{key}: {value}")
|
|
141
|
+
|
|
142
|
+
🔹 Используйте dict comprehensions:
|
|
143
|
+
✅ squares = {x: x**2 for x in range(5)}
|
|
144
|
+
""",
|
|
145
|
+
"example": """
|
|
146
|
+
# Хорошие практики работы со словарями
|
|
147
|
+
student_grades = {
|
|
148
|
+
"Анна": 85,
|
|
149
|
+
"Борис": 92,
|
|
150
|
+
"Вера": 78
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# Безопасный доступ к значениям
|
|
154
|
+
def get_grade(students: dict, name: str) -> str:
|
|
155
|
+
grade = students.get(name)
|
|
156
|
+
if grade is not None:
|
|
157
|
+
return f"Оценка {name}: {grade}"
|
|
158
|
+
return f"Студент {name} не найден"
|
|
159
|
+
|
|
160
|
+
# Итерация по словарю
|
|
161
|
+
print("Все оценки:")
|
|
162
|
+
for student, grade in student_grades.items():
|
|
163
|
+
status = "отлично" if grade >= 90 else "хорошо" if grade >= 80 else "удовлетворительно"
|
|
164
|
+
print(f"{student}: {grade} ({status})")
|
|
165
|
+
|
|
166
|
+
# Добавление нового студента
|
|
167
|
+
student_grades["Григорий"] = 88
|
|
168
|
+
"""
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
"error_handling": {
|
|
172
|
+
"title": "Обработка ошибок",
|
|
173
|
+
"practices": """
|
|
174
|
+
🔹 Используйте конкретные типы исключений:
|
|
175
|
+
❌ except Exception: # Слишком общее
|
|
176
|
+
✅ except ValueError: # Конкретное исключение
|
|
177
|
+
|
|
178
|
+
🔹 Всегда обрабатывайте ошибки осмысленно:
|
|
179
|
+
try:
|
|
180
|
+
result = risky_operation()
|
|
181
|
+
except ValueError as e:
|
|
182
|
+
print(f"Ошибка значения: {e}")
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
🔹 Используйте finally для очистки ресурсов:
|
|
186
|
+
try:
|
|
187
|
+
file = open("data.txt")
|
|
188
|
+
# работа с файлом
|
|
189
|
+
finally:
|
|
190
|
+
file.close()
|
|
191
|
+
|
|
192
|
+
🔹 Или лучше используйте контекстные менеджеры:
|
|
193
|
+
with open("data.txt") as file:
|
|
194
|
+
# файл автоматически закроется
|
|
195
|
+
""",
|
|
196
|
+
"example": """
|
|
197
|
+
def safe_divide(a: float, b: float) -> float:
|
|
198
|
+
\"\"\"
|
|
199
|
+
Безопасное деление с обработкой ошибок.
|
|
200
|
+
\"\"\"
|
|
201
|
+
try:
|
|
202
|
+
if b == 0:
|
|
203
|
+
raise ValueError("Деление на ноль невозможно")
|
|
204
|
+
|
|
205
|
+
result = a / b
|
|
206
|
+
return result
|
|
207
|
+
|
|
208
|
+
except TypeError:
|
|
209
|
+
print("Ошибка: аргументы должны быть числами")
|
|
210
|
+
return 0.0
|
|
211
|
+
except ValueError as e:
|
|
212
|
+
print(f"Ошибка значения: {e}")
|
|
213
|
+
return 0.0
|
|
214
|
+
|
|
215
|
+
# Использование
|
|
216
|
+
print(safe_divide(10, 2)) # 5.0
|
|
217
|
+
print(safe_divide(10, 0)) # Обработка деления на ноль
|
|
218
|
+
print(safe_divide("10", 2)) # Обработка неправильного типа
|
|
219
|
+
"""
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def show_best_practice(topic: str) -> None:
|
|
225
|
+
"""
|
|
226
|
+
Show best practices for a specific Python topic.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
topic: The Python topic to show best practices for.
|
|
230
|
+
Available topics: variables, functions, lists, dictionaries, error_handling
|
|
231
|
+
|
|
232
|
+
Displays formatted best practices with examples to the console.
|
|
233
|
+
"""
|
|
234
|
+
topic_lower = topic.lower().strip()
|
|
235
|
+
|
|
236
|
+
if topic_lower not in BEST_PRACTICES:
|
|
237
|
+
available_topics = ", ".join(BEST_PRACTICES.keys())
|
|
238
|
+
print(f"❌ Тема '{topic}' не найдена.")
|
|
239
|
+
print(f"📚 Доступные темы: {available_topics}")
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
practice_data = BEST_PRACTICES[topic_lower]
|
|
243
|
+
|
|
244
|
+
print("=" * 60)
|
|
245
|
+
print(f"📖 {practice_data['title']}")
|
|
246
|
+
print("=" * 60)
|
|
247
|
+
print()
|
|
248
|
+
print("🎯 ЛУЧШИЕ ПРАКТИКИ:")
|
|
249
|
+
print(practice_data['practices'])
|
|
250
|
+
print()
|
|
251
|
+
print("💡 ПРИМЕР КОДА:")
|
|
252
|
+
print(practice_data['example'])
|
|
253
|
+
print("=" * 60)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def list_available_topics() -> List[str]:
|
|
257
|
+
"""
|
|
258
|
+
Get a list of all available best practice topics.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
List of available topic names
|
|
262
|
+
"""
|
|
263
|
+
return list(BEST_PRACTICES.keys())
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def get_topic_summary(topic: str) -> str:
|
|
267
|
+
"""
|
|
268
|
+
Get a brief summary of a best practice topic.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
topic: The topic to get summary for
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Brief summary string or error message
|
|
275
|
+
"""
|
|
276
|
+
topic_lower = topic.lower().strip()
|
|
277
|
+
|
|
278
|
+
if topic_lower not in BEST_PRACTICES:
|
|
279
|
+
return f"Тема '{topic}' не найдена"
|
|
280
|
+
|
|
281
|
+
return BEST_PRACTICES[topic_lower]['title']
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Learning System Module
|
|
3
|
+
|
|
4
|
+
Provides comprehensive learning tools for Python beginners using fishertools.
|
|
5
|
+
Includes tutorial engine, progress tracking, and interactive learning sessions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .core import LearningSystem
|
|
9
|
+
from .tutorial import TutorialEngine
|
|
10
|
+
from .progress import ProgressSystem
|
|
11
|
+
from .session import InteractiveSessionManager
|
|
12
|
+
from .models import (
|
|
13
|
+
StepExplanation,
|
|
14
|
+
InteractiveExercise,
|
|
15
|
+
LearningProgress,
|
|
16
|
+
TutorialSession,
|
|
17
|
+
ValidationResult,
|
|
18
|
+
CodeContext
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"LearningSystem",
|
|
23
|
+
"TutorialEngine",
|
|
24
|
+
"ProgressSystem",
|
|
25
|
+
"InteractiveSessionManager",
|
|
26
|
+
"StepExplanation",
|
|
27
|
+
"InteractiveExercise",
|
|
28
|
+
"LearningProgress",
|
|
29
|
+
"TutorialSession",
|
|
30
|
+
"ValidationResult",
|
|
31
|
+
"CodeContext"
|
|
32
|
+
]
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core Learning System implementation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import uuid
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import List, Optional, Dict, Set
|
|
9
|
+
from .models import (
|
|
10
|
+
TutorialSession, StepExplanation, DifficultyLevel,
|
|
11
|
+
CodeContext, LearningProgress, InteractiveExercise, ExerciseStatus
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LearningSystem:
|
|
16
|
+
"""
|
|
17
|
+
Central component coordinating all learning activities.
|
|
18
|
+
|
|
19
|
+
Provides step-by-step explanations, tutorial management,
|
|
20
|
+
and progress tracking for Python beginners.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, config_path: Optional[str] = None):
|
|
24
|
+
"""Initialize the learning system with optional configuration."""
|
|
25
|
+
self.config_path = config_path
|
|
26
|
+
self._tutorial_engine = None
|
|
27
|
+
self._progress_system = None
|
|
28
|
+
self._session_manager = None
|
|
29
|
+
|
|
30
|
+
# Topic relationships for suggesting related topics
|
|
31
|
+
self._topic_relationships = {
|
|
32
|
+
"variables": ["data_types", "operators", "input_output"],
|
|
33
|
+
"data_types": ["variables", "type_conversion", "strings"],
|
|
34
|
+
"lists": ["loops", "indexing", "list_methods", "collections"],
|
|
35
|
+
"dictionaries": ["lists", "collections", "key_value_pairs"],
|
|
36
|
+
"loops": ["lists", "conditionals", "iteration"],
|
|
37
|
+
"functions": ["parameters", "return_values", "scope"],
|
|
38
|
+
"conditionals": ["boolean_logic", "comparison_operators"],
|
|
39
|
+
"error_handling": ["exceptions", "try_except", "debugging"],
|
|
40
|
+
"file_operations": ["strings", "error_handling", "paths"],
|
|
41
|
+
"classes": ["objects", "methods", "inheritance"],
|
|
42
|
+
"modules": ["imports", "packages", "namespaces"]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Content adaptation templates by level
|
|
46
|
+
self._level_adaptations = {
|
|
47
|
+
DifficultyLevel.BEGINNER: {
|
|
48
|
+
"vocabulary": "simple",
|
|
49
|
+
"examples": "basic",
|
|
50
|
+
"detail_level": "high",
|
|
51
|
+
"prerequisites": "minimal"
|
|
52
|
+
},
|
|
53
|
+
DifficultyLevel.INTERMEDIATE: {
|
|
54
|
+
"vocabulary": "technical",
|
|
55
|
+
"examples": "practical",
|
|
56
|
+
"detail_level": "medium",
|
|
57
|
+
"prerequisites": "some"
|
|
58
|
+
},
|
|
59
|
+
DifficultyLevel.ADVANCED: {
|
|
60
|
+
"vocabulary": "advanced",
|
|
61
|
+
"examples": "complex",
|
|
62
|
+
"detail_level": "low",
|
|
63
|
+
"prerequisites": "extensive"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
def start_tutorial(self, topic: str, level: str = "beginner") -> TutorialSession:
|
|
68
|
+
"""
|
|
69
|
+
Start a new tutorial session for the given topic and level.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
topic: The topic to learn (e.g., "lists", "functions", "error_handling")
|
|
73
|
+
level: Difficulty level ("beginner", "intermediate", "advanced")
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
TutorialSession: A new tutorial session object
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
ValueError: If topic or level is invalid
|
|
80
|
+
"""
|
|
81
|
+
# Validate level
|
|
82
|
+
try:
|
|
83
|
+
difficulty_level = DifficultyLevel(level)
|
|
84
|
+
except ValueError:
|
|
85
|
+
raise ValueError(f"Invalid level '{level}'. Must be one of: beginner, intermediate, advanced")
|
|
86
|
+
|
|
87
|
+
# Validate topic (basic validation - could be expanded)
|
|
88
|
+
if not topic or not isinstance(topic, str):
|
|
89
|
+
raise ValueError("Topic must be a non-empty string")
|
|
90
|
+
|
|
91
|
+
# Generate session ID
|
|
92
|
+
session_id = str(uuid.uuid4())
|
|
93
|
+
|
|
94
|
+
# Create basic exercises for the topic (simplified for now)
|
|
95
|
+
exercises = self._create_exercises_for_topic(topic, difficulty_level)
|
|
96
|
+
|
|
97
|
+
# Create tutorial session
|
|
98
|
+
session = TutorialSession(
|
|
99
|
+
session_id=session_id,
|
|
100
|
+
topic=topic,
|
|
101
|
+
level=difficulty_level,
|
|
102
|
+
start_time=datetime.now(),
|
|
103
|
+
exercises=exercises
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return session
|
|
107
|
+
|
|
108
|
+
def get_step_by_step_explanation(self, code: str, context: Optional[CodeContext] = None) -> List[StepExplanation]:
|
|
109
|
+
"""
|
|
110
|
+
Generate step-by-step explanation for the given code.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
code: Python code to explain
|
|
114
|
+
context: Optional context information for better explanations
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
List[StepExplanation]: Detailed explanations for each step
|
|
118
|
+
"""
|
|
119
|
+
if not code or not isinstance(code, str):
|
|
120
|
+
raise ValueError("Code must be a non-empty string")
|
|
121
|
+
|
|
122
|
+
explanations = []
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
# Parse the code into an AST
|
|
126
|
+
tree = ast.parse(code.strip())
|
|
127
|
+
|
|
128
|
+
# Generate explanations for each statement
|
|
129
|
+
for i, node in enumerate(ast.walk(tree)):
|
|
130
|
+
if isinstance(node, (ast.stmt, ast.expr)):
|
|
131
|
+
explanation = self._explain_ast_node(node, i + 1, context)
|
|
132
|
+
if explanation:
|
|
133
|
+
explanations.append(explanation)
|
|
134
|
+
|
|
135
|
+
except SyntaxError as e:
|
|
136
|
+
# If code has syntax errors, provide a basic explanation
|
|
137
|
+
explanations.append(StepExplanation(
|
|
138
|
+
step_number=1,
|
|
139
|
+
description=f"Syntax error in code: {str(e)}",
|
|
140
|
+
code_snippet=code,
|
|
141
|
+
related_concepts=["syntax", "debugging"]
|
|
142
|
+
))
|
|
143
|
+
|
|
144
|
+
return explanations
|
|
145
|
+
|
|
146
|
+
def suggest_related_topics(self, current_topic: str) -> List[str]:
|
|
147
|
+
"""
|
|
148
|
+
Suggest related topics based on the current topic.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
current_topic: The topic currently being studied
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
List[str]: List of related topic names
|
|
155
|
+
"""
|
|
156
|
+
if not current_topic or not isinstance(current_topic, str):
|
|
157
|
+
return []
|
|
158
|
+
|
|
159
|
+
# Get direct relationships
|
|
160
|
+
related = self._topic_relationships.get(current_topic.lower(), [])
|
|
161
|
+
|
|
162
|
+
# Add topics that reference the current topic
|
|
163
|
+
for topic, relationships in self._topic_relationships.items():
|
|
164
|
+
if current_topic.lower() in relationships and topic not in related:
|
|
165
|
+
related.append(topic)
|
|
166
|
+
|
|
167
|
+
return related[:5] # Limit to 5 suggestions
|
|
168
|
+
|
|
169
|
+
def adapt_content_for_level(self, content: str, level: str) -> str:
|
|
170
|
+
"""
|
|
171
|
+
Adapt content complexity for the specified level.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
content: Original content to adapt
|
|
175
|
+
level: Target difficulty level
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
str: Adapted content appropriate for the level
|
|
179
|
+
"""
|
|
180
|
+
if not content or not isinstance(content, str):
|
|
181
|
+
return content
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
difficulty_level = DifficultyLevel(level)
|
|
185
|
+
except ValueError:
|
|
186
|
+
return content # Return original if invalid level
|
|
187
|
+
|
|
188
|
+
adaptation = self._level_adaptations[difficulty_level]
|
|
189
|
+
|
|
190
|
+
# Apply adaptations based on level
|
|
191
|
+
if difficulty_level == DifficultyLevel.BEGINNER:
|
|
192
|
+
# Add more explanatory text and simpler vocabulary
|
|
193
|
+
adapted = self._simplify_vocabulary(content)
|
|
194
|
+
adapted = self._add_beginner_context(adapted)
|
|
195
|
+
elif difficulty_level == DifficultyLevel.INTERMEDIATE:
|
|
196
|
+
# Balance between detail and conciseness
|
|
197
|
+
adapted = self._add_practical_context(content)
|
|
198
|
+
else: # ADVANCED
|
|
199
|
+
# More concise, assume prior knowledge
|
|
200
|
+
adapted = self._make_concise(content)
|
|
201
|
+
|
|
202
|
+
return adapted
|
|
203
|
+
|
|
204
|
+
def track_progress(self, user_id: str, topic: str, completed: bool) -> None:
|
|
205
|
+
"""
|
|
206
|
+
Track user progress for a specific topic.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
user_id: Unique identifier for the user
|
|
210
|
+
topic: Topic that was studied
|
|
211
|
+
completed: Whether the topic was completed successfully
|
|
212
|
+
"""
|
|
213
|
+
if not self._progress_system:
|
|
214
|
+
from .progress import ProgressSystem
|
|
215
|
+
self._progress_system = ProgressSystem()
|
|
216
|
+
|
|
217
|
+
self._progress_system.update_progress(user_id, topic, completed)
|
|
218
|
+
|
|
219
|
+
def get_user_progress(self, user_id: str) -> Optional[LearningProgress]:
|
|
220
|
+
"""
|
|
221
|
+
Get current progress for a user.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
user_id: Unique identifier for the user
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Optional[LearningProgress]: User's progress or None if not found
|
|
228
|
+
"""
|
|
229
|
+
if not self._progress_system:
|
|
230
|
+
from .progress import ProgressSystem
|
|
231
|
+
self._progress_system = ProgressSystem()
|
|
232
|
+
|
|
233
|
+
return self._progress_system.get_progress(user_id)
|
|
234
|
+
def _create_exercises_for_topic(self, topic: str, level: DifficultyLevel) -> List[InteractiveExercise]:
|
|
235
|
+
"""Create basic exercises for a given topic and level."""
|
|
236
|
+
exercises = []
|
|
237
|
+
|
|
238
|
+
# Basic exercise templates by topic
|
|
239
|
+
exercise_templates = {
|
|
240
|
+
"variables": {
|
|
241
|
+
"title": "Working with Variables",
|
|
242
|
+
"description": "Practice creating and using variables",
|
|
243
|
+
"starter_code": "# Create a variable called 'name' and assign your name to it\n",
|
|
244
|
+
"expected_output": "Variable assignment"
|
|
245
|
+
},
|
|
246
|
+
"lists": {
|
|
247
|
+
"title": "List Operations",
|
|
248
|
+
"description": "Practice creating and manipulating lists",
|
|
249
|
+
"starter_code": "# Create a list of your favorite colors\ncolors = []\n",
|
|
250
|
+
"expected_output": "List with items"
|
|
251
|
+
},
|
|
252
|
+
"functions": {
|
|
253
|
+
"title": "Function Definition",
|
|
254
|
+
"description": "Practice defining and calling functions",
|
|
255
|
+
"starter_code": "# Define a function that greets a person\ndef greet(name):\n pass\n",
|
|
256
|
+
"expected_output": "Function definition"
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
template = exercise_templates.get(topic.lower())
|
|
261
|
+
if template:
|
|
262
|
+
exercise = InteractiveExercise(
|
|
263
|
+
id=str(uuid.uuid4()),
|
|
264
|
+
title=template["title"],
|
|
265
|
+
description=template["description"],
|
|
266
|
+
starter_code=template["starter_code"],
|
|
267
|
+
expected_output=template["expected_output"],
|
|
268
|
+
hints=["Think about the basic syntax", "Check the examples"],
|
|
269
|
+
difficulty_level=level,
|
|
270
|
+
topic=topic
|
|
271
|
+
)
|
|
272
|
+
exercises.append(exercise)
|
|
273
|
+
|
|
274
|
+
return exercises
|
|
275
|
+
|
|
276
|
+
def _explain_ast_node(self, node: ast.AST, step_number: int, context: Optional[CodeContext]) -> Optional[StepExplanation]:
|
|
277
|
+
"""Generate explanation for an AST node."""
|
|
278
|
+
if isinstance(node, ast.Assign):
|
|
279
|
+
return StepExplanation(
|
|
280
|
+
step_number=step_number,
|
|
281
|
+
description="Variable assignment: storing a value in a variable",
|
|
282
|
+
code_snippet=ast.unparse(node) if hasattr(ast, 'unparse') else str(node),
|
|
283
|
+
related_concepts=["variables", "assignment"]
|
|
284
|
+
)
|
|
285
|
+
elif isinstance(node, ast.FunctionDef):
|
|
286
|
+
return StepExplanation(
|
|
287
|
+
step_number=step_number,
|
|
288
|
+
description=f"Function definition: creating a reusable block of code named '{node.name}'",
|
|
289
|
+
code_snippet=ast.unparse(node) if hasattr(ast, 'unparse') else str(node),
|
|
290
|
+
related_concepts=["functions", "definition", "parameters"]
|
|
291
|
+
)
|
|
292
|
+
elif isinstance(node, ast.Call):
|
|
293
|
+
return StepExplanation(
|
|
294
|
+
step_number=step_number,
|
|
295
|
+
description="Function call: executing a function with given arguments",
|
|
296
|
+
code_snippet=ast.unparse(node) if hasattr(ast, 'unparse') else str(node),
|
|
297
|
+
related_concepts=["functions", "calls", "arguments"]
|
|
298
|
+
)
|
|
299
|
+
elif isinstance(node, ast.If):
|
|
300
|
+
return StepExplanation(
|
|
301
|
+
step_number=step_number,
|
|
302
|
+
description="Conditional statement: executing code based on a condition",
|
|
303
|
+
code_snippet=ast.unparse(node) if hasattr(ast, 'unparse') else str(node),
|
|
304
|
+
related_concepts=["conditionals", "boolean_logic"]
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
def _simplify_vocabulary(self, content: str) -> str:
|
|
310
|
+
"""Simplify vocabulary for beginners."""
|
|
311
|
+
# Basic vocabulary replacements
|
|
312
|
+
replacements = {
|
|
313
|
+
"instantiate": "create",
|
|
314
|
+
"initialize": "set up",
|
|
315
|
+
"parameter": "input value",
|
|
316
|
+
"argument": "value you pass in",
|
|
317
|
+
"iterate": "go through each item",
|
|
318
|
+
"concatenate": "join together"
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
result = content
|
|
322
|
+
for technical, simple in replacements.items():
|
|
323
|
+
result = result.replace(technical, simple)
|
|
324
|
+
|
|
325
|
+
return result
|
|
326
|
+
|
|
327
|
+
def _add_beginner_context(self, content: str) -> str:
|
|
328
|
+
"""Add extra context for beginners."""
|
|
329
|
+
return f"For beginners: {content}\n\nRemember: Take your time and don't worry if this seems complex at first!"
|
|
330
|
+
|
|
331
|
+
def _add_practical_context(self, content: str) -> str:
|
|
332
|
+
"""Add practical context for intermediate learners."""
|
|
333
|
+
return f"{content}\n\nPractical tip: This concept is commonly used in real-world programming."
|
|
334
|
+
|
|
335
|
+
def _make_concise(self, content: str) -> str:
|
|
336
|
+
"""Make content more concise for advanced learners."""
|
|
337
|
+
# Remove beginner-specific phrases
|
|
338
|
+
phrases_to_remove = [
|
|
339
|
+
"For beginners: ",
|
|
340
|
+
"Remember: ",
|
|
341
|
+
"Don't worry if this seems complex",
|
|
342
|
+
"Take your time"
|
|
343
|
+
]
|
|
344
|
+
|
|
345
|
+
result = content
|
|
346
|
+
for phrase in phrases_to_remove:
|
|
347
|
+
result = result.replace(phrase, "")
|
|
348
|
+
|
|
349
|
+
return result.strip()
|