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,112 @@
1
+ """
2
+ Data models for the Learning System module.
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+ from datetime import datetime
7
+ from typing import List, Optional, Any, Dict, Literal
8
+ from enum import Enum
9
+
10
+
11
+ class DifficultyLevel(Enum):
12
+ """Difficulty levels for learning content."""
13
+ BEGINNER = "beginner"
14
+ INTERMEDIATE = "intermediate"
15
+ ADVANCED = "advanced"
16
+
17
+
18
+ class ExerciseStatus(Enum):
19
+ """Status of an interactive exercise."""
20
+ NOT_STARTED = "not_started"
21
+ IN_PROGRESS = "in_progress"
22
+ COMPLETED = "completed"
23
+ FAILED = "failed"
24
+
25
+
26
+ @dataclass
27
+ class CodeContext:
28
+ """Context information for code analysis."""
29
+ file_path: Optional[str] = None
30
+ function_name: Optional[str] = None
31
+ class_name: Optional[str] = None
32
+ imports: List[str] = None
33
+ variables: Dict[str, Any] = None
34
+
35
+ def __post_init__(self):
36
+ if self.imports is None:
37
+ self.imports = []
38
+ if self.variables is None:
39
+ self.variables = {}
40
+
41
+
42
+ @dataclass
43
+ class StepExplanation:
44
+ """Detailed explanation of a single code step."""
45
+ step_number: int
46
+ description: str
47
+ code_snippet: str
48
+ input_example: Optional[str] = None
49
+ output_example: Optional[str] = None
50
+ related_concepts: List[str] = None
51
+ visual_aid: Optional[str] = None
52
+
53
+ def __post_init__(self):
54
+ if self.related_concepts is None:
55
+ self.related_concepts = []
56
+
57
+
58
+ @dataclass
59
+ class ValidationResult:
60
+ """Result of validating an exercise solution."""
61
+ is_correct: bool
62
+ feedback: str
63
+ errors: List[str] = None
64
+ suggestions: List[str] = None
65
+
66
+ def __post_init__(self):
67
+ if self.errors is None:
68
+ self.errors = []
69
+ if self.suggestions is None:
70
+ self.suggestions = []
71
+
72
+
73
+ @dataclass
74
+ class InteractiveExercise:
75
+ """An interactive coding exercise."""
76
+ id: str
77
+ title: str
78
+ description: str
79
+ starter_code: str
80
+ expected_output: str
81
+ hints: List[str]
82
+ difficulty_level: DifficultyLevel
83
+ topic: str
84
+ status: ExerciseStatus = ExerciseStatus.NOT_STARTED
85
+ attempts: int = 0
86
+ max_attempts: int = 3
87
+
88
+
89
+ @dataclass
90
+ class LearningProgress:
91
+ """User's learning progress tracking."""
92
+ user_id: str
93
+ completed_topics: List[str]
94
+ current_level: DifficultyLevel
95
+ total_exercises_completed: int
96
+ last_activity: datetime
97
+ achievements: List[str]
98
+ session_count: int = 0
99
+ total_time_spent: int = 0 # in minutes
100
+
101
+
102
+ @dataclass
103
+ class TutorialSession:
104
+ """A tutorial learning session."""
105
+ session_id: str
106
+ topic: str
107
+ level: DifficultyLevel
108
+ start_time: datetime
109
+ exercises: List[InteractiveExercise]
110
+ current_exercise_index: int = 0
111
+ is_completed: bool = False
112
+ end_time: Optional[datetime] = None
@@ -0,0 +1,314 @@
1
+ """
2
+ Progress tracking system for learning activities.
3
+ """
4
+
5
+ import json
6
+ import os
7
+ from typing import List, Optional, Dict
8
+ from datetime import datetime
9
+ from .models import LearningProgress, DifficultyLevel
10
+
11
+
12
+ class ProgressSystem:
13
+ """
14
+ Tracks user learning progress and manages achievements.
15
+
16
+ Provides persistent progress tracking between sessions and
17
+ suggests appropriate next steps based on completion status.
18
+ """
19
+
20
+ def __init__(self, storage_path: Optional[str] = None):
21
+ """
22
+ Initialize the progress system.
23
+
24
+ Args:
25
+ storage_path: Optional path for persistent storage
26
+ """
27
+ self.storage_path = storage_path or os.path.expanduser("~/.fishertools_progress")
28
+ self._progress_data: Dict[str, LearningProgress] = {}
29
+
30
+ # Topic progression paths
31
+ self._topic_progression = {
32
+ "beginner": [
33
+ "variables", "data_types", "input_output", "operators",
34
+ "conditionals", "loops", "lists", "functions"
35
+ ],
36
+ "intermediate": [
37
+ "dictionaries", "file_operations", "error_handling",
38
+ "modules", "classes", "list_comprehensions"
39
+ ],
40
+ "advanced": [
41
+ "decorators", "generators", "context_managers",
42
+ "metaclasses", "async_programming", "testing"
43
+ ]
44
+ }
45
+
46
+ # Achievement definitions
47
+ self._achievements = {
48
+ "first_steps": "Completed first tutorial",
49
+ "variable_master": "Mastered variables and data types",
50
+ "loop_expert": "Completed all loop exercises",
51
+ "function_guru": "Created 10 functions",
52
+ "error_handler": "Learned error handling",
53
+ "level_up": "Advanced to next difficulty level",
54
+ "persistent_learner": "Studied for 5 consecutive days",
55
+ "completionist": "Finished all beginner topics"
56
+ }
57
+
58
+ def create_user_profile(self, user_id: str, initial_level: DifficultyLevel = DifficultyLevel.BEGINNER) -> LearningProgress:
59
+ """
60
+ Create a new user progress profile.
61
+
62
+ Args:
63
+ user_id: Unique identifier for the user
64
+ initial_level: Starting difficulty level
65
+
66
+ Returns:
67
+ LearningProgress: New progress profile
68
+ """
69
+ if not user_id or not isinstance(user_id, str):
70
+ raise ValueError("User ID must be a non-empty string")
71
+
72
+ progress = LearningProgress(
73
+ user_id=user_id,
74
+ completed_topics=[],
75
+ current_level=initial_level,
76
+ total_exercises_completed=0,
77
+ last_activity=datetime.now(),
78
+ achievements=[],
79
+ session_count=0,
80
+ total_time_spent=0
81
+ )
82
+
83
+ self._progress_data[user_id] = progress
84
+ self.save_progress(user_id)
85
+
86
+ return progress
87
+
88
+ def update_progress(self, user_id: str, topic: str, completed: bool) -> None:
89
+ """
90
+ Update user progress for a specific topic.
91
+
92
+ Args:
93
+ user_id: Unique identifier for the user
94
+ topic: Topic that was studied
95
+ completed: Whether the topic was completed successfully
96
+ """
97
+ if not user_id or not isinstance(user_id, str):
98
+ raise ValueError("User ID must be a non-empty string")
99
+
100
+ if not topic or not isinstance(topic, str):
101
+ raise ValueError("Topic must be a non-empty string")
102
+
103
+ # Get or create user progress
104
+ progress = self.get_progress(user_id)
105
+ if not progress:
106
+ progress = self.create_user_profile(user_id)
107
+
108
+ # Update last activity
109
+ progress.last_activity = datetime.now()
110
+
111
+ # Add topic to completed list if completed and not already there
112
+ if completed and topic not in progress.completed_topics:
113
+ progress.completed_topics.append(topic)
114
+ progress.total_exercises_completed += 1
115
+
116
+ # Check for achievements
117
+ self._check_achievements(progress, topic)
118
+
119
+ # Check for level progression
120
+ self._check_level_progression(progress)
121
+
122
+ self.save_progress(user_id)
123
+
124
+ def get_progress(self, user_id: str) -> Optional[LearningProgress]:
125
+ """
126
+ Get current progress for a user.
127
+
128
+ Args:
129
+ user_id: Unique identifier for the user
130
+
131
+ Returns:
132
+ Optional[LearningProgress]: User's progress or None if not found
133
+ """
134
+ if not user_id or not isinstance(user_id, str):
135
+ return None
136
+
137
+ # Try to get from memory first
138
+ if user_id in self._progress_data:
139
+ return self._progress_data[user_id]
140
+
141
+ # Try to load from storage
142
+ return self.load_progress(user_id)
143
+
144
+ def suggest_next_topics(self, user_id: str) -> List[str]:
145
+ """
146
+ Suggest appropriate next topics based on user progress.
147
+
148
+ Args:
149
+ user_id: Unique identifier for the user
150
+
151
+ Returns:
152
+ List[str]: List of suggested topic names
153
+ """
154
+ progress = self.get_progress(user_id)
155
+ if not progress:
156
+ # Return beginner topics for new users
157
+ return self._topic_progression["beginner"][:3]
158
+
159
+ level_key = progress.current_level.value
160
+ available_topics = self._topic_progression.get(level_key, [])
161
+
162
+ # Filter out completed topics
163
+ suggested = [topic for topic in available_topics
164
+ if topic not in progress.completed_topics]
165
+
166
+ # If all topics at current level are completed, suggest next level
167
+ if not suggested and level_key == "beginner":
168
+ suggested = self._topic_progression["intermediate"][:3]
169
+ elif not suggested and level_key == "intermediate":
170
+ suggested = self._topic_progression["advanced"][:3]
171
+
172
+ return suggested[:5] # Limit to 5 suggestions
173
+
174
+ def add_achievement(self, user_id: str, achievement: str) -> None:
175
+ """
176
+ Add an achievement to the user's profile.
177
+
178
+ Args:
179
+ user_id: Unique identifier for the user
180
+ achievement: Achievement name or description
181
+ """
182
+ progress = self.get_progress(user_id)
183
+ if progress and achievement not in progress.achievements:
184
+ progress.achievements.append(achievement)
185
+ self.save_progress(user_id)
186
+
187
+ def save_progress(self, user_id: str) -> None:
188
+ """
189
+ Save user progress to persistent storage.
190
+
191
+ Args:
192
+ user_id: Unique identifier for the user
193
+ """
194
+ progress = self._progress_data.get(user_id)
195
+ if not progress:
196
+ return
197
+
198
+ try:
199
+ # Ensure storage directory exists
200
+ os.makedirs(os.path.dirname(self.storage_path), exist_ok=True)
201
+
202
+ # Load existing data
203
+ all_progress = {}
204
+ if os.path.exists(self.storage_path):
205
+ with open(self.storage_path, 'r', encoding='utf-8') as f:
206
+ all_progress = json.load(f)
207
+
208
+ # Convert progress to serializable format
209
+ progress_dict = {
210
+ "user_id": progress.user_id,
211
+ "completed_topics": progress.completed_topics,
212
+ "current_level": progress.current_level.value,
213
+ "total_exercises_completed": progress.total_exercises_completed,
214
+ "last_activity": progress.last_activity.isoformat(),
215
+ "achievements": progress.achievements,
216
+ "session_count": progress.session_count,
217
+ "total_time_spent": progress.total_time_spent
218
+ }
219
+
220
+ # Update and save
221
+ all_progress[user_id] = progress_dict
222
+ with open(self.storage_path, 'w', encoding='utf-8') as f:
223
+ json.dump(all_progress, f, indent=2)
224
+
225
+ except (OSError, json.JSONEncodeError) as e:
226
+ # Silently fail - progress tracking shouldn't break the system
227
+ pass
228
+
229
+ def load_progress(self, user_id: str) -> Optional[LearningProgress]:
230
+ """
231
+ Load user progress from persistent storage.
232
+
233
+ Args:
234
+ user_id: Unique identifier for the user
235
+
236
+ Returns:
237
+ Optional[LearningProgress]: Loaded progress or None if not found
238
+ """
239
+ try:
240
+ if not os.path.exists(self.storage_path):
241
+ return None
242
+
243
+ with open(self.storage_path, 'r', encoding='utf-8') as f:
244
+ all_progress = json.load(f)
245
+
246
+ progress_dict = all_progress.get(user_id)
247
+ if not progress_dict:
248
+ return None
249
+
250
+ # Convert back to LearningProgress object
251
+ progress = LearningProgress(
252
+ user_id=progress_dict["user_id"],
253
+ completed_topics=progress_dict["completed_topics"],
254
+ current_level=DifficultyLevel(progress_dict["current_level"]),
255
+ total_exercises_completed=progress_dict["total_exercises_completed"],
256
+ last_activity=datetime.fromisoformat(progress_dict["last_activity"]),
257
+ achievements=progress_dict["achievements"],
258
+ session_count=progress_dict.get("session_count", 0),
259
+ total_time_spent=progress_dict.get("total_time_spent", 0)
260
+ )
261
+
262
+ # Cache in memory
263
+ self._progress_data[user_id] = progress
264
+ return progress
265
+
266
+ except (OSError, json.JSONDecodeError, KeyError, ValueError):
267
+ # Return None if loading fails
268
+ return None
269
+
270
+ def _check_achievements(self, progress: LearningProgress, completed_topic: str) -> None:
271
+ """Check and award achievements based on progress."""
272
+ # First tutorial completion
273
+ if len(progress.completed_topics) == 1:
274
+ self.add_achievement(progress.user_id, self._achievements["first_steps"])
275
+
276
+ # Topic-specific achievements
277
+ if completed_topic == "variables" and "data_types" in progress.completed_topics:
278
+ self.add_achievement(progress.user_id, self._achievements["variable_master"])
279
+
280
+ if completed_topic in ["for_loops", "while_loops"] and all(
281
+ topic in progress.completed_topics for topic in ["for_loops", "while_loops"]
282
+ ):
283
+ self.add_achievement(progress.user_id, self._achievements["loop_expert"])
284
+
285
+ if completed_topic == "error_handling":
286
+ self.add_achievement(progress.user_id, self._achievements["error_handler"])
287
+
288
+ # Milestone achievements
289
+ if progress.total_exercises_completed >= 10:
290
+ self.add_achievement(progress.user_id, self._achievements["function_guru"])
291
+
292
+ # Check if all beginner topics completed
293
+ beginner_topics = set(self._topic_progression["beginner"])
294
+ completed_topics = set(progress.completed_topics)
295
+ if beginner_topics.issubset(completed_topics):
296
+ self.add_achievement(progress.user_id, self._achievements["completionist"])
297
+
298
+ def _check_level_progression(self, progress: LearningProgress) -> None:
299
+ """Check if user should advance to next level."""
300
+ current_level = progress.current_level.value
301
+ current_topics = set(self._topic_progression.get(current_level, []))
302
+ completed_topics = set(progress.completed_topics)
303
+
304
+ # Check if 80% of current level topics are completed
305
+ if len(current_topics) > 0:
306
+ completion_rate = len(current_topics.intersection(completed_topics)) / len(current_topics)
307
+
308
+ if completion_rate >= 0.8:
309
+ if current_level == "beginner":
310
+ progress.current_level = DifficultyLevel.INTERMEDIATE
311
+ self.add_achievement(progress.user_id, self._achievements["level_up"])
312
+ elif current_level == "intermediate":
313
+ progress.current_level = DifficultyLevel.ADVANCED
314
+ self.add_achievement(progress.user_id, self._achievements["level_up"])