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,444 @@
1
+ """
2
+ Property-based tests for visual documentation consistency.
3
+
4
+ Feature: fishertools-enhancements
5
+ Property 4: Visual Documentation Consistency
6
+ Validates: Requirements 4.1, 4.2, 4.3, 4.4, 4.5
7
+ """
8
+
9
+ import pytest
10
+ from hypothesis import given, strategies as st, assume
11
+ from fishertools.documentation import VisualDocumentation
12
+ from fishertools.documentation.models import (
13
+ FunctionInfo, MermaidDiagram, DiagramType,
14
+ FlowDiagram, Flowchart, StructureDiagram
15
+ )
16
+
17
+
18
+ # Test data generators
19
+ @st.composite
20
+ def generate_function_info(draw):
21
+ """Generate valid FunctionInfo for testing."""
22
+ name = draw(st.text(min_size=1, max_size=20, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd'), blacklist_characters='_')))
23
+ assume(name.isidentifier() and not name.startswith('_'))
24
+
25
+ docstring = draw(st.one_of(
26
+ st.none(),
27
+ st.text(min_size=10, max_size=200)
28
+ ))
29
+
30
+ # Generate parameters with type annotations
31
+ param_count = draw(st.integers(min_value=0, max_value=5))
32
+ parameters = {}
33
+ for i in range(param_count):
34
+ param_name = f"param_{i}"
35
+ param_type = draw(st.sampled_from(['str', 'int', 'float', 'bool', 'List[str]', 'Dict[str, Any]', 'Optional[str]']))
36
+ parameters[param_name] = param_type
37
+
38
+ return_type = draw(st.one_of(
39
+ st.none(),
40
+ st.sampled_from(['str', 'int', 'bool', 'List[str]', 'None'])
41
+ ))
42
+
43
+ return FunctionInfo(
44
+ name=name,
45
+ docstring=docstring,
46
+ parameters=parameters,
47
+ return_type=return_type,
48
+ module_path="/fake/path/module.py",
49
+ line_number=draw(st.integers(min_value=1, max_value=1000))
50
+ )
51
+
52
+
53
+ @st.composite
54
+ def generate_code_sample(draw):
55
+ """Generate valid Python code samples for testing."""
56
+ # Generate simple but valid Python code structures
57
+ code_templates = [
58
+ "def {name}():\n return {value}",
59
+ "if {condition}:\n print('{message}')\nelse:\n print('alternative')",
60
+ "for i in range({count}):\n print(i)",
61
+ "while {condition}:\n {action}\n break",
62
+ "x = {value}\nprint(x)\nreturn x"
63
+ ]
64
+
65
+ template = draw(st.sampled_from(code_templates))
66
+
67
+ # Fill in template variables
68
+ name = draw(st.text(min_size=3, max_size=15, alphabet=st.characters(whitelist_categories=('Lu', 'Ll'))))
69
+ assume(name.isidentifier())
70
+
71
+ value = draw(st.one_of(
72
+ st.integers(min_value=1, max_value=100),
73
+ st.text(min_size=1, max_size=20, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd'))),
74
+ st.sampled_from([True, False])
75
+ ))
76
+
77
+ condition = draw(st.sampled_from(['True', 'False', 'x > 0', 'len(data) > 0']))
78
+ message = draw(st.text(min_size=5, max_size=30, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd', 'Zs'))))
79
+ count = draw(st.integers(min_value=1, max_value=10))
80
+ action = draw(st.sampled_from(['x += 1', 'print("loop")', 'data.append(i)']))
81
+
82
+ try:
83
+ code = template.format(
84
+ name=name,
85
+ value=repr(value),
86
+ condition=condition,
87
+ message=message,
88
+ count=count,
89
+ action=action
90
+ )
91
+ return code
92
+ except (KeyError, ValueError):
93
+ # If template formatting fails, return a simple default
94
+ return f"def {name}():\n return {repr(value)}"
95
+
96
+
97
+ @st.composite
98
+ def generate_data_structure(draw):
99
+ """Generate various data structures for visualization testing."""
100
+ data_type = draw(st.sampled_from(['list', 'dict', 'set', 'string', 'int', 'float', 'bool', 'none']))
101
+
102
+ if data_type == 'list':
103
+ return draw(st.lists(st.integers(min_value=0, max_value=100), max_size=15))
104
+ elif data_type == 'dict':
105
+ return draw(st.dictionaries(
106
+ st.text(min_size=1, max_size=10, alphabet=st.characters(whitelist_categories=('Lu', 'Ll'))),
107
+ st.integers(min_value=0, max_value=100),
108
+ max_size=10
109
+ ))
110
+ elif data_type == 'set':
111
+ return draw(st.sets(st.integers(min_value=0, max_value=50), max_size=10))
112
+ elif data_type == 'string':
113
+ return draw(st.text(max_size=100))
114
+ elif data_type == 'int':
115
+ return draw(st.integers(min_value=-1000, max_value=1000))
116
+ elif data_type == 'float':
117
+ return draw(st.floats(min_value=-100.0, max_value=100.0, allow_nan=False, allow_infinity=False))
118
+ elif data_type == 'bool':
119
+ return draw(st.booleans())
120
+ else: # none
121
+ return None
122
+
123
+
124
+ class TestVisualDocumentationProperties:
125
+ """Property-based tests for visual documentation consistency."""
126
+
127
+ @given(
128
+ st.lists(st.text(min_size=1, max_size=20, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd'), blacklist_characters='_')), min_size=1, max_size=10),
129
+ st.sampled_from(['modern', 'classic', 'minimal'])
130
+ )
131
+ def test_architecture_diagram_consistency(self, modules, style):
132
+ """
133
+ Property 4.1: Architecture diagram consistency
134
+
135
+ For any list of modules and any style, the Visual_Documentation should
136
+ display architecture diagrams with consistent styling.
137
+
138
+ **Validates: Requirements 4.1, 4.5**
139
+ """
140
+ # Filter out invalid module names
141
+ valid_modules = [m for m in modules if m.isidentifier() and not m.startswith('_')]
142
+ assume(len(valid_modules) > 0)
143
+
144
+ visual_doc = VisualDocumentation(style=style)
145
+
146
+ # Generate architecture diagram
147
+ diagram = visual_doc.create_architecture_diagram(valid_modules)
148
+
149
+ # Verify diagram structure (4.1)
150
+ assert isinstance(diagram, MermaidDiagram)
151
+ assert diagram.diagram_type == DiagramType.ARCHITECTURE
152
+ assert diagram.content is not None
153
+ assert len(diagram.content.strip()) > 0
154
+ assert diagram.title == "Module Architecture"
155
+
156
+ # Verify all modules are included in the diagram
157
+ for module in valid_modules:
158
+ assert module in diagram.content
159
+
160
+ # Verify Mermaid syntax is valid (basic check)
161
+ assert "graph TB" in diagram.content or "graph TD" in diagram.content
162
+
163
+ # Apply consistent styling and verify consistency (4.5)
164
+ styled_diagram = visual_doc.apply_consistent_styling(diagram)
165
+
166
+ # Verify styling is applied consistently
167
+ assert isinstance(styled_diagram, MermaidDiagram)
168
+ assert styled_diagram.diagram_type == diagram.diagram_type
169
+ assert styled_diagram.title == diagram.title
170
+
171
+ # Verify style-specific elements are present
172
+ if style == "modern":
173
+ assert "#e1f5fe" in styled_diagram.content or "#01579b" in styled_diagram.content
174
+ elif style == "classic":
175
+ assert "#fff3e0" in styled_diagram.content or "#e65100" in styled_diagram.content
176
+
177
+ # Verify theme configuration is added
178
+ assert "%%{init:" in styled_diagram.content
179
+
180
+ @given(generate_function_info(), st.sampled_from(['modern', 'classic', 'minimal']))
181
+ def test_data_flow_diagram_consistency(self, function_info, style):
182
+ """
183
+ Property 4.2: Data flow diagram consistency
184
+
185
+ For any function and any style, the Visual_Documentation should
186
+ show data flow schemes with consistent styling.
187
+
188
+ **Validates: Requirements 4.2, 4.5**
189
+ """
190
+ visual_doc = VisualDocumentation(style=style)
191
+
192
+ # Generate data flow diagram
193
+ flow_diagram = visual_doc.generate_data_flow_diagram(function_info)
194
+
195
+ # Verify diagram structure (4.2)
196
+ assert isinstance(flow_diagram, FlowDiagram)
197
+ assert flow_diagram.title == f"Data Flow: {function_info.name}"
198
+ assert len(flow_diagram.nodes) >= 3 # At least input, process, output
199
+ assert len(flow_diagram.edges) >= 2 # At least input->process, process->output
200
+
201
+ # Verify required nodes are present
202
+ node_types = [node.get('type') for node in flow_diagram.nodes]
203
+ assert 'input' in node_types
204
+ assert 'process' in node_types
205
+ assert 'output' in node_types
206
+
207
+ # Verify function name appears in process node
208
+ process_nodes = [node for node in flow_diagram.nodes if node.get('type') == 'process']
209
+ assert any(function_info.name in node.get('label', '') for node in process_nodes)
210
+
211
+ # Verify edges connect properly
212
+ edge_froms = [edge.get('from') for edge in flow_diagram.edges]
213
+ edge_tos = [edge.get('to') for edge in flow_diagram.edges]
214
+ assert 'input' in edge_froms or any('param_' in ef for ef in edge_froms)
215
+ assert 'output' in edge_tos
216
+ assert 'process' in edge_tos
217
+
218
+ # If function has parameters, verify they are represented
219
+ if function_info.parameters:
220
+ param_nodes = [node for node in flow_diagram.nodes if node.get('type') == 'parameter']
221
+ assert len(param_nodes) == len(function_info.parameters)
222
+
223
+ @given(generate_code_sample(), st.sampled_from(['modern', 'classic', 'minimal']))
224
+ def test_algorithm_flowchart_consistency(self, code, style):
225
+ """
226
+ Property 4.4: Algorithm flowchart consistency
227
+
228
+ For any algorithm code and any style, the Visual_Documentation should
229
+ create flowcharts with consistent styling.
230
+
231
+ **Validates: Requirements 4.4, 4.5**
232
+ """
233
+ visual_doc = VisualDocumentation(style=style)
234
+
235
+ # Generate algorithm flowchart
236
+ flowchart = visual_doc.create_algorithm_flowchart(code)
237
+
238
+ # Verify flowchart structure (4.4)
239
+ assert isinstance(flowchart, Flowchart)
240
+ assert flowchart.title is not None
241
+ assert len(flowchart.title.strip()) > 0
242
+ assert len(flowchart.steps) > 0
243
+
244
+ # Verify each step has required fields
245
+ for step in flowchart.steps:
246
+ assert 'id' in step
247
+ assert 'type' in step
248
+ assert 'description' in step
249
+ assert 'line_number' in step
250
+ assert step['line_number'] > 0
251
+
252
+ # Verify connections are logical
253
+ if len(flowchart.steps) > 1:
254
+ assert len(flowchart.connections) >= len(flowchart.steps) - 1
255
+
256
+ # Verify connection structure
257
+ for connection in flowchart.connections:
258
+ assert 'from' in connection
259
+ assert 'to' in connection
260
+
261
+ # Verify referenced steps exist
262
+ step_ids = [step['id'] for step in flowchart.steps]
263
+ assert connection['from'] in step_ids
264
+ assert connection['to'] in step_ids
265
+
266
+ # Verify step types are appropriate
267
+ valid_step_types = ['start', 'end', 'process', 'decision', 'loop', 'output']
268
+ for step in flowchart.steps:
269
+ assert step['type'] in valid_step_types
270
+
271
+ @given(generate_data_structure(), st.sampled_from(['modern', 'classic', 'minimal']))
272
+ def test_data_structure_visualization_consistency(self, data, style):
273
+ """
274
+ Property 4.3: Data structure visualization consistency
275
+
276
+ For any data structure and any style, the Visual_Documentation should
277
+ provide visual representations with consistent styling.
278
+
279
+ **Validates: Requirements 4.3, 4.5**
280
+ """
281
+ visual_doc = VisualDocumentation(style=style)
282
+
283
+ # Generate data structure visualization
284
+ struct_diagram = visual_doc.visualize_data_structure(data)
285
+
286
+ # Verify diagram structure (4.3)
287
+ assert isinstance(struct_diagram, StructureDiagram)
288
+ assert struct_diagram.structure_type is not None
289
+ assert struct_diagram.visualization is not None
290
+ assert struct_diagram.title is not None
291
+
292
+ # Verify structure type matches data type
293
+ if data is None:
294
+ assert struct_diagram.structure_type == "None"
295
+ elif isinstance(data, bool): # Check bool before int since bool is subclass of int
296
+ assert struct_diagram.structure_type == "bool"
297
+ elif isinstance(data, int):
298
+ assert struct_diagram.structure_type == "int"
299
+ elif isinstance(data, list):
300
+ assert struct_diagram.structure_type == "list"
301
+ elif isinstance(data, tuple):
302
+ assert struct_diagram.structure_type == "tuple"
303
+ elif isinstance(data, dict):
304
+ assert struct_diagram.structure_type == "dict"
305
+ elif isinstance(data, set):
306
+ assert struct_diagram.structure_type == "set"
307
+ elif isinstance(data, str):
308
+ assert struct_diagram.structure_type == "string"
309
+ elif isinstance(data, float):
310
+ assert struct_diagram.structure_type == "float"
311
+
312
+ # Verify visualization is meaningful
313
+ assert len(struct_diagram.visualization.strip()) > 0
314
+
315
+ # For collections, verify size information is included when appropriate
316
+ if isinstance(data, (list, tuple, dict, set)) and len(data) > 10:
317
+ assert "..." in struct_diagram.visualization
318
+ assert "total" in struct_diagram.visualization
319
+
320
+ @given(generate_code_sample(), generate_data_structure(), st.sampled_from(['modern', 'classic', 'minimal']))
321
+ def test_example_visualization_consistency(self, code, result, style):
322
+ """
323
+ Property 4.3: Example visualization consistency
324
+
325
+ For any code example and result, the Visual_Documentation should
326
+ create consistent visual representations.
327
+
328
+ **Validates: Requirements 4.3, 4.5**
329
+ """
330
+ visual_doc = VisualDocumentation(style=style)
331
+
332
+ # Generate example visualization
333
+ html_viz = visual_doc.create_example_visualization(code, result)
334
+
335
+ # Verify HTML structure (4.3)
336
+ assert isinstance(html_viz, str)
337
+ assert len(html_viz.strip()) > 0
338
+
339
+ # Verify required HTML elements are present
340
+ assert '<div class="example-visualization">' in html_viz
341
+ assert '<div class="example-header">' in html_viz
342
+ assert '<div class="code-section">' in html_viz
343
+ assert '<div class="result-section">' in html_viz
344
+ assert '<style>' in html_viz
345
+
346
+ # Verify code is properly escaped and included
347
+ # The _escape_html method escapes single quotes as &#x27;
348
+ escaped_code = code.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace("'", '&#x27;').replace('"', '&quot;')
349
+ assert escaped_code in html_viz
350
+
351
+ # Verify result visualization is included
352
+ result_viz = visual_doc.visualize_data_structure(result)
353
+ escaped_result = result_viz.visualization.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#x27;')
354
+ assert escaped_result in html_viz
355
+
356
+ # Verify style-specific CSS is applied (4.5)
357
+ if style == "modern":
358
+ assert "#01579b" in html_viz or "#4a148c" in html_viz
359
+ elif style == "classic":
360
+ assert "#e65100" in html_viz or "#880e4f" in html_viz
361
+
362
+ # Verify consistent structure across all styles
363
+ assert 'font-family:' in html_viz
364
+ assert 'background:' in html_viz
365
+ assert 'border:' in html_viz
366
+
367
+ @given(
368
+ st.lists(st.text(min_size=1, max_size=15, alphabet=st.characters(whitelist_categories=('Lu', 'Ll'))), min_size=1, max_size=5),
369
+ st.sampled_from(['modern', 'classic', 'minimal'])
370
+ )
371
+ def test_consistent_styling_across_diagram_types(self, modules, style):
372
+ """
373
+ Property 4.5: Consistent styling across all visual elements
374
+
375
+ For any visual documentation elements and any style, all diagrams
376
+ should maintain consistent styling themes.
377
+
378
+ **Validates: Requirements 4.5**
379
+ """
380
+ # Filter valid modules
381
+ valid_modules = [m for m in modules if m.isidentifier()]
382
+ assume(len(valid_modules) > 0)
383
+
384
+ visual_doc = VisualDocumentation(style=style)
385
+
386
+ # Generate different types of diagrams
387
+ arch_diagram = visual_doc.create_architecture_diagram(valid_modules)
388
+
389
+ # Apply styling to all diagrams
390
+ styled_arch = visual_doc.apply_consistent_styling(arch_diagram)
391
+
392
+ # Verify consistent theme application
393
+ diagrams = [styled_arch]
394
+
395
+ # All diagrams should have theme configuration
396
+ for diagram in diagrams:
397
+ assert "%%{init:" in diagram.content
398
+
399
+ # Verify style-specific consistency
400
+ if style == "modern":
401
+ color_palette = ["#e1f5fe", "#01579b", "#4a148c", "#f3e5f5"]
402
+ for diagram in diagrams:
403
+ # At least one color from the modern palette should be present
404
+ assert any(color in diagram.content for color in color_palette)
405
+ elif style == "classic":
406
+ color_palette = ["#fff3e0", "#e65100", "#880e4f", "#fce4ec"]
407
+ for diagram in diagrams:
408
+ # At least one color from the classic palette should be present
409
+ assert any(color in diagram.content for color in color_palette)
410
+ else: # minimal
411
+ # Minimal style should have more neutral colors
412
+ neutral_indicators = ["#f5f5f5", "#333", "#666", "#999"]
413
+ for diagram in diagrams:
414
+ assert any(indicator in diagram.content for indicator in neutral_indicators)
415
+
416
+ def test_visual_documentation_error_handling(self):
417
+ """
418
+ Test that visual documentation handles edge cases gracefully.
419
+
420
+ Validates that the system provides meaningful errors for invalid inputs
421
+ while maintaining consistency.
422
+ """
423
+ visual_doc = VisualDocumentation()
424
+
425
+ # Test empty module list
426
+ with pytest.raises(ValueError, match="At least one module must be provided"):
427
+ visual_doc.create_architecture_diagram([])
428
+
429
+ # Test empty code
430
+ with pytest.raises(ValueError, match="Code cannot be empty"):
431
+ visual_doc.create_algorithm_flowchart("")
432
+
433
+ with pytest.raises(ValueError, match="Code cannot be empty"):
434
+ visual_doc.create_example_visualization("", "result")
435
+
436
+ # Test empty diagram content
437
+ empty_diagram = MermaidDiagram(DiagramType.ARCHITECTURE, "", "Test")
438
+ with pytest.raises(ValueError, match="Diagram content cannot be empty"):
439
+ visual_doc.apply_consistent_styling(empty_diagram)
440
+
441
+ # Test function with empty name
442
+ empty_func = FunctionInfo("", None, {}, None, "/path", 1)
443
+ with pytest.raises(ValueError, match="Function must have a name"):
444
+ visual_doc.generate_data_flow_diagram(empty_func)
@@ -0,0 +1,3 @@
1
+ """
2
+ Tests for the error explanation system.
3
+ """