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,626 @@
1
+ """
2
+ Tutorial Engine for generating step-by-step explanations and interactive lessons.
3
+ """
4
+
5
+ import ast
6
+ import re
7
+ import uuid
8
+ from typing import List, Optional, Dict, Any, Tuple
9
+ from .models import (
10
+ StepExplanation, InteractiveExercise, ValidationResult,
11
+ CodeContext, DifficultyLevel, ExerciseStatus
12
+ )
13
+
14
+
15
+ class TutorialEngine:
16
+ """
17
+ Generates step-by-step explanations and creates interactive exercises.
18
+
19
+ Provides detailed code explanations with examples and creates
20
+ interactive learning experiences for beginners.
21
+ """
22
+
23
+ def __init__(self):
24
+ """Initialize the tutorial engine."""
25
+ # Integration point for example repository
26
+ self._example_repository = None
27
+
28
+ # Code pattern explanations for different constructs
29
+ self._code_patterns = {
30
+ 'variable_assignment': {
31
+ 'pattern': r'^(\w+)\s*=\s*(.+)$',
32
+ 'explanation': "Variable assignment: storing the value {value} in variable '{var}'",
33
+ 'concepts': ['variables', 'assignment', 'data_storage']
34
+ },
35
+ 'function_call': {
36
+ 'pattern': r'(\w+)\s*\([^)]*\)',
37
+ 'explanation': "Function call: executing the function '{func}' with given arguments",
38
+ 'concepts': ['functions', 'function_calls', 'execution']
39
+ },
40
+ 'list_creation': {
41
+ 'pattern': r'\[.*\]',
42
+ 'explanation': "List creation: making a new list containing the specified items",
43
+ 'concepts': ['lists', 'data_structures', 'collections']
44
+ },
45
+ 'dict_creation': {
46
+ 'pattern': r'\{.*\}',
47
+ 'explanation': "Dictionary creation: making a new dictionary with key-value pairs",
48
+ 'concepts': ['dictionaries', 'data_structures', 'key_value_pairs']
49
+ },
50
+ 'for_loop': {
51
+ 'pattern': r'^for\s+\w+\s+in\s+.+:',
52
+ 'explanation': "For loop: repeating code for each item in the collection",
53
+ 'concepts': ['loops', 'iteration', 'control_flow']
54
+ },
55
+ 'if_statement': {
56
+ 'pattern': r'^if\s+.+:',
57
+ 'explanation': "Conditional statement: executing code only if the condition is true",
58
+ 'concepts': ['conditionals', 'boolean_logic', 'control_flow']
59
+ },
60
+ 'function_definition': {
61
+ 'pattern': r'^def\s+(\w+)\s*\([^)]*\):',
62
+ 'explanation': "Function definition: creating a reusable block of code named '{func}'",
63
+ 'concepts': ['functions', 'definition', 'code_organization']
64
+ },
65
+ 'import_statement': {
66
+ 'pattern': r'^(from\s+\w+\s+)?import\s+.+',
67
+ 'explanation': "Import statement: bringing in code from other modules or libraries",
68
+ 'concepts': ['imports', 'modules', 'libraries']
69
+ },
70
+ 'print_statement': {
71
+ 'pattern': r'print\s*\([^)]*\)',
72
+ 'explanation': "Print statement: displaying output to the user",
73
+ 'concepts': ['output', 'display', 'debugging']
74
+ }
75
+ }
76
+
77
+ # Exercise templates by topic and difficulty
78
+ self._exercise_templates = {
79
+ 'variables': {
80
+ DifficultyLevel.BEGINNER: {
81
+ 'title': 'Basic Variable Assignment',
82
+ 'description': 'Practice creating and using variables to store different types of data.',
83
+ 'starter_code': '# Create a variable called "name" and assign your name to it\n# Then create a variable called "age" and assign your age\n',
84
+ 'solution': 'name = "Alice"\nage = 25',
85
+ 'hints': [
86
+ 'Use quotes around text values (strings)',
87
+ 'Numbers don\'t need quotes',
88
+ 'Variable names should be descriptive'
89
+ ]
90
+ },
91
+ DifficultyLevel.INTERMEDIATE: {
92
+ 'title': 'Variable Operations',
93
+ 'description': 'Practice performing operations with variables and updating their values.',
94
+ 'starter_code': '# Create two number variables and calculate their sum\n# Store the result in a new variable\n',
95
+ 'solution': 'num1 = 10\nnum2 = 20\nsum_result = num1 + num2',
96
+ 'hints': [
97
+ 'Use meaningful variable names',
98
+ 'You can use variables in calculations',
99
+ 'Store the result in a new variable'
100
+ ]
101
+ }
102
+ },
103
+ 'lists': {
104
+ DifficultyLevel.BEGINNER: {
105
+ 'title': 'Creating and Using Lists',
106
+ 'description': 'Practice creating lists and accessing their elements.',
107
+ 'starter_code': '# Create a list of your favorite fruits\n# Then print the first fruit in the list\n',
108
+ 'solution': 'fruits = ["apple", "banana", "orange"]\nprint(fruits[0])',
109
+ 'hints': [
110
+ 'Use square brackets [] to create a list',
111
+ 'Separate items with commas',
112
+ 'Use index [0] to get the first item'
113
+ ]
114
+ },
115
+ DifficultyLevel.INTERMEDIATE: {
116
+ 'title': 'List Operations',
117
+ 'description': 'Practice adding, removing, and modifying list elements.',
118
+ 'starter_code': '# Create a list of numbers\n# Add a new number to the end\n# Remove the first number\n',
119
+ 'solution': 'numbers = [1, 2, 3]\nnumbers.append(4)\nnumbers.pop(0)',
120
+ 'hints': [
121
+ 'Use append() to add to the end',
122
+ 'Use pop(0) to remove the first item',
123
+ 'Lists are mutable (can be changed)'
124
+ ]
125
+ }
126
+ },
127
+ 'functions': {
128
+ DifficultyLevel.BEGINNER: {
129
+ 'title': 'Simple Function Definition',
130
+ 'description': 'Practice defining and calling a simple function.',
131
+ 'starter_code': '# Define a function called "greet" that prints "Hello!"\n# Then call the function\n',
132
+ 'solution': 'def greet():\n print("Hello!")\n\ngreet()',
133
+ 'hints': [
134
+ 'Use "def" to define a function',
135
+ 'Don\'t forget the colon (:) after the function name',
136
+ 'Indent the function body',
137
+ 'Call the function by using its name with parentheses'
138
+ ]
139
+ },
140
+ DifficultyLevel.INTERMEDIATE: {
141
+ 'title': 'Function with Parameters',
142
+ 'description': 'Practice creating functions that accept parameters and return values.',
143
+ 'starter_code': '# Define a function that takes a name parameter and returns a greeting\n# Call the function with your name\n',
144
+ 'solution': 'def greet(name):\n return f"Hello, {name}!"\n\nresult = greet("Alice")',
145
+ 'hints': [
146
+ 'Put parameter names inside the parentheses',
147
+ 'Use "return" to send a value back',
148
+ 'Store the returned value in a variable'
149
+ ]
150
+ }
151
+ },
152
+ 'loops': {
153
+ DifficultyLevel.BEGINNER: {
154
+ 'title': 'Simple For Loop',
155
+ 'description': 'Practice using a for loop to iterate through a list.',
156
+ 'starter_code': '# Create a list of colors\n# Use a for loop to print each color\n',
157
+ 'solution': 'colors = ["red", "green", "blue"]\nfor color in colors:\n print(color)',
158
+ 'hints': [
159
+ 'Use "for item in list:" syntax',
160
+ 'Don\'t forget the colon (:)',
161
+ 'Indent the loop body',
162
+ 'The loop variable takes each value from the list'
163
+ ]
164
+ }
165
+ }
166
+ }
167
+
168
+ def generate_step_explanation(self, code_line: str, context: Optional[CodeContext] = None) -> StepExplanation:
169
+ """
170
+ Generate detailed explanation for a single line of code.
171
+
172
+ Args:
173
+ code_line: Single line of Python code to explain
174
+ context: Context information about the code
175
+
176
+ Returns:
177
+ StepExplanation: Detailed explanation with examples
178
+ """
179
+ if not code_line or not isinstance(code_line, str):
180
+ raise ValueError("Code line must be a non-empty string")
181
+
182
+ code_line = code_line.strip()
183
+ if not code_line:
184
+ raise ValueError("Code line cannot be empty or whitespace only")
185
+
186
+ # Analyze the code line to determine its type and generate explanation
187
+ explanation_data = self._analyze_code_line(code_line, context)
188
+
189
+ # Generate input/output examples if possible
190
+ input_example, output_example = self._generate_examples(code_line, explanation_data)
191
+
192
+ return StepExplanation(
193
+ step_number=1, # Will be set by caller if needed
194
+ description=explanation_data['description'],
195
+ code_snippet=code_line,
196
+ input_example=input_example,
197
+ output_example=output_example,
198
+ related_concepts=explanation_data['concepts'],
199
+ visual_aid=explanation_data.get('visual_aid')
200
+ )
201
+
202
+ def create_interactive_exercise(self, topic: str, difficulty: DifficultyLevel = DifficultyLevel.BEGINNER) -> InteractiveExercise:
203
+ """
204
+ Create an interactive coding exercise for the given topic.
205
+
206
+ Args:
207
+ topic: Topic for the exercise (e.g., "lists", "functions")
208
+ difficulty: Difficulty level for the exercise
209
+
210
+ Returns:
211
+ InteractiveExercise: A new interactive exercise
212
+ """
213
+ if not topic or not isinstance(topic, str):
214
+ raise ValueError("Topic must be a non-empty string")
215
+
216
+ topic_lower = topic.lower()
217
+
218
+ # Get exercise template
219
+ template = self._get_exercise_template(topic_lower, difficulty)
220
+
221
+ return InteractiveExercise(
222
+ id=str(uuid.uuid4()),
223
+ title=template['title'],
224
+ description=template['description'],
225
+ starter_code=template['starter_code'],
226
+ expected_output=template.get('solution', 'Correct implementation'),
227
+ hints=template['hints'],
228
+ difficulty_level=difficulty,
229
+ topic=topic,
230
+ status=ExerciseStatus.NOT_STARTED
231
+ )
232
+
233
+ def validate_solution(self, exercise: InteractiveExercise, solution: str) -> ValidationResult:
234
+ """
235
+ Validate a user's solution to an interactive exercise.
236
+
237
+ Args:
238
+ exercise: The exercise being solved
239
+ solution: User's code solution
240
+
241
+ Returns:
242
+ ValidationResult: Validation result with feedback
243
+ """
244
+ if not solution or not isinstance(solution, str):
245
+ return ValidationResult(
246
+ is_correct=False,
247
+ feedback="Solution cannot be empty",
248
+ errors=["No code provided"],
249
+ suggestions=["Please write some code to solve the exercise"]
250
+ )
251
+
252
+ # Basic syntax validation
253
+ syntax_errors = self._check_syntax(solution)
254
+ if syntax_errors:
255
+ return ValidationResult(
256
+ is_correct=False,
257
+ feedback="Your code has syntax errors",
258
+ errors=syntax_errors,
259
+ suggestions=["Check your syntax", "Make sure all parentheses and brackets are closed"]
260
+ )
261
+
262
+ # Topic-specific validation
263
+ validation_result = self._validate_topic_specific(exercise, solution)
264
+
265
+ return validation_result
266
+
267
+ def provide_hint(self, exercise: InteractiveExercise, attempt: str) -> str:
268
+ """
269
+ Provide a helpful hint based on the user's attempt.
270
+
271
+ Args:
272
+ exercise: The exercise being solved
273
+ attempt: User's current attempt
274
+
275
+ Returns:
276
+ str: Helpful hint for the user
277
+ """
278
+ if not attempt or not attempt.strip():
279
+ # No attempt yet, provide first hint
280
+ if exercise.hints:
281
+ return exercise.hints[0]
282
+ return "Start by reading the exercise description carefully."
283
+
284
+ # Analyze the attempt to provide specific hints
285
+ attempt = attempt.strip()
286
+
287
+ # Check for common issues and provide targeted hints
288
+ if exercise.topic.lower() == 'variables':
289
+ if '=' not in attempt:
290
+ return "Remember to use the = sign to assign values to variables."
291
+ if attempt.count('"') % 2 != 0:
292
+ return "Make sure you have matching quotes around text values."
293
+
294
+ elif exercise.topic.lower() == 'lists':
295
+ if '[' not in attempt or ']' not in attempt:
296
+ return "Use square brackets [] to create a list."
297
+ if 'print' not in attempt.lower() and 'print' in exercise.description.lower():
298
+ return "Don't forget to print the result as requested."
299
+
300
+ elif exercise.topic.lower() == 'functions':
301
+ if 'def' not in attempt:
302
+ return "Use 'def' to define a function."
303
+ if ':' not in attempt:
304
+ return "Don't forget the colon (:) after the function definition."
305
+ if not any(line.startswith(' ') for line in attempt.split('\n')):
306
+ return "Remember to indent the function body."
307
+
308
+ # Return next available hint or generic encouragement
309
+ if exercise.attempts < len(exercise.hints):
310
+ return exercise.hints[exercise.attempts]
311
+
312
+ return "You're on the right track! Keep trying different approaches."
313
+
314
+ def explain_solution(self, exercise: InteractiveExercise) -> List[StepExplanation]:
315
+ """
316
+ Provide detailed explanation of the exercise solution.
317
+
318
+ Args:
319
+ exercise: The completed exercise
320
+
321
+ Returns:
322
+ List[StepExplanation]: Step-by-step solution explanation
323
+ """
324
+ # Get the expected solution from exercise templates
325
+ topic_lower = exercise.topic.lower()
326
+ template = self._get_exercise_template(topic_lower, exercise.difficulty_level)
327
+ solution_code = template.get('solution', exercise.expected_output)
328
+
329
+ # Generate step-by-step explanations for the solution
330
+ explanations = []
331
+ lines = solution_code.split('\n')
332
+
333
+ for i, line in enumerate(lines, 1):
334
+ line = line.strip()
335
+ if line and not line.startswith('#'): # Skip empty lines and comments
336
+ explanation = self.generate_step_explanation(line)
337
+ explanation.step_number = i
338
+ explanations.append(explanation)
339
+
340
+ return explanations
341
+
342
+ def _analyze_code_line(self, code_line: str, context: Optional[CodeContext]) -> Dict[str, Any]:
343
+ """Analyze a code line and determine its type and explanation."""
344
+ code_line = code_line.strip()
345
+
346
+ # Check against known patterns
347
+ for pattern_name, pattern_info in self._code_patterns.items():
348
+ if re.search(pattern_info['pattern'], code_line):
349
+ description = pattern_info['explanation']
350
+
351
+ # Extract specific information for templated explanations
352
+ if pattern_name == 'variable_assignment':
353
+ match = re.match(pattern_info['pattern'], code_line)
354
+ if match:
355
+ var_name, value = match.groups()
356
+ description = description.format(var=var_name, value=value)
357
+
358
+ elif pattern_name == 'function_call':
359
+ match = re.search(r'(\w+)\s*\(', code_line)
360
+ if match:
361
+ func_name = match.group(1)
362
+ description = description.format(func=func_name)
363
+
364
+ elif pattern_name == 'function_definition':
365
+ match = re.match(pattern_info['pattern'], code_line)
366
+ if match:
367
+ func_name = match.group(1)
368
+ description = description.format(func=func_name)
369
+
370
+ return {
371
+ 'description': description,
372
+ 'concepts': pattern_info['concepts'],
373
+ 'pattern': pattern_name
374
+ }
375
+
376
+ # Default explanation for unrecognized patterns
377
+ return {
378
+ 'description': f"Python code: {code_line}",
379
+ 'concepts': ['python_syntax'],
380
+ 'pattern': 'unknown'
381
+ }
382
+
383
+ def _generate_examples(self, code_line: str, explanation_data: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]:
384
+ """Generate input and output examples for a code line."""
385
+ pattern = explanation_data.get('pattern', 'unknown')
386
+
387
+ if pattern == 'variable_assignment':
388
+ # Extract the assignment
389
+ match = re.match(r'^(\w+)\s*=\s*(.+)$', code_line)
390
+ if match:
391
+ var_name, value = match.groups()
392
+ return f"Before: {var_name} is undefined", f"After: {var_name} = {value}"
393
+
394
+ elif pattern == 'print_statement':
395
+ # Extract what's being printed
396
+ match = re.search(r'print\s*\(([^)]*)\)', code_line)
397
+ if match:
398
+ content = match.group(1)
399
+ return f"Input: {content}", f"Output: (printed to screen)"
400
+
401
+ elif pattern == 'list_creation':
402
+ return "Input: individual items", "Output: organized list structure"
403
+
404
+ elif pattern == 'function_call':
405
+ return "Input: function arguments", "Output: function result"
406
+
407
+ return None, None
408
+
409
+ def _get_exercise_template(self, topic: str, difficulty: DifficultyLevel) -> Dict[str, Any]:
410
+ """Get exercise template for topic and difficulty."""
411
+ if topic in self._exercise_templates:
412
+ topic_templates = self._exercise_templates[topic]
413
+ if difficulty in topic_templates:
414
+ return topic_templates[difficulty]
415
+ # Fall back to beginner if difficulty not found
416
+ return topic_templates.get(DifficultyLevel.BEGINNER, self._get_default_template(topic))
417
+
418
+ # Return default template for unknown topics
419
+ return self._get_default_template(topic)
420
+
421
+ def _get_default_template(self, topic: str) -> Dict[str, Any]:
422
+ """Get default exercise template for unknown topics."""
423
+ return {
424
+ 'title': f'Practice with {topic.title()}',
425
+ 'description': f'Practice basic concepts related to {topic}.',
426
+ 'starter_code': f'# Write code to practice {topic}\n',
427
+ 'solution': f'# Solution for {topic} exercise',
428
+ 'hints': [
429
+ 'Read the exercise description carefully',
430
+ 'Start with simple examples',
431
+ 'Test your code step by step'
432
+ ]
433
+ }
434
+
435
+ def _check_syntax(self, code: str) -> List[str]:
436
+ """Check code for syntax errors."""
437
+ errors = []
438
+ try:
439
+ ast.parse(code)
440
+ except SyntaxError as e:
441
+ errors.append(f"Syntax error on line {e.lineno}: {e.msg}")
442
+ except Exception as e:
443
+ errors.append(f"Code error: {str(e)}")
444
+
445
+ return errors
446
+
447
+ def _validate_topic_specific(self, exercise: InteractiveExercise, solution: str) -> ValidationResult:
448
+ """Perform topic-specific validation of the solution."""
449
+ topic = exercise.topic.lower()
450
+
451
+ if topic == 'variables':
452
+ return self._validate_variables_exercise(exercise, solution)
453
+ elif topic == 'lists':
454
+ return self._validate_lists_exercise(exercise, solution)
455
+ elif topic == 'functions':
456
+ return self._validate_functions_exercise(exercise, solution)
457
+ elif topic == 'loops':
458
+ return self._validate_loops_exercise(exercise, solution)
459
+ else:
460
+ # Generic validation
461
+ return ValidationResult(
462
+ is_correct=True,
463
+ feedback="Code looks good! Well done.",
464
+ suggestions=["Keep practicing to improve your skills"]
465
+ )
466
+
467
+ def _validate_variables_exercise(self, exercise: InteractiveExercise, solution: str) -> ValidationResult:
468
+ """Validate variables exercise."""
469
+ if '=' not in solution:
470
+ return ValidationResult(
471
+ is_correct=False,
472
+ feedback="You need to assign values to variables using the = operator.",
473
+ suggestions=["Use variable_name = value to create variables"]
474
+ )
475
+
476
+ # Check if variables are created
477
+ lines = solution.split('\n')
478
+ assignments = [line for line in lines if '=' in line and not line.strip().startswith('#')]
479
+
480
+ if len(assignments) == 0:
481
+ return ValidationResult(
482
+ is_correct=False,
483
+ feedback="No variable assignments found.",
484
+ suggestions=["Create at least one variable assignment"]
485
+ )
486
+
487
+ return ValidationResult(
488
+ is_correct=True,
489
+ feedback="Great! You've successfully created variables.",
490
+ suggestions=["Try creating variables with different data types"]
491
+ )
492
+
493
+ def _validate_lists_exercise(self, exercise: InteractiveExercise, solution: str) -> ValidationResult:
494
+ """Validate lists exercise."""
495
+ if '[' not in solution or ']' not in solution:
496
+ return ValidationResult(
497
+ is_correct=False,
498
+ feedback="You need to create a list using square brackets [].",
499
+ suggestions=["Use [item1, item2, item3] to create a list"]
500
+ )
501
+
502
+ return ValidationResult(
503
+ is_correct=True,
504
+ feedback="Excellent! You've created a list successfully.",
505
+ suggestions=["Try adding or removing items from your list"]
506
+ )
507
+
508
+ def _validate_functions_exercise(self, exercise: InteractiveExercise, solution: str) -> ValidationResult:
509
+ """Validate functions exercise."""
510
+ if 'def ' not in solution:
511
+ return ValidationResult(
512
+ is_correct=False,
513
+ feedback="You need to define a function using the 'def' keyword.",
514
+ suggestions=["Use 'def function_name():' to define a function"]
515
+ )
516
+
517
+ if ':' not in solution:
518
+ return ValidationResult(
519
+ is_correct=False,
520
+ feedback="Function definitions need a colon (:) at the end.",
521
+ suggestions=["Add a colon after the function definition line"]
522
+ )
523
+
524
+ # Check for function call
525
+ lines = solution.split('\n')
526
+ has_call = any(line.strip() and not line.strip().startswith('def') and
527
+ not line.strip().startswith('#') and
528
+ not line.strip().startswith(' ') for line in lines)
529
+
530
+ if not has_call:
531
+ return ValidationResult(
532
+ is_correct=False,
533
+ feedback="Don't forget to call your function after defining it.",
534
+ suggestions=["Call your function by writing function_name()"]
535
+ )
536
+
537
+ return ValidationResult(
538
+ is_correct=True,
539
+ feedback="Perfect! You've defined and called a function.",
540
+ suggestions=["Try creating functions with parameters"]
541
+ )
542
+
543
+ def _validate_loops_exercise(self, exercise: InteractiveExercise, solution: str) -> ValidationResult:
544
+ """Validate loops exercise."""
545
+ if 'for ' not in solution:
546
+ return ValidationResult(
547
+ is_correct=False,
548
+ feedback="You need to create a for loop using the 'for' keyword.",
549
+ suggestions=["Use 'for item in collection:' to create a loop"]
550
+ )
551
+
552
+ if ' in ' not in solution:
553
+ return ValidationResult(
554
+ is_correct=False,
555
+ feedback="For loops need the 'in' keyword to iterate over a collection.",
556
+ suggestions=["Use 'for item in list:' syntax"]
557
+ )
558
+
559
+ return ValidationResult(
560
+ is_correct=True,
561
+ feedback="Great job! You've created a working loop.",
562
+ suggestions=["Try looping over different types of collections"]
563
+ )
564
+
565
+ def get_related_examples(self, topic: str, difficulty: DifficultyLevel = DifficultyLevel.BEGINNER):
566
+ """
567
+ Get related examples from the example repository.
568
+
569
+ Args:
570
+ topic: Topic to find examples for
571
+ difficulty: Difficulty level filter
572
+
573
+ Returns:
574
+ List of related examples if repository is available
575
+ """
576
+ if self._example_repository is None:
577
+ return []
578
+
579
+ try:
580
+ examples = self._example_repository.get_examples_by_topic(topic)
581
+ # Filter by difficulty if needed
582
+ filtered_examples = [
583
+ ex for ex in examples
584
+ if ex.difficulty == difficulty.value
585
+ ]
586
+ return filtered_examples[:3] # Limit to 3 most relevant
587
+ except Exception:
588
+ return []
589
+
590
+ def enhance_explanation_with_examples(self, explanation: StepExplanation, topic: str) -> StepExplanation:
591
+ """
592
+ Enhance a step explanation with related examples from the repository.
593
+
594
+ Args:
595
+ explanation: Original explanation
596
+ topic: Topic to find examples for
597
+
598
+ Returns:
599
+ Enhanced explanation with example references
600
+ """
601
+ if self._example_repository is None:
602
+ return explanation
603
+
604
+ try:
605
+ related_examples = self.get_related_examples(topic)
606
+ if related_examples:
607
+ # Add example references to the explanation
608
+ example_titles = [ex.title for ex in related_examples[:2]]
609
+ enhanced_description = explanation.description
610
+ if example_titles:
611
+ enhanced_description += f"\n\nRelated examples: {', '.join(example_titles)}"
612
+
613
+ # Create enhanced explanation
614
+ return StepExplanation(
615
+ step_number=explanation.step_number,
616
+ description=enhanced_description,
617
+ code_snippet=explanation.code_snippet,
618
+ input_example=explanation.input_example,
619
+ output_example=explanation.output_example,
620
+ related_concepts=explanation.related_concepts,
621
+ visual_aid=explanation.visual_aid
622
+ )
623
+ except Exception:
624
+ pass
625
+
626
+ return explanation