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