vfbquery 0.4.1__py3-none-any.whl → 0.5.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.
- test/readme_parser.py +29 -27
- test/term_info_queries_test.py +46 -34
- test/test_dataset_template_queries.py +138 -0
- test/test_default_caching.py +89 -84
- test/test_examples_code.py +7 -0
- test/test_examples_diff.py +95 -172
- test/test_expression_overlaps.py +183 -0
- test/test_expression_pattern_fragments.py +123 -0
- test/test_images_neurons.py +152 -0
- test/test_images_that_develop_from.py +112 -0
- test/test_lineage_clones_in.py +190 -0
- test/test_nblast_queries.py +124 -0
- test/test_neuron_classes_fasciculating.py +187 -0
- test/test_neuron_inputs.py +193 -0
- test/test_neuron_neuron_connectivity.py +89 -0
- test/test_neuron_region_connectivity.py +117 -0
- test/test_neurons_part_here.py +203 -0
- test/test_new_owlery_queries.py +282 -0
- test/test_publication_transgene_queries.py +101 -0
- test/test_query_performance.py +739 -0
- test/test_similar_morphology.py +177 -0
- test/test_tracts_nerves_innervating.py +188 -0
- test/test_transcriptomics.py +223 -0
- vfbquery/__init__.py +47 -35
- vfbquery/cached_functions.py +772 -131
- vfbquery/neo4j_client.py +120 -0
- vfbquery/owlery_client.py +463 -0
- vfbquery/solr_cache_integration.py +34 -30
- vfbquery/solr_fetcher.py +1 -1
- vfbquery/solr_result_cache.py +338 -36
- vfbquery/term_info_queries.py +1 -1
- vfbquery/vfb_queries.py +2969 -627
- vfbquery-0.5.1.dist-info/METADATA +2806 -0
- vfbquery-0.5.1.dist-info/RECORD +40 -0
- vfbquery-0.4.1.dist-info/METADATA +0 -1315
- vfbquery-0.4.1.dist-info/RECORD +0 -19
- {vfbquery-0.4.1.dist-info → vfbquery-0.5.1.dist-info}/LICENSE +0 -0
- {vfbquery-0.4.1.dist-info → vfbquery-0.5.1.dist-info}/WHEEL +0 -0
- {vfbquery-0.4.1.dist-info → vfbquery-0.5.1.dist-info}/top_level.txt +0 -0
test/test_default_caching.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Test VFBquery default caching functionality.
|
|
3
3
|
|
|
4
|
-
These tests ensure that the
|
|
5
|
-
|
|
4
|
+
These tests ensure that the SOLR-based caching system works correctly
|
|
5
|
+
and provides expected performance benefits with 3-month TTL.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import unittest
|
|
@@ -12,161 +12,166 @@ from unittest.mock import MagicMock
|
|
|
12
12
|
import sys
|
|
13
13
|
|
|
14
14
|
# Mock vispy imports before importing vfbquery
|
|
15
|
-
for module in ['vispy', 'vispy.scene', 'vispy.util', 'vispy.util.fonts',
|
|
16
|
-
'vispy.util.fonts._triage', 'vispy.util.fonts._quartz',
|
|
17
|
-
'vispy.ext', 'vispy.ext.cocoapy', 'navis', 'navis.plotting',
|
|
15
|
+
for module in ['vispy', 'vispy.scene', 'vispy.util', 'vispy.util.fonts',
|
|
16
|
+
'vispy.util.fonts._triage', 'vispy.util.fonts._quartz',
|
|
17
|
+
'vispy.ext', 'vispy.ext.cocoapy', 'navis', 'navis.plotting',
|
|
18
18
|
'navis.plotting.vispy', 'navis.plotting.vispy.viewer']:
|
|
19
19
|
sys.modules[module] = MagicMock()
|
|
20
20
|
|
|
21
21
|
# Set environment variables
|
|
22
22
|
os.environ.update({
|
|
23
23
|
'MPLBACKEND': 'Agg',
|
|
24
|
-
'VISPY_GL_LIB': 'osmesa',
|
|
24
|
+
'VISPY_GL_LIB': 'osmesa',
|
|
25
25
|
'VISPY_USE_EGL': '0',
|
|
26
26
|
'VFBQUERY_CACHE_ENABLED': 'true'
|
|
27
27
|
})
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class TestDefaultCaching(unittest.TestCase):
|
|
31
|
-
"""Test default caching behavior in VFBquery."""
|
|
32
|
-
|
|
31
|
+
"""Test default SOLR caching behavior in VFBquery."""
|
|
32
|
+
|
|
33
33
|
def setUp(self):
|
|
34
34
|
"""Set up test environment."""
|
|
35
35
|
# Clear any existing cache before each test
|
|
36
36
|
try:
|
|
37
37
|
import vfbquery
|
|
38
|
-
if hasattr(vfbquery, '
|
|
39
|
-
|
|
38
|
+
if hasattr(vfbquery, 'clear_solr_cache'):
|
|
39
|
+
# Clear cache for a test term
|
|
40
|
+
vfbquery.clear_solr_cache('term_info', 'FBbt_00003748')
|
|
40
41
|
except ImportError:
|
|
41
42
|
pass
|
|
42
|
-
|
|
43
|
+
|
|
43
44
|
def test_caching_enabled_by_default(self):
|
|
44
|
-
"""Test that caching is automatically enabled when importing vfbquery."""
|
|
45
|
+
"""Test that SOLR caching is automatically enabled when importing vfbquery."""
|
|
45
46
|
import vfbquery
|
|
46
|
-
|
|
47
|
-
# Check that caching functions are available
|
|
48
|
-
self.assertTrue(hasattr(vfbquery, '
|
|
49
|
-
self.assertTrue(hasattr(vfbquery, '
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
stats
|
|
53
|
-
|
|
54
|
-
self.
|
|
55
|
-
self.
|
|
47
|
+
|
|
48
|
+
# Check that SOLR caching functions are available
|
|
49
|
+
self.assertTrue(hasattr(vfbquery, 'get_solr_cache'))
|
|
50
|
+
self.assertTrue(hasattr(vfbquery, 'clear_solr_cache'))
|
|
51
|
+
self.assertTrue(hasattr(vfbquery, 'get_solr_cache_stats_func'))
|
|
52
|
+
|
|
53
|
+
# Check that caching is enabled (we can't easily check SOLR stats without network calls)
|
|
54
|
+
# But we can verify the infrastructure is in place
|
|
55
|
+
self.assertTrue(hasattr(vfbquery, '__caching_available__'))
|
|
56
|
+
self.assertTrue(vfbquery.__caching_available__)
|
|
56
57
|
|
|
57
58
|
def test_cache_performance_improvement(self):
|
|
58
|
-
"""Test that caching provides performance improvement."""
|
|
59
|
+
"""Test that SOLR caching provides performance improvement."""
|
|
59
60
|
import vfbquery
|
|
60
|
-
|
|
61
|
+
|
|
61
62
|
test_term = 'FBbt_00003748' # medulla
|
|
62
|
-
|
|
63
|
+
|
|
63
64
|
# First call (cold - populates cache)
|
|
64
65
|
start_time = time.time()
|
|
65
66
|
result1 = vfbquery.get_term_info(test_term)
|
|
66
67
|
cold_time = time.time() - start_time
|
|
67
|
-
|
|
68
|
+
|
|
68
69
|
# Verify we got a result
|
|
69
70
|
self.assertIsNotNone(result1)
|
|
70
71
|
if result1 is not None:
|
|
71
72
|
self.assertIn('Name', result1)
|
|
72
|
-
|
|
73
|
+
|
|
73
74
|
# Second call (warm - should hit cache)
|
|
74
|
-
start_time = time.time()
|
|
75
|
+
start_time = time.time()
|
|
75
76
|
result2 = vfbquery.get_term_info(test_term)
|
|
76
77
|
warm_time = time.time() - start_time
|
|
77
|
-
|
|
78
|
-
# Verify
|
|
78
|
+
|
|
79
|
+
# Verify caching is working (results should be identical)
|
|
79
80
|
self.assertIsNotNone(result2)
|
|
80
81
|
self.assertEqual(result1, result2) # Should be identical
|
|
81
|
-
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
# Check cache statistics
|
|
86
|
-
|
|
87
|
-
self.
|
|
88
|
-
self.
|
|
82
|
+
|
|
83
|
+
# Note: Performance improvement may vary due to network conditions
|
|
84
|
+
# The main test is that caching prevents redundant computation
|
|
85
|
+
|
|
86
|
+
# Check SOLR cache statistics
|
|
87
|
+
solr_stats = vfbquery.get_solr_cache_stats_func()
|
|
88
|
+
self.assertIsInstance(solr_stats, dict)
|
|
89
|
+
self.assertIn('total_cache_documents', solr_stats)
|
|
89
90
|
|
|
90
91
|
def test_cache_statistics_tracking(self):
|
|
91
|
-
"""Test that cache statistics are properly tracked."""
|
|
92
|
+
"""Test that SOLR cache statistics are properly tracked."""
|
|
92
93
|
import vfbquery
|
|
93
|
-
|
|
94
|
-
#
|
|
95
|
-
vfbquery.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
# Make a unique query that won't be cached
|
|
94
|
+
|
|
95
|
+
# Get baseline SOLR stats
|
|
96
|
+
initial_stats = vfbquery.get_solr_cache_stats_func()
|
|
97
|
+
initial_docs = initial_stats['total_cache_documents']
|
|
98
|
+
|
|
99
|
+
# Make a unique query that should populate cache
|
|
101
100
|
unique_term = 'FBbt_00005106' # Use a different term
|
|
102
101
|
result = vfbquery.get_term_info(unique_term)
|
|
103
102
|
self.assertIsNotNone(result)
|
|
104
|
-
|
|
105
|
-
# Check that stats were updated
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
self.
|
|
110
|
-
self.
|
|
111
|
-
self.assertGreaterEqual(updated_stats['memory_cache_size_mb'], 0)
|
|
103
|
+
|
|
104
|
+
# Check that SOLR stats were updated (may take time to reflect)
|
|
105
|
+
# We mainly verify the stats function works and returns reasonable data
|
|
106
|
+
updated_stats = vfbquery.get_solr_cache_stats_func()
|
|
107
|
+
self.assertIsInstance(updated_stats, dict)
|
|
108
|
+
self.assertIn('total_cache_documents', updated_stats)
|
|
109
|
+
self.assertIn('cache_efficiency', updated_stats)
|
|
112
110
|
|
|
113
111
|
def test_memory_size_tracking(self):
|
|
114
|
-
"""Test that
|
|
112
|
+
"""Test that SOLR cache size is properly tracked."""
|
|
115
113
|
import vfbquery
|
|
116
|
-
|
|
117
|
-
# Clear cache to start fresh
|
|
118
|
-
vfbquery.clear_vfbquery_cache()
|
|
119
|
-
|
|
114
|
+
|
|
120
115
|
# Cache a few different terms
|
|
121
116
|
test_terms = ['FBbt_00003748', 'VFB_00101567']
|
|
122
|
-
|
|
117
|
+
|
|
123
118
|
for term in test_terms:
|
|
124
|
-
vfbquery.get_term_info(term)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
#
|
|
128
|
-
|
|
129
|
-
self.
|
|
119
|
+
result = vfbquery.get_term_info(term)
|
|
120
|
+
self.assertIsNotNone(result)
|
|
121
|
+
|
|
122
|
+
# Check SOLR cache stats are available
|
|
123
|
+
stats = vfbquery.get_solr_cache_stats_func()
|
|
124
|
+
self.assertIsInstance(stats, dict)
|
|
125
|
+
self.assertIn('estimated_size_mb', stats)
|
|
126
|
+
self.assertGreaterEqual(stats['estimated_size_mb'], 0)
|
|
130
127
|
|
|
131
128
|
def test_cache_ttl_configuration(self):
|
|
132
|
-
"""Test that cache TTL is properly configured."""
|
|
129
|
+
"""Test that SOLR cache TTL is properly configured."""
|
|
133
130
|
import vfbquery
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
|
|
132
|
+
# Get SOLR cache instance to check TTL
|
|
133
|
+
solr_cache = vfbquery.get_solr_cache()
|
|
134
|
+
self.assertIsNotNone(solr_cache)
|
|
135
|
+
|
|
136
|
+
# Check that TTL is configured (we can't easily check the exact value without accessing private attributes)
|
|
137
|
+
# But we can verify the cache object exists and has expected methods
|
|
138
|
+
self.assertTrue(hasattr(solr_cache, 'ttl_hours'))
|
|
139
|
+
self.assertTrue(hasattr(solr_cache, 'cache_result'))
|
|
140
|
+
self.assertTrue(hasattr(solr_cache, 'get_cached_result'))
|
|
140
141
|
|
|
141
142
|
def test_transparent_caching(self):
|
|
142
143
|
"""Test that regular VFBquery functions are transparently cached."""
|
|
143
144
|
import vfbquery
|
|
144
|
-
|
|
145
|
+
|
|
145
146
|
# Test that get_term_info and get_instances are using cached versions
|
|
146
147
|
test_term = 'FBbt_00003748'
|
|
147
|
-
|
|
148
|
+
|
|
148
149
|
# These should work with caching transparently
|
|
149
150
|
term_info = vfbquery.get_term_info(test_term)
|
|
150
151
|
self.assertIsNotNone(term_info)
|
|
151
|
-
|
|
152
|
+
|
|
152
153
|
instances = vfbquery.get_instances(test_term, limit=5)
|
|
153
154
|
self.assertIsNotNone(instances)
|
|
154
|
-
|
|
155
|
-
#
|
|
156
|
-
|
|
157
|
-
self.
|
|
155
|
+
|
|
156
|
+
# SOLR cache should be accessible
|
|
157
|
+
solr_stats = vfbquery.get_solr_cache_stats_func()
|
|
158
|
+
self.assertIsInstance(solr_stats, dict)
|
|
159
|
+
self.assertIn('total_cache_documents', solr_stats)
|
|
158
160
|
|
|
159
161
|
def test_cache_disable_environment_variable(self):
|
|
160
162
|
"""Test that caching can be disabled via environment variable."""
|
|
161
163
|
# This test would need to be run in a separate process to test
|
|
162
164
|
# the environment variable behavior at import time
|
|
163
165
|
# For now, just verify the current state respects the env var
|
|
164
|
-
|
|
166
|
+
|
|
165
167
|
cache_enabled = os.getenv('VFBQUERY_CACHE_ENABLED', 'true').lower()
|
|
166
168
|
if cache_enabled not in ('false', '0', 'no', 'off'):
|
|
167
169
|
import vfbquery
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
# If caching is enabled, SOLR cache should be available
|
|
171
|
+
solr_cache = vfbquery.get_solr_cache()
|
|
172
|
+
self.assertIsNotNone(solr_cache)
|
|
173
|
+
self.assertTrue(hasattr(vfbquery, '__caching_available__'))
|
|
174
|
+
self.assertTrue(vfbquery.__caching_available__)
|
|
170
175
|
|
|
171
176
|
|
|
172
177
|
if __name__ == '__main__':
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
results = [
|
|
2
|
+
"vfb.get_term_info('FBbt_00003748', force_refresh=True)",
|
|
3
|
+
"vfb.get_term_info('VFB_00000001')",
|
|
4
|
+
"vfb.get_term_info('VFB_00101567')",
|
|
5
|
+
"vfb.get_instances('FBbt_00003748', return_dataframe=False, force_refresh=True)",
|
|
6
|
+
"vfb.get_templates(return_dataframe=False)",
|
|
7
|
+
]
|
test/test_examples_diff.py
CHANGED
|
@@ -102,6 +102,26 @@ def format_for_readme(data):
|
|
|
102
102
|
except Exception as e:
|
|
103
103
|
return f"Error formatting JSON: {str(e)}"
|
|
104
104
|
|
|
105
|
+
def sort_rows_in_data(data):
|
|
106
|
+
"""Sort rows in data structures by id to ensure consistent ordering"""
|
|
107
|
+
if isinstance(data, dict):
|
|
108
|
+
result = {}
|
|
109
|
+
for k, v in data.items():
|
|
110
|
+
if k == 'rows' and isinstance(v, list):
|
|
111
|
+
# Sort rows by id if they have id field
|
|
112
|
+
try:
|
|
113
|
+
sorted_rows = sorted(v, key=lambda x: x.get('id', '') if isinstance(x, dict) else str(x))
|
|
114
|
+
result[k] = sorted_rows
|
|
115
|
+
except (TypeError, AttributeError):
|
|
116
|
+
result[k] = v
|
|
117
|
+
else:
|
|
118
|
+
result[k] = sort_rows_in_data(v)
|
|
119
|
+
return result
|
|
120
|
+
elif isinstance(data, list):
|
|
121
|
+
return [sort_rows_in_data(item) for item in data]
|
|
122
|
+
else:
|
|
123
|
+
return data
|
|
124
|
+
|
|
105
125
|
def remove_nulls(data):
|
|
106
126
|
if isinstance(data, dict):
|
|
107
127
|
new_dict = {}
|
|
@@ -124,199 +144,102 @@ def remove_nulls(data):
|
|
|
124
144
|
def main():
|
|
125
145
|
init(autoreset=True)
|
|
126
146
|
|
|
127
|
-
# Import the
|
|
147
|
+
# Import the python code blocks
|
|
128
148
|
try:
|
|
129
|
-
from
|
|
130
|
-
from test_examples import results as python_blocks
|
|
149
|
+
from .test_examples_code import results as python_blocks
|
|
131
150
|
except ImportError as e:
|
|
132
151
|
print(f"{Fore.RED}Error importing test files: {e}{Style.RESET_ALL}")
|
|
133
152
|
sys.exit(1)
|
|
134
153
|
|
|
135
154
|
print(f'Found {len(python_blocks)} Python code blocks')
|
|
136
|
-
print(f'Found {len(json_blocks)} JSON blocks')
|
|
137
|
-
|
|
138
|
-
if len(python_blocks) != len(json_blocks):
|
|
139
|
-
print(f"{Fore.RED}Error: Number of Python blocks ({len(python_blocks)}) doesn't match JSON blocks ({len(json_blocks)}){Style.RESET_ALL}")
|
|
140
|
-
sys.exit(1)
|
|
141
155
|
|
|
142
156
|
failed = False
|
|
143
157
|
|
|
144
|
-
for i,
|
|
145
|
-
python_code = stringify_numeric_keys(python_code)
|
|
146
|
-
expected_json = stringify_numeric_keys(expected_json)
|
|
158
|
+
for i, python_code in enumerate(python_blocks):
|
|
147
159
|
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
expected_json_filtered = remove_nulls(expected_json)
|
|
151
|
-
diff = DeepDiff(expected_json_filtered, python_code_filtered,
|
|
152
|
-
ignore_order=True,
|
|
153
|
-
ignore_numeric_type_changes=True,
|
|
154
|
-
report_repetition=True,
|
|
155
|
-
verbose_level=2)
|
|
160
|
+
print(f'\n{Fore.CYAN}Example #{i+1}:{Style.RESET_ALL}')
|
|
161
|
+
print(f' README query: {python_code}')
|
|
156
162
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
# Execute the python code and get result
|
|
164
|
+
try:
|
|
165
|
+
# Evaluate the code to get the result
|
|
166
|
+
result = eval(python_code)
|
|
160
167
|
|
|
161
|
-
#
|
|
162
|
-
if '
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if 'dictionary_item_removed' in diff:
|
|
187
|
-
print(f'\n{Fore.RED}Removed keys:{Style.RESET_ALL}')
|
|
188
|
-
for item in diff['dictionary_item_removed']:
|
|
189
|
-
key = item.replace('root', '')
|
|
190
|
-
path_parts = key.strip('[]').split('][')
|
|
191
|
-
|
|
192
|
-
# Get the actual value that was removed
|
|
193
|
-
current = expected_json
|
|
194
|
-
for part in path_parts:
|
|
195
|
-
if part.startswith("'") and part.endswith("'"):
|
|
196
|
-
part = part.strip("'")
|
|
197
|
-
elif part.startswith('"') and part.endswith('"'):
|
|
198
|
-
part = part.strip('"')
|
|
199
|
-
try:
|
|
200
|
-
if part.startswith('number:'):
|
|
201
|
-
part = float(part.split(':')[1])
|
|
202
|
-
current = current[part]
|
|
203
|
-
except (KeyError, TypeError):
|
|
204
|
-
current = '[Unable to access path]'
|
|
205
|
-
break
|
|
206
|
-
|
|
207
|
-
print(f' {Fore.RED}-{key}: {get_brief_dict_representation(current)}{Style.RESET_ALL}')
|
|
208
|
-
|
|
209
|
-
if 'values_changed' in diff:
|
|
210
|
-
print(f'\n{Fore.YELLOW}Changed values:{Style.RESET_ALL}')
|
|
211
|
-
for key, value in diff['values_changed'].items():
|
|
212
|
-
path = key.replace('root', '')
|
|
213
|
-
old_val = value.get('old_value', 'N/A')
|
|
214
|
-
new_val = value.get('new_value', 'N/A')
|
|
215
|
-
print(f' {Fore.YELLOW}{path}:{Style.RESET_ALL}')
|
|
216
|
-
print(f' {Fore.RED}- {old_val}{Style.RESET_ALL}')
|
|
217
|
-
print(f' {Fore.GREEN}+ {new_val}{Style.RESET_ALL}')
|
|
168
|
+
# Validate structure based on function
|
|
169
|
+
if 'get_term_info' in python_code:
|
|
170
|
+
# Should be a dict with specific keys
|
|
171
|
+
if not isinstance(result, dict):
|
|
172
|
+
print(f'{Fore.RED}get_term_info should return a dict{Style.RESET_ALL}')
|
|
173
|
+
failed = True
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
expected_keys = ['IsIndividual', 'IsClass', 'Images', 'Examples', 'Domains', 'Licenses', 'Publications', 'Synonyms']
|
|
177
|
+
for key in expected_keys:
|
|
178
|
+
if key not in result:
|
|
179
|
+
print(f'{Fore.RED}Missing key: {key}{Style.RESET_ALL}')
|
|
180
|
+
failed = True
|
|
181
|
+
elif key in ['IsIndividual', 'IsClass'] and not isinstance(result[key], bool):
|
|
182
|
+
print(f'{Fore.RED}Key {key} is not bool: {type(result[key])}{Style.RESET_ALL}')
|
|
183
|
+
failed = True
|
|
184
|
+
|
|
185
|
+
if 'SuperTypes' in result and not isinstance(result['SuperTypes'], list):
|
|
186
|
+
print(f'{Fore.RED}SuperTypes is not list{Style.RESET_ALL}')
|
|
187
|
+
failed = True
|
|
188
|
+
|
|
189
|
+
if 'Queries' in result and not isinstance(result['Queries'], list):
|
|
190
|
+
print(f'{Fore.RED}Queries is not list{Style.RESET_ALL}')
|
|
191
|
+
failed = True
|
|
218
192
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
print(f' {Fore.GREEN}+{path}: {value}{Style.RESET_ALL}')
|
|
193
|
+
elif 'get_instances' in python_code:
|
|
194
|
+
# Should be a list of dicts or a dict with rows
|
|
195
|
+
if isinstance(result, list):
|
|
196
|
+
if len(result) > 0 and not isinstance(result[0], dict):
|
|
197
|
+
print(f'{Fore.RED}get_instances items should be dicts{Style.RESET_ALL}')
|
|
198
|
+
failed = True
|
|
199
|
+
elif isinstance(result, dict):
|
|
200
|
+
# Check if it has 'rows' key
|
|
201
|
+
if 'rows' not in result:
|
|
202
|
+
print(f'{Fore.RED}get_instances dict should have "rows" key{Style.RESET_ALL}')
|
|
203
|
+
failed = True
|
|
204
|
+
elif not isinstance(result['rows'], list):
|
|
205
|
+
print(f'{Fore.RED}get_instances "rows" should be list{Style.RESET_ALL}')
|
|
206
|
+
failed = True
|
|
207
|
+
else:
|
|
208
|
+
print(f'{Fore.RED}get_instances should return a list or dict, got {type(result)}{Style.RESET_ALL}')
|
|
209
|
+
failed = True
|
|
210
|
+
continue
|
|
238
211
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
print(f' {Fore.RED}-{path}:{Style.RESET_ALL}')
|
|
246
|
-
if isinstance(value, dict):
|
|
247
|
-
for k, v in value.items():
|
|
248
|
-
brief_v = get_brief_dict_representation(v)
|
|
249
|
-
print(f' {Fore.RED}-{k}: {brief_v}{Style.RESET_ALL}')
|
|
250
|
-
else:
|
|
251
|
-
# Fixed the problematic line by breaking it into simpler parts
|
|
252
|
-
items = value[:3]
|
|
253
|
-
items_str = ", ".join([get_brief_dict_representation(item) for item in items])
|
|
254
|
-
ellipsis = "..." if len(value) > 3 else ""
|
|
255
|
-
print(f' {Fore.RED}[{items_str}{ellipsis}]{Style.RESET_ALL}')
|
|
256
|
-
else:
|
|
257
|
-
print(f' {Fore.RED}-{path}: {value}{Style.RESET_ALL}')
|
|
258
|
-
|
|
259
|
-
# For comparing complex row objects that have significant differences
|
|
260
|
-
if 'iterable_item_added' in diff and 'iterable_item_removed' in diff:
|
|
261
|
-
added_rows = [(k, v) for k, v in diff['iterable_item_added'].items() if 'rows' in k]
|
|
262
|
-
removed_rows = [(k, v) for k, v in diff['iterable_item_removed'].items() if 'rows' in k]
|
|
212
|
+
elif 'get_templates' in python_code:
|
|
213
|
+
# Should be a dict with rows
|
|
214
|
+
if not isinstance(result, dict):
|
|
215
|
+
print(f'{Fore.RED}get_templates should return a dict{Style.RESET_ALL}')
|
|
216
|
+
failed = True
|
|
217
|
+
continue
|
|
263
218
|
|
|
264
|
-
if
|
|
265
|
-
print(f'
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if isinstance(added_val, dict) and isinstance(removed_val, dict):
|
|
272
|
-
# Compare the two row objects and show key differences
|
|
273
|
-
row_diff = compare_objects(removed_val, added_val, f'Row {i}')
|
|
274
|
-
if row_diff:
|
|
275
|
-
print(f' {Fore.YELLOW}Row {i} differences:{Style.RESET_ALL}')
|
|
276
|
-
for line in row_diff:
|
|
277
|
-
print(f' {line}')
|
|
219
|
+
if 'rows' not in result:
|
|
220
|
+
print(f'{Fore.RED}get_templates dict should have "rows" key{Style.RESET_ALL}')
|
|
221
|
+
failed = True
|
|
222
|
+
elif not isinstance(result['rows'], list):
|
|
223
|
+
print(f'{Fore.RED}get_templates "rows" should be list{Style.RESET_ALL}')
|
|
224
|
+
failed = True
|
|
278
225
|
|
|
279
|
-
|
|
280
|
-
print(f'
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
old_type = type(value.get('old_value', 'N/A')).__name__
|
|
284
|
-
new_type = type(value.get('new_value', 'N/A')).__name__
|
|
285
|
-
old_val = value.get('old_value', 'N/A')
|
|
286
|
-
new_val = value.get('new_value', 'N/A')
|
|
287
|
-
print(f' {Fore.YELLOW}{path}:{Style.RESET_ALL}')
|
|
288
|
-
print(f' {Fore.RED}- {old_type}: {str(old_val)[:100] + "..." if len(str(old_val)) > 100 else old_val}{Style.RESET_ALL}')
|
|
289
|
-
print(f' {Fore.GREEN}+ {new_type}: {str(new_val)[:100] + "..." if len(str(new_val)) > 100 else new_val}{Style.RESET_ALL}')
|
|
290
|
-
|
|
291
|
-
# Print a summary of the differences
|
|
292
|
-
print(f'\n{Fore.YELLOW}Summary of differences:{Style.RESET_ALL}')
|
|
293
|
-
add_keys = len(diff.get('dictionary_item_added', []))
|
|
294
|
-
add_items = len(diff.get('iterable_item_added', {}))
|
|
295
|
-
rem_keys = len(diff.get('dictionary_item_removed', []))
|
|
296
|
-
rem_items = len(diff.get('iterable_item_removed', {}))
|
|
297
|
-
changed_vals = len(diff.get('values_changed', {}))
|
|
298
|
-
type_changes = len(diff.get('type_changes', {}))
|
|
226
|
+
else:
|
|
227
|
+
print(f'{Fore.RED}Unknown function in code{Style.RESET_ALL}')
|
|
228
|
+
failed = True
|
|
229
|
+
continue
|
|
299
230
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
print(f' {Fore.YELLOW}Changed:{Style.RESET_ALL} {changed_vals} values, {type_changes} type changes')
|
|
303
|
-
|
|
304
|
-
# After printing the summary, add the formatted output for README
|
|
305
|
-
print(f'\n{Fore.CYAN}Suggested README update for example #{i+1}:{Style.RESET_ALL}')
|
|
231
|
+
if not failed:
|
|
232
|
+
print(f'{Fore.GREEN}Structure validation passed{Style.RESET_ALL}')
|
|
306
233
|
|
|
307
|
-
|
|
308
|
-
print(f'
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
else:
|
|
313
|
-
print(f'\n{Fore.GREEN}Example #{i+1}: ✓ PASS{Style.RESET_ALL}')
|
|
314
|
-
|
|
234
|
+
except Exception as e:
|
|
235
|
+
print(f'{Fore.RED}Error executing code: {e}{Style.RESET_ALL}')
|
|
236
|
+
failed = True
|
|
237
|
+
|
|
315
238
|
if failed:
|
|
316
|
-
print(f'\n{Fore.RED}Some
|
|
239
|
+
print(f'\n{Fore.RED}Some tests failed{Style.RESET_ALL}')
|
|
317
240
|
sys.exit(1)
|
|
318
241
|
else:
|
|
319
|
-
print(f'\n{Fore.GREEN}All
|
|
242
|
+
print(f'\n{Fore.GREEN}All tests passed{Style.RESET_ALL}')
|
|
320
243
|
|
|
321
244
|
if __name__ == "__main__":
|
|
322
245
|
main()
|