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,500 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interactive session manager for learning activities.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import List, Optional, Dict, Any
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from .models import (
|
|
9
|
+
TutorialSession, InteractiveExercise, ValidationResult,
|
|
10
|
+
DifficultyLevel, ExerciseStatus
|
|
11
|
+
)
|
|
12
|
+
from .tutorial import TutorialEngine
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class InteractiveSessionManager:
|
|
16
|
+
"""
|
|
17
|
+
Manages interactive learning sessions with exercises and feedback.
|
|
18
|
+
|
|
19
|
+
Handles user input, provides feedback, and manages session state
|
|
20
|
+
for interactive learning experiences.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
"""Initialize the session manager."""
|
|
25
|
+
self._active_sessions: Dict[str, TutorialSession] = {}
|
|
26
|
+
self._tutorial_engine = TutorialEngine()
|
|
27
|
+
|
|
28
|
+
# Integration points
|
|
29
|
+
self._example_repository = None
|
|
30
|
+
|
|
31
|
+
# Additional examples by topic for when users struggle
|
|
32
|
+
self._additional_examples = {
|
|
33
|
+
'variables': [
|
|
34
|
+
"Try creating a variable for your favorite color: color = 'blue'",
|
|
35
|
+
"Create a variable for a number: count = 42",
|
|
36
|
+
"Make a variable for your city: city = 'New York'",
|
|
37
|
+
"Store a boolean value: is_student = True"
|
|
38
|
+
],
|
|
39
|
+
'lists': [
|
|
40
|
+
"Create a list of numbers: numbers = [1, 2, 3, 4, 5]",
|
|
41
|
+
"Make a list of names: friends = ['Alice', 'Bob', 'Charlie']",
|
|
42
|
+
"Try an empty list first: empty_list = []",
|
|
43
|
+
"Add items one by one: my_list.append('new_item')"
|
|
44
|
+
],
|
|
45
|
+
'functions': [
|
|
46
|
+
"Start with a simple function: def say_hello(): print('Hello!')",
|
|
47
|
+
"Try a function with a parameter: def greet(name): print(f'Hi, {name}!')",
|
|
48
|
+
"Create a function that returns a value: def add_numbers(a, b): return a + b",
|
|
49
|
+
"Remember to call your function after defining it: say_hello()"
|
|
50
|
+
],
|
|
51
|
+
'loops': [
|
|
52
|
+
"Simple loop over a list: for item in [1, 2, 3]: print(item)",
|
|
53
|
+
"Loop with range: for i in range(5): print(i)",
|
|
54
|
+
"Loop over strings: for char in 'hello': print(char)",
|
|
55
|
+
"Use meaningful variable names: for student in students: print(student)"
|
|
56
|
+
],
|
|
57
|
+
'conditionals': [
|
|
58
|
+
"Basic if statement: if age >= 18: print('Adult')",
|
|
59
|
+
"If-else: if score > 90: print('A') else: print('Try again')",
|
|
60
|
+
"Multiple conditions: if x > 0 and x < 10: print('Single digit')",
|
|
61
|
+
"Check for equality: if name == 'Alice': print('Hello Alice!')"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def create_session(self, user_id: str, topic: str, level: DifficultyLevel) -> TutorialSession:
|
|
66
|
+
"""
|
|
67
|
+
Create a new interactive learning session.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
user_id: Unique identifier for the user
|
|
71
|
+
topic: Topic for the session
|
|
72
|
+
level: Difficulty level for the session
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
TutorialSession: New tutorial session
|
|
76
|
+
"""
|
|
77
|
+
if not user_id or not isinstance(user_id, str):
|
|
78
|
+
raise ValueError("User ID must be a non-empty string")
|
|
79
|
+
|
|
80
|
+
if not topic or not isinstance(topic, str):
|
|
81
|
+
raise ValueError("Topic must be a non-empty string")
|
|
82
|
+
|
|
83
|
+
# Generate session ID
|
|
84
|
+
session_id = str(uuid.uuid4())
|
|
85
|
+
|
|
86
|
+
# Create exercises for the topic using the tutorial engine
|
|
87
|
+
exercises = []
|
|
88
|
+
|
|
89
|
+
# Create multiple exercises of increasing difficulty
|
|
90
|
+
base_exercise = self._tutorial_engine.create_interactive_exercise(topic, level)
|
|
91
|
+
exercises.append(base_exercise)
|
|
92
|
+
|
|
93
|
+
# Add a follow-up exercise if beginner level
|
|
94
|
+
if level == DifficultyLevel.BEGINNER:
|
|
95
|
+
follow_up = self._create_follow_up_exercise(topic, level)
|
|
96
|
+
if follow_up:
|
|
97
|
+
exercises.append(follow_up)
|
|
98
|
+
|
|
99
|
+
# Create the session
|
|
100
|
+
session = TutorialSession(
|
|
101
|
+
session_id=session_id,
|
|
102
|
+
topic=topic,
|
|
103
|
+
level=level,
|
|
104
|
+
start_time=datetime.now(),
|
|
105
|
+
exercises=exercises,
|
|
106
|
+
current_exercise_index=0,
|
|
107
|
+
is_completed=False
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Store the session
|
|
111
|
+
self._active_sessions[session_id] = session
|
|
112
|
+
|
|
113
|
+
return session
|
|
114
|
+
|
|
115
|
+
def create_session_from_example(self, user_id: str, example):
|
|
116
|
+
"""
|
|
117
|
+
Create a session based on a specific example from the repository.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
user_id: Unique identifier for the user
|
|
121
|
+
example: CodeExample from the repository
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
TutorialSession: New tutorial session based on the example
|
|
125
|
+
"""
|
|
126
|
+
if not user_id or not isinstance(user_id, str):
|
|
127
|
+
raise ValueError("User ID must be a non-empty string")
|
|
128
|
+
|
|
129
|
+
if not example:
|
|
130
|
+
raise ValueError("Example must be provided")
|
|
131
|
+
|
|
132
|
+
# Generate session ID
|
|
133
|
+
session_id = str(uuid.uuid4())
|
|
134
|
+
|
|
135
|
+
# Determine difficulty level from example
|
|
136
|
+
try:
|
|
137
|
+
from .models import DifficultyLevel
|
|
138
|
+
level = DifficultyLevel(example.difficulty)
|
|
139
|
+
except (ValueError, AttributeError):
|
|
140
|
+
level = DifficultyLevel.BEGINNER
|
|
141
|
+
|
|
142
|
+
# Create exercises based on the example
|
|
143
|
+
exercises = []
|
|
144
|
+
|
|
145
|
+
# Create main exercise from the example
|
|
146
|
+
main_exercise = InteractiveExercise(
|
|
147
|
+
id=str(uuid.uuid4()),
|
|
148
|
+
title=f"Practice: {example.title}",
|
|
149
|
+
description=f"{example.description}\n\nExample code:\n{example.code}",
|
|
150
|
+
starter_code=f"# Based on the example above, try to write similar code\n# {example.explanation}\n\n",
|
|
151
|
+
expected_output=getattr(example, 'expected_output', 'Working code implementation'),
|
|
152
|
+
hints=[
|
|
153
|
+
"Look at the example code for guidance",
|
|
154
|
+
"Break the problem into smaller steps",
|
|
155
|
+
"Test your code as you write it"
|
|
156
|
+
] + getattr(example, 'common_mistakes', []),
|
|
157
|
+
difficulty_level=level,
|
|
158
|
+
topic=example.topics[0] if example.topics else "general",
|
|
159
|
+
status=ExerciseStatus.NOT_STARTED
|
|
160
|
+
)
|
|
161
|
+
exercises.append(main_exercise)
|
|
162
|
+
|
|
163
|
+
# Create the session
|
|
164
|
+
session = TutorialSession(
|
|
165
|
+
session_id=session_id,
|
|
166
|
+
topic=example.topics[0] if example.topics else "general",
|
|
167
|
+
level=level,
|
|
168
|
+
start_time=datetime.now(),
|
|
169
|
+
exercises=exercises,
|
|
170
|
+
current_exercise_index=0,
|
|
171
|
+
is_completed=False
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Store the session
|
|
175
|
+
self._active_sessions[session_id] = session
|
|
176
|
+
|
|
177
|
+
return session
|
|
178
|
+
|
|
179
|
+
def get_session(self, session_id: str) -> Optional[TutorialSession]:
|
|
180
|
+
"""
|
|
181
|
+
Get an active session by ID.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
session_id: Unique session identifier
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Optional[TutorialSession]: Session or None if not found
|
|
188
|
+
"""
|
|
189
|
+
if not session_id or not isinstance(session_id, str):
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
return self._active_sessions.get(session_id)
|
|
193
|
+
|
|
194
|
+
def submit_solution(self, session_id: str, solution: str) -> ValidationResult:
|
|
195
|
+
"""
|
|
196
|
+
Submit a solution for the current exercise in the session.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
session_id: Unique session identifier
|
|
200
|
+
solution: User's code solution
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
ValidationResult: Validation result with feedback
|
|
204
|
+
"""
|
|
205
|
+
session = self.get_session(session_id)
|
|
206
|
+
if not session:
|
|
207
|
+
return ValidationResult(
|
|
208
|
+
is_correct=False,
|
|
209
|
+
feedback="Session not found",
|
|
210
|
+
errors=["Invalid session ID"]
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if session.is_completed:
|
|
214
|
+
return ValidationResult(
|
|
215
|
+
is_correct=False,
|
|
216
|
+
feedback="Session is already completed",
|
|
217
|
+
errors=["Session completed"]
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Get current exercise
|
|
221
|
+
current_exercise = self._get_current_exercise(session)
|
|
222
|
+
if not current_exercise:
|
|
223
|
+
return ValidationResult(
|
|
224
|
+
is_correct=False,
|
|
225
|
+
feedback="No active exercise in session",
|
|
226
|
+
errors=["No exercise available"]
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Update exercise status and attempt count
|
|
230
|
+
current_exercise.status = ExerciseStatus.IN_PROGRESS
|
|
231
|
+
current_exercise.attempts += 1
|
|
232
|
+
|
|
233
|
+
# Validate the solution using the tutorial engine
|
|
234
|
+
result = self._tutorial_engine.validate_solution(current_exercise, solution)
|
|
235
|
+
|
|
236
|
+
# Update exercise status based on result
|
|
237
|
+
if result.is_correct:
|
|
238
|
+
current_exercise.status = ExerciseStatus.COMPLETED
|
|
239
|
+
# Add encouraging feedback for correct solutions
|
|
240
|
+
result.feedback = f"Excellent work! {result.feedback}"
|
|
241
|
+
result.suggestions.append("Ready for the next challenge!")
|
|
242
|
+
else:
|
|
243
|
+
# Check if max attempts reached
|
|
244
|
+
if current_exercise.attempts >= current_exercise.max_attempts:
|
|
245
|
+
current_exercise.status = ExerciseStatus.FAILED
|
|
246
|
+
result.feedback += " You've reached the maximum attempts. Let's move to the next exercise."
|
|
247
|
+
result.suggestions.append("Don't worry, learning takes practice!")
|
|
248
|
+
|
|
249
|
+
return result
|
|
250
|
+
|
|
251
|
+
def get_hint(self, session_id: str) -> Optional[str]:
|
|
252
|
+
"""
|
|
253
|
+
Get a hint for the current exercise in the session.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
session_id: Unique session identifier
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Optional[str]: Hint text or None if no hints available
|
|
260
|
+
"""
|
|
261
|
+
session = self.get_session(session_id)
|
|
262
|
+
if not session:
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
current_exercise = self._get_current_exercise(session)
|
|
266
|
+
if not current_exercise:
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
# Get hint from tutorial engine based on current attempt
|
|
270
|
+
hint = self._tutorial_engine.provide_hint(current_exercise, "")
|
|
271
|
+
|
|
272
|
+
return hint
|
|
273
|
+
|
|
274
|
+
def next_exercise(self, session_id: str) -> Optional[InteractiveExercise]:
|
|
275
|
+
"""
|
|
276
|
+
Move to the next exercise in the session.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
session_id: Unique session identifier
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Optional[InteractiveExercise]: Next exercise or None if session complete
|
|
283
|
+
"""
|
|
284
|
+
session = self.get_session(session_id)
|
|
285
|
+
if not session:
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
# Move to next exercise
|
|
289
|
+
session.current_exercise_index += 1
|
|
290
|
+
|
|
291
|
+
# Check if session is complete
|
|
292
|
+
if session.current_exercise_index >= len(session.exercises):
|
|
293
|
+
session.is_completed = True
|
|
294
|
+
session.end_time = datetime.now()
|
|
295
|
+
return None
|
|
296
|
+
|
|
297
|
+
# Return the next exercise
|
|
298
|
+
return session.exercises[session.current_exercise_index]
|
|
299
|
+
|
|
300
|
+
def complete_session(self, session_id: str) -> Dict[str, Any]:
|
|
301
|
+
"""
|
|
302
|
+
Complete the session and return summary statistics.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
session_id: Unique session identifier
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Dict[str, Any]: Session completion summary
|
|
309
|
+
"""
|
|
310
|
+
session = self.get_session(session_id)
|
|
311
|
+
if not session:
|
|
312
|
+
return {"error": "Session not found"}
|
|
313
|
+
|
|
314
|
+
# Mark session as completed if not already
|
|
315
|
+
if not session.is_completed:
|
|
316
|
+
session.is_completed = True
|
|
317
|
+
session.end_time = datetime.now()
|
|
318
|
+
|
|
319
|
+
# Calculate statistics
|
|
320
|
+
total_exercises = len(session.exercises)
|
|
321
|
+
completed_exercises = sum(1 for ex in session.exercises if ex.status == ExerciseStatus.COMPLETED)
|
|
322
|
+
failed_exercises = sum(1 for ex in session.exercises if ex.status == ExerciseStatus.FAILED)
|
|
323
|
+
total_attempts = sum(ex.attempts for ex in session.exercises)
|
|
324
|
+
|
|
325
|
+
# Calculate session duration
|
|
326
|
+
duration_minutes = 0
|
|
327
|
+
if session.end_time and session.start_time:
|
|
328
|
+
duration = session.end_time - session.start_time
|
|
329
|
+
duration_minutes = duration.total_seconds() / 60
|
|
330
|
+
|
|
331
|
+
# Calculate success rate
|
|
332
|
+
success_rate = (completed_exercises / total_exercises * 100) if total_exercises > 0 else 0
|
|
333
|
+
|
|
334
|
+
# Generate feedback message
|
|
335
|
+
if success_rate >= 80:
|
|
336
|
+
feedback = "Outstanding performance! You're mastering this topic."
|
|
337
|
+
elif success_rate >= 60:
|
|
338
|
+
feedback = "Good job! Keep practicing to improve further."
|
|
339
|
+
elif success_rate >= 40:
|
|
340
|
+
feedback = "You're making progress. Don't give up!"
|
|
341
|
+
else:
|
|
342
|
+
feedback = "Learning takes time. Consider reviewing the basics and trying again."
|
|
343
|
+
|
|
344
|
+
# Remove session from active sessions
|
|
345
|
+
if session_id in self._active_sessions:
|
|
346
|
+
del self._active_sessions[session_id]
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
"session_id": session_id,
|
|
350
|
+
"topic": session.topic,
|
|
351
|
+
"level": session.level.value,
|
|
352
|
+
"duration_minutes": round(duration_minutes, 2),
|
|
353
|
+
"total_exercises": total_exercises,
|
|
354
|
+
"completed_exercises": completed_exercises,
|
|
355
|
+
"failed_exercises": failed_exercises,
|
|
356
|
+
"total_attempts": total_attempts,
|
|
357
|
+
"success_rate": round(success_rate, 2),
|
|
358
|
+
"feedback": feedback,
|
|
359
|
+
"completed_at": session.end_time.isoformat() if session.end_time else None
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
def provide_additional_examples(self, session_id: str, topic: str) -> List[str]:
|
|
363
|
+
"""
|
|
364
|
+
Provide additional examples when user is struggling.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
session_id: Unique session identifier
|
|
368
|
+
topic: Topic for which to provide examples
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
List[str]: List of additional example descriptions
|
|
372
|
+
"""
|
|
373
|
+
session = self.get_session(session_id)
|
|
374
|
+
if not session:
|
|
375
|
+
return []
|
|
376
|
+
|
|
377
|
+
topic_lower = topic.lower()
|
|
378
|
+
|
|
379
|
+
# Get examples for the topic
|
|
380
|
+
examples = self._additional_examples.get(topic_lower, [])
|
|
381
|
+
|
|
382
|
+
# If no specific examples, provide generic encouragement
|
|
383
|
+
if not examples:
|
|
384
|
+
examples = [
|
|
385
|
+
f"Try breaking down the {topic} problem into smaller steps",
|
|
386
|
+
f"Look for similar {topic} examples online or in documentation",
|
|
387
|
+
f"Practice with simpler {topic} exercises first",
|
|
388
|
+
"Don't hesitate to ask for help when you're stuck"
|
|
389
|
+
]
|
|
390
|
+
|
|
391
|
+
return examples
|
|
392
|
+
|
|
393
|
+
def get_current_exercise(self, session_id: str) -> Optional[InteractiveExercise]:
|
|
394
|
+
"""
|
|
395
|
+
Get the current exercise for a session.
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
session_id: Unique session identifier
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Optional[InteractiveExercise]: Current exercise or None
|
|
402
|
+
"""
|
|
403
|
+
session = self.get_session(session_id)
|
|
404
|
+
if not session:
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
return self._get_current_exercise(session)
|
|
408
|
+
|
|
409
|
+
def get_session_progress(self, session_id: str) -> Dict[str, Any]:
|
|
410
|
+
"""
|
|
411
|
+
Get progress information for a session.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
session_id: Unique session identifier
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
Dict[str, Any]: Progress information
|
|
418
|
+
"""
|
|
419
|
+
session = self.get_session(session_id)
|
|
420
|
+
if not session:
|
|
421
|
+
return {"error": "Session not found"}
|
|
422
|
+
|
|
423
|
+
total_exercises = len(session.exercises)
|
|
424
|
+
current_index = session.current_exercise_index
|
|
425
|
+
completed_exercises = sum(1 for ex in session.exercises if ex.status == ExerciseStatus.COMPLETED)
|
|
426
|
+
|
|
427
|
+
progress_percentage = (completed_exercises / total_exercises * 100) if total_exercises > 0 else 0
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
"session_id": session_id,
|
|
431
|
+
"topic": session.topic,
|
|
432
|
+
"level": session.level.value,
|
|
433
|
+
"current_exercise": current_index + 1,
|
|
434
|
+
"total_exercises": total_exercises,
|
|
435
|
+
"completed_exercises": completed_exercises,
|
|
436
|
+
"progress_percentage": round(progress_percentage, 2),
|
|
437
|
+
"is_completed": session.is_completed
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
def _get_current_exercise(self, session: TutorialSession) -> Optional[InteractiveExercise]:
|
|
441
|
+
"""Get the current exercise from a session."""
|
|
442
|
+
if (session.current_exercise_index < 0 or
|
|
443
|
+
session.current_exercise_index >= len(session.exercises)):
|
|
444
|
+
return None
|
|
445
|
+
|
|
446
|
+
return session.exercises[session.current_exercise_index]
|
|
447
|
+
|
|
448
|
+
def _create_follow_up_exercise(self, topic: str, level: DifficultyLevel) -> Optional[InteractiveExercise]:
|
|
449
|
+
"""Create a follow-up exercise for the topic."""
|
|
450
|
+
topic_lower = topic.lower()
|
|
451
|
+
|
|
452
|
+
# Define follow-up exercises for different topics
|
|
453
|
+
follow_up_templates = {
|
|
454
|
+
'variables': {
|
|
455
|
+
'title': 'Variable Operations',
|
|
456
|
+
'description': 'Practice using variables in calculations and operations.',
|
|
457
|
+
'starter_code': '# Use the variables you created to perform some operations\n# For example, if you have age and name, create a message\n',
|
|
458
|
+
'hints': [
|
|
459
|
+
'You can combine variables with strings using f-strings',
|
|
460
|
+
'Try doing math operations with number variables',
|
|
461
|
+
'Use descriptive variable names'
|
|
462
|
+
]
|
|
463
|
+
},
|
|
464
|
+
'lists': {
|
|
465
|
+
'title': 'List Manipulation',
|
|
466
|
+
'description': 'Practice adding, removing, and accessing list elements.',
|
|
467
|
+
'starter_code': '# Take your list and try adding a new item\n# Then remove an item and print the result\n',
|
|
468
|
+
'hints': [
|
|
469
|
+
'Use append() to add items',
|
|
470
|
+
'Use remove() to delete items by value',
|
|
471
|
+
'Use len() to get the list size'
|
|
472
|
+
]
|
|
473
|
+
},
|
|
474
|
+
'functions': {
|
|
475
|
+
'title': 'Function with Return Value',
|
|
476
|
+
'description': 'Create a function that takes parameters and returns a result.',
|
|
477
|
+
'starter_code': '# Create a function that takes two numbers and returns their sum\n# Then call it and print the result\n',
|
|
478
|
+
'hints': [
|
|
479
|
+
'Use parameters inside the parentheses',
|
|
480
|
+
'Use return to send a value back',
|
|
481
|
+
'Store the returned value in a variable'
|
|
482
|
+
]
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
template = follow_up_templates.get(topic_lower)
|
|
487
|
+
if not template:
|
|
488
|
+
return None
|
|
489
|
+
|
|
490
|
+
return InteractiveExercise(
|
|
491
|
+
id=str(uuid.uuid4()),
|
|
492
|
+
title=template['title'],
|
|
493
|
+
description=template['description'],
|
|
494
|
+
starter_code=template['starter_code'],
|
|
495
|
+
expected_output="Follow-up exercise completion",
|
|
496
|
+
hints=template['hints'],
|
|
497
|
+
difficulty_level=level,
|
|
498
|
+
topic=topic,
|
|
499
|
+
status=ExerciseStatus.NOT_STARTED
|
|
500
|
+
)
|