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,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
|