fishertools 0.2.1__py3-none-any.whl → 0.4.0__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 (69) hide show
  1. fishertools/__init__.py +16 -5
  2. fishertools/errors/__init__.py +11 -3
  3. fishertools/errors/exception_types.py +282 -0
  4. fishertools/errors/explainer.py +87 -1
  5. fishertools/errors/models.py +73 -1
  6. fishertools/errors/patterns.py +40 -0
  7. fishertools/examples/cli_example.py +156 -0
  8. fishertools/examples/learn_example.py +65 -0
  9. fishertools/examples/logger_example.py +176 -0
  10. fishertools/examples/menu_example.py +101 -0
  11. fishertools/examples/storage_example.py +175 -0
  12. fishertools/input_utils.py +185 -0
  13. fishertools/learn/__init__.py +19 -2
  14. fishertools/learn/examples.py +88 -1
  15. fishertools/learn/knowledge_engine.py +321 -0
  16. fishertools/learn/repl/__init__.py +19 -0
  17. fishertools/learn/repl/cli.py +31 -0
  18. fishertools/learn/repl/code_sandbox.py +229 -0
  19. fishertools/learn/repl/command_handler.py +544 -0
  20. fishertools/learn/repl/command_parser.py +165 -0
  21. fishertools/learn/repl/engine.py +479 -0
  22. fishertools/learn/repl/models.py +121 -0
  23. fishertools/learn/repl/session_manager.py +284 -0
  24. fishertools/learn/repl/test_code_sandbox.py +261 -0
  25. fishertools/learn/repl/test_code_sandbox_pbt.py +148 -0
  26. fishertools/learn/repl/test_command_handler.py +224 -0
  27. fishertools/learn/repl/test_command_handler_pbt.py +189 -0
  28. fishertools/learn/repl/test_command_parser.py +160 -0
  29. fishertools/learn/repl/test_command_parser_pbt.py +100 -0
  30. fishertools/learn/repl/test_engine.py +190 -0
  31. fishertools/learn/repl/test_session_manager.py +310 -0
  32. fishertools/learn/repl/test_session_manager_pbt.py +182 -0
  33. fishertools/learn/test_knowledge_engine.py +241 -0
  34. fishertools/learn/test_knowledge_engine_pbt.py +180 -0
  35. fishertools/patterns/__init__.py +46 -0
  36. fishertools/patterns/cli.py +175 -0
  37. fishertools/patterns/logger.py +140 -0
  38. fishertools/patterns/menu.py +99 -0
  39. fishertools/patterns/storage.py +127 -0
  40. fishertools/readme_transformer.py +631 -0
  41. fishertools/safe/__init__.py +6 -1
  42. fishertools/safe/files.py +329 -1
  43. fishertools/transform_readme.py +105 -0
  44. fishertools-0.4.0.dist-info/METADATA +104 -0
  45. fishertools-0.4.0.dist-info/RECORD +131 -0
  46. {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/WHEEL +1 -1
  47. tests/test_documentation_properties.py +329 -0
  48. tests/test_documentation_structure.py +349 -0
  49. tests/test_errors/test_exception_types.py +446 -0
  50. tests/test_errors/test_exception_types_pbt.py +333 -0
  51. tests/test_errors/test_patterns.py +52 -0
  52. tests/test_input_utils/__init__.py +1 -0
  53. tests/test_input_utils/test_input_utils.py +65 -0
  54. tests/test_learn/test_examples.py +179 -1
  55. tests/test_learn/test_explain_properties.py +307 -0
  56. tests/test_patterns_cli.py +611 -0
  57. tests/test_patterns_docstrings.py +473 -0
  58. tests/test_patterns_logger.py +465 -0
  59. tests/test_patterns_menu.py +440 -0
  60. tests/test_patterns_storage.py +447 -0
  61. tests/test_readme_enhancements_v0_3_1.py +2036 -0
  62. tests/test_readme_transformer/__init__.py +1 -0
  63. tests/test_readme_transformer/test_readme_infrastructure.py +1023 -0
  64. tests/test_readme_transformer/test_transform_readme_integration.py +431 -0
  65. tests/test_safe/test_files.py +726 -1
  66. fishertools-0.2.1.dist-info/METADATA +0 -256
  67. fishertools-0.2.1.dist-info/RECORD +0 -81
  68. {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/licenses/LICENSE +0 -0
  69. {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,431 @@
1
+ """
2
+ Integration tests for the README transformation script.
3
+
4
+ Tests the complete transformation process including:
5
+ - File reading and writing
6
+ - Backup creation and recovery
7
+ - Error handling for missing files
8
+ - End-to-end transformation workflow
9
+ """
10
+
11
+ import tempfile
12
+ from pathlib import Path
13
+
14
+ import pytest
15
+
16
+ from fishertools.transform_readme import transform_readme
17
+ from fishertools.readme_transformer import BackupManager
18
+
19
+
20
+ class TestTransformReadmeIntegration:
21
+ """Integration tests for the transform_readme function."""
22
+
23
+ def test_transform_readme_success(self, tmp_path: Path) -> None:
24
+ """Test successful README transformation."""
25
+ readme_path = tmp_path / "README.md"
26
+ test_content = "Introduction\n\n# Section\n\nContent"
27
+ readme_path.write_text(test_content, encoding="utf-8")
28
+
29
+ result = transform_readme(str(readme_path), create_backup=False)
30
+
31
+ assert result is True
32
+ transformed = readme_path.read_text(encoding="utf-8")
33
+ assert "pip install fishertools" in transformed
34
+ assert "Для кого эта библиотека" in transformed
35
+
36
+ def test_transform_readme_with_backup(self, tmp_path: Path) -> None:
37
+ """Test transformation with backup creation."""
38
+ readme_path = tmp_path / "README.md"
39
+ test_content = "Introduction\n\n# Section\n\nContent"
40
+ readme_path.write_text(test_content, encoding="utf-8")
41
+
42
+ # Change to tmp_path directory so backups are created there
43
+ import os
44
+ original_cwd = os.getcwd()
45
+ try:
46
+ os.chdir(tmp_path)
47
+ result = transform_readme(str(readme_path), create_backup=True)
48
+
49
+ assert result is True
50
+
51
+ # Verify backup was created
52
+ backup_dir = tmp_path / ".readme_backups"
53
+ assert backup_dir.exists()
54
+ backups = list(backup_dir.glob("README_backup_*.md"))
55
+ assert len(backups) > 0
56
+ finally:
57
+ os.chdir(original_cwd)
58
+
59
+ def test_transform_readme_file_not_found(self, tmp_path: Path) -> None:
60
+ """Test error handling when README file does not exist."""
61
+ readme_path = tmp_path / "nonexistent.md"
62
+
63
+ with pytest.raises(FileNotFoundError):
64
+ transform_readme(str(readme_path))
65
+
66
+ def test_transform_readme_preserves_content(self, tmp_path: Path) -> None:
67
+ """Test that transformation preserves original content."""
68
+ readme_path = tmp_path / "README.md"
69
+ original_intro = "This is the original introduction"
70
+ original_content = "This is the original detailed content"
71
+ test_content = f"{original_intro}\n\n# Section\n\n{original_content}"
72
+ readme_path.write_text(test_content, encoding="utf-8")
73
+
74
+ result = transform_readme(str(readme_path), create_backup=False)
75
+
76
+ assert result is True
77
+ transformed = readme_path.read_text(encoding="utf-8")
78
+ assert original_intro in transformed
79
+ assert original_content in transformed
80
+
81
+ def test_transform_readme_with_custom_features(self, tmp_path: Path) -> None:
82
+ """Test transformation with custom features."""
83
+ readme_path = tmp_path / "README.md"
84
+ test_content = "Introduction\n\n# Section\n\nContent"
85
+ readme_path.write_text(test_content, encoding="utf-8")
86
+
87
+ custom_features = [
88
+ {"task": "Custom Task 1", "function": "custom_func1()"},
89
+ {"task": "Custom Task 2", "function": "custom_func2()"},
90
+ ]
91
+
92
+ result = transform_readme(
93
+ str(readme_path), create_backup=False, features=custom_features
94
+ )
95
+
96
+ assert result is True
97
+ transformed = readme_path.read_text(encoding="utf-8")
98
+ assert "Custom Task 1" in transformed
99
+ assert "custom_func1()" in transformed
100
+ assert "Custom Task 2" in transformed
101
+ assert "custom_func2()" in transformed
102
+
103
+ def test_transform_readme_with_custom_audience(self, tmp_path: Path) -> None:
104
+ """Test transformation with custom target audience bullets."""
105
+ readme_path = tmp_path / "README.md"
106
+ test_content = "Introduction\n\n# Section\n\nContent"
107
+ readme_path.write_text(test_content, encoding="utf-8")
108
+
109
+ custom_bullets = [
110
+ "Custom bullet 1",
111
+ "Custom bullet 2",
112
+ "Custom bullet 3",
113
+ ]
114
+
115
+ result = transform_readme(
116
+ str(readme_path),
117
+ create_backup=False,
118
+ target_audience_bullets=custom_bullets,
119
+ )
120
+
121
+ assert result is True
122
+ transformed = readme_path.read_text(encoding="utf-8")
123
+ for bullet in custom_bullets:
124
+ assert bullet in transformed
125
+
126
+ def test_transform_readme_backup_recovery(self, tmp_path: Path) -> None:
127
+ """Test backup creation and recovery mechanism."""
128
+ readme_path = tmp_path / "README.md"
129
+ original_content = "Original content"
130
+ readme_path.write_text(original_content, encoding="utf-8")
131
+
132
+ # Change to tmp_path directory so backups are created there
133
+ import os
134
+ original_cwd = os.getcwd()
135
+ try:
136
+ os.chdir(tmp_path)
137
+ # Transform with backup
138
+ result = transform_readme(str(readme_path), create_backup=True)
139
+ assert result is True
140
+
141
+ # Get the backup file
142
+ backup_dir = tmp_path / ".readme_backups"
143
+ backups = list(backup_dir.glob("README_backup_*.md"))
144
+ assert len(backups) > 0
145
+
146
+ backup_path = backups[0]
147
+
148
+ # Verify backup contains original content
149
+ backup_content = backup_path.read_text(encoding="utf-8")
150
+ assert backup_content == original_content
151
+
152
+ # Modify the README
153
+ readme_path.write_text("Modified content", encoding="utf-8")
154
+
155
+ # Recover from backup
156
+ backup_manager = BackupManager(str(readme_path), str(backup_dir))
157
+ backup_manager.restore_backup(backup_path)
158
+
159
+ # Verify recovery
160
+ recovered_content = readme_path.read_text(encoding="utf-8")
161
+ assert recovered_content == original_content
162
+ finally:
163
+ os.chdir(original_cwd)
164
+
165
+ def test_transform_readme_multiple_backups(self, tmp_path: Path) -> None:
166
+ """Test creation of multiple backups."""
167
+ import time
168
+ import os
169
+
170
+ readme_path = tmp_path / "README.md"
171
+ readme_path.write_text("Content 1", encoding="utf-8")
172
+
173
+ original_cwd = os.getcwd()
174
+ try:
175
+ os.chdir(tmp_path)
176
+ # Create first backup
177
+ result1 = transform_readme(str(readme_path), create_backup=True)
178
+ assert result1 is True
179
+
180
+ # Wait to ensure different timestamp
181
+ time.sleep(1.1)
182
+
183
+ # Modify and create second backup
184
+ readme_path.write_text("Content 2", encoding="utf-8")
185
+ result2 = transform_readme(str(readme_path), create_backup=True)
186
+ assert result2 is True
187
+
188
+ # Verify both backups exist
189
+ backup_dir = tmp_path / ".readme_backups"
190
+ backups = list(backup_dir.glob("README_backup_*.md"))
191
+ assert len(backups) >= 2
192
+ finally:
193
+ os.chdir(original_cwd)
194
+
195
+ def test_transform_readme_validation_failure(self, tmp_path: Path) -> None:
196
+ """Test handling of validation failures."""
197
+ readme_path = tmp_path / "README.md"
198
+ # Create a README with problematic content
199
+ test_content = "Introduction\n\n# Section\n\nContent"
200
+ readme_path.write_text(test_content, encoding="utf-8")
201
+
202
+ # This should succeed with valid content
203
+ result = transform_readme(str(readme_path), create_backup=False)
204
+ assert result is True
205
+
206
+ def test_transform_readme_empty_file(self, tmp_path: Path) -> None:
207
+ """Test handling of empty README file."""
208
+ readme_path = tmp_path / "README.md"
209
+ readme_path.write_text("", encoding="utf-8")
210
+
211
+ # Should handle gracefully
212
+ result = transform_readme(str(readme_path), create_backup=False)
213
+
214
+ # Result depends on implementation - may succeed or fail gracefully
215
+ # The important thing is it doesn't crash
216
+ assert isinstance(result, bool)
217
+
218
+ def test_transform_readme_large_file(self, tmp_path: Path) -> None:
219
+ """Test handling of large README file."""
220
+ readme_path = tmp_path / "README.md"
221
+
222
+ # Create a large README with lots of content
223
+ large_content = "Introduction\n\n"
224
+ for i in range(100):
225
+ large_content += f"## Section {i}\n\nContent for section {i}\n\n"
226
+
227
+ readme_path.write_text(large_content, encoding="utf-8")
228
+
229
+ result = transform_readme(str(readme_path), create_backup=False)
230
+
231
+ assert result is True
232
+ transformed = readme_path.read_text(encoding="utf-8")
233
+ assert "pip install fishertools" in transformed
234
+ assert "Для кого эта библиотека" in transformed
235
+
236
+ def test_transform_readme_with_special_characters(self, tmp_path: Path) -> None:
237
+ """Test handling of special characters in content."""
238
+ readme_path = tmp_path / "README.md"
239
+ test_content = "Introduction with émojis 🐍\n\n# Section\n\nContent with special chars: @#$%"
240
+ readme_path.write_text(test_content, encoding="utf-8")
241
+
242
+ result = transform_readme(str(readme_path), create_backup=False)
243
+
244
+ assert result is True
245
+ transformed = readme_path.read_text(encoding="utf-8")
246
+ assert "émojis 🐍" in transformed
247
+ assert "@#$%" in transformed
248
+
249
+ def test_transform_readme_idempotent(self, tmp_path: Path) -> None:
250
+ """Test that transformation is idempotent (can be run multiple times)."""
251
+ readme_path = tmp_path / "README.md"
252
+ test_content = "Introduction\n\n# Section\n\nContent"
253
+ readme_path.write_text(test_content, encoding="utf-8")
254
+
255
+ # First transformation
256
+ result1 = transform_readme(str(readme_path), create_backup=False)
257
+ assert result1 is True
258
+ first_result = readme_path.read_text(encoding="utf-8")
259
+
260
+ # Second transformation - should not duplicate sections
261
+ result2 = transform_readme(str(readme_path), create_backup=False)
262
+ assert result2 is True
263
+ second_result = readme_path.read_text(encoding="utf-8")
264
+
265
+ # Both should have the required sections
266
+ assert "pip install fishertools" in first_result
267
+ assert "Для кого эта библиотека" in first_result
268
+ assert "pip install fishertools" in second_result
269
+ assert "Для кого эта библиотека" in second_result
270
+
271
+ # Count occurrences - should not increase significantly
272
+ first_install_count = first_result.count("pip install fishertools")
273
+ second_install_count = second_result.count("pip install fishertools")
274
+
275
+ # Allow for some variation but not doubling
276
+ assert second_install_count <= first_install_count + 1
277
+
278
+ def test_transform_readme_real_world_example(self, tmp_path: Path) -> None:
279
+ """Test transformation with a realistic README structure."""
280
+ readme_path = tmp_path / "README.md"
281
+ realistic_content = """# Fishertools
282
+
283
+ **Инструменты, которые делают Python удобнее и безопаснее для новичков**
284
+
285
+ Fishertools - это Python библиотека, созданная специально для начинающих разработчиков.
286
+
287
+ ## 🎯 Основные возможности
288
+
289
+ ### 🚨 Объяснение ошибок Python
290
+ Получайте понятные объяснения ошибок на русском языке.
291
+
292
+ ### 🛡️ Безопасные утилиты
293
+ Функции, которые предотвращают типичные ошибки новичков.
294
+
295
+ ## 📦 Установка
296
+
297
+ ```bash
298
+ pip install fishertools
299
+ ```
300
+
301
+ ## 🚀 Быстрый старт
302
+
303
+ ```python
304
+ from fishertools import explain_error
305
+ ```
306
+
307
+ ## 📖 Документация
308
+
309
+ Полная документация доступна на сайте.
310
+ """
311
+ readme_path.write_text(realistic_content, encoding="utf-8")
312
+
313
+ result = transform_readme(str(readme_path), create_backup=False)
314
+
315
+ assert result is True
316
+ transformed = readme_path.read_text(encoding="utf-8")
317
+
318
+ # Verify all required sections are present
319
+ assert "Fishertools" in transformed
320
+ assert "pip install fishertools" in transformed
321
+ assert "Для кого эта библиотека" in transformed
322
+ assert "Задача" in transformed
323
+ assert "Что вызвать" in transformed
324
+ assert "explain_error(e)" in transformed
325
+ assert "safe_read_file(path)" in transformed
326
+
327
+ def test_transform_readme_preserves_formatting(self, tmp_path: Path) -> None:
328
+ """Test that transformation preserves markdown formatting."""
329
+ readme_path = tmp_path / "README.md"
330
+ test_content = """Introduction
331
+
332
+ # Heading 1
333
+
334
+ ## Heading 2
335
+
336
+ ### Heading 3
337
+
338
+ - Bullet 1
339
+ - Bullet 2
340
+
341
+ 1. Numbered 1
342
+ 2. Numbered 2
343
+
344
+ **Bold text** and *italic text*
345
+
346
+ ```python
347
+ code_block()
348
+ ```
349
+ """
350
+ readme_path.write_text(test_content, encoding="utf-8")
351
+
352
+ result = transform_readme(str(readme_path), create_backup=False)
353
+
354
+ assert result is True
355
+ transformed = readme_path.read_text(encoding="utf-8")
356
+
357
+ # Verify formatting is preserved
358
+ assert "# Heading 1" in transformed
359
+ assert "## Heading 2" in transformed
360
+ assert "### Heading 3" in transformed
361
+ assert "- Bullet 1" in transformed
362
+ assert "1. Numbered 1" in transformed
363
+ assert "**Bold text**" in transformed
364
+ assert "*italic text*" in transformed
365
+ assert "```python" in transformed
366
+
367
+ def test_transform_readme_error_handling_io_error(self, tmp_path: Path) -> None:
368
+ """Test error handling for IO errors."""
369
+ readme_path = tmp_path / "README.md"
370
+ readme_path.write_text("Content", encoding="utf-8")
371
+
372
+ # Make the file read-only to simulate IO error on write
373
+ import os
374
+
375
+ os.chmod(readme_path, 0o444)
376
+
377
+ try:
378
+ # This should fail gracefully
379
+ result = transform_readme(str(readme_path), create_backup=False)
380
+ # Result should be False due to write error
381
+ assert result is False
382
+ finally:
383
+ # Restore permissions for cleanup
384
+ os.chmod(readme_path, 0o644)
385
+
386
+ def test_transform_readme_with_all_options(self, tmp_path: Path) -> None:
387
+ """Test transformation with all options specified."""
388
+ readme_path = tmp_path / "README.md"
389
+ test_content = "Introduction\n\n# Section\n\nContent"
390
+ readme_path.write_text(test_content, encoding="utf-8")
391
+
392
+ custom_features = [
393
+ {"task": "Task 1", "function": "func1()"},
394
+ {"task": "Task 2", "function": "func2()"},
395
+ ]
396
+
397
+ custom_bullets = [
398
+ "Bullet 1",
399
+ "Bullet 2",
400
+ "Bullet 3",
401
+ ]
402
+
403
+ import os
404
+ original_cwd = os.getcwd()
405
+ try:
406
+ os.chdir(tmp_path)
407
+ result = transform_readme(
408
+ str(readme_path),
409
+ create_backup=True,
410
+ features=custom_features,
411
+ target_audience_bullets=custom_bullets,
412
+ )
413
+
414
+ assert result is True
415
+
416
+ # Verify backup was created
417
+ backup_dir = tmp_path / ".readme_backups"
418
+ assert backup_dir.exists()
419
+
420
+ # Verify custom features are in the result
421
+ transformed = readme_path.read_text(encoding="utf-8")
422
+ assert "Task 1" in transformed
423
+ assert "func1()" in transformed
424
+ assert "Task 2" in transformed
425
+ assert "func2()" in transformed
426
+
427
+ # Verify custom bullets are in the result
428
+ for bullet in custom_bullets:
429
+ assert bullet in transformed
430
+ finally:
431
+ os.chdir(original_cwd)