vfbquery 0.3.4__py3-none-any.whl → 0.4.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 -1
- test/term_info_queries_test.py +58 -0
- test/test_default_caching.py +173 -0
- test/test_examples_diff.py +6 -1
- vfbquery/__init__.py +83 -1
- vfbquery/cache_enhancements.py +465 -0
- vfbquery/cached_functions.py +227 -0
- vfbquery/solr_cache_integration.py +212 -0
- vfbquery/solr_fetcher.py +47 -3
- vfbquery/solr_result_cache.py +659 -0
- vfbquery/vfb_queries.py +315 -73
- {vfbquery-0.3.4.dist-info → vfbquery-0.4.1.dist-info}/METADATA +16 -16
- vfbquery-0.4.1.dist-info/RECORD +19 -0
- vfbquery-0.3.4.dist-info/RECORD +0 -14
- {vfbquery-0.3.4.dist-info → vfbquery-0.4.1.dist-info}/LICENSE +0 -0
- {vfbquery-0.3.4.dist-info → vfbquery-0.4.1.dist-info}/WHEEL +0 -0
- {vfbquery-0.3.4.dist-info → vfbquery-0.4.1.dist-info}/top_level.txt +0 -0
test/readme_parser.py
CHANGED
|
@@ -27,7 +27,35 @@ def extract_code_blocks(readme_path):
|
|
|
27
27
|
# Look for vfb.* calls and extract them
|
|
28
28
|
vfb_calls = re.findall(r'(vfb\.[^)]*\))', block)
|
|
29
29
|
if vfb_calls:
|
|
30
|
-
|
|
30
|
+
# Add force_refresh=True to each call to ensure fresh data in tests
|
|
31
|
+
# Exceptions:
|
|
32
|
+
# - get_templates() doesn't support force_refresh (no SOLR cache)
|
|
33
|
+
# - Performance test terms (FBbt_00003748, VFB_00101567) should use cache
|
|
34
|
+
for call in vfb_calls:
|
|
35
|
+
# Check if this is get_templates() - if so, don't add force_refresh
|
|
36
|
+
if 'get_templates' in call:
|
|
37
|
+
processed_python_blocks.append(call)
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
# Check if this call uses performance test terms - skip force_refresh for those
|
|
41
|
+
if 'FBbt_00003748' in call or 'VFB_00101567' in call:
|
|
42
|
+
processed_python_blocks.append(call)
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
# Check if the call already has parameters
|
|
46
|
+
if '(' in call and ')' in call:
|
|
47
|
+
# Insert force_refresh=True before the closing parenthesis
|
|
48
|
+
# Handle both cases: with and without existing parameters
|
|
49
|
+
if call.rstrip(')').endswith('('):
|
|
50
|
+
# No parameters: vfb.function()
|
|
51
|
+
modified_call = call[:-1] + 'force_refresh=True)'
|
|
52
|
+
else:
|
|
53
|
+
# Has parameters: vfb.function(param1, param2)
|
|
54
|
+
modified_call = call[:-1] + ', force_refresh=True)'
|
|
55
|
+
processed_python_blocks.append(modified_call)
|
|
56
|
+
else:
|
|
57
|
+
# Shouldn't happen, but include original call if no parentheses
|
|
58
|
+
processed_python_blocks.append(call)
|
|
31
59
|
|
|
32
60
|
# Process JSON blocks
|
|
33
61
|
processed_json_blocks = []
|
test/term_info_queries_test.py
CHANGED
|
@@ -524,6 +524,64 @@ class TermInfoQueriesTest(unittest.TestCase):
|
|
|
524
524
|
self.assertFalse("filemeta" in serialized)
|
|
525
525
|
self.assertFalse("template" in serialized)
|
|
526
526
|
|
|
527
|
+
def test_term_info_performance(self):
|
|
528
|
+
"""
|
|
529
|
+
Performance test for specific term info queries.
|
|
530
|
+
Tests the execution time for FBbt_00003748 and VFB_00101567.
|
|
531
|
+
"""
|
|
532
|
+
import vfbquery as vfb
|
|
533
|
+
|
|
534
|
+
# Test performance for FBbt_00003748 (mushroom body)
|
|
535
|
+
start_time = time.time()
|
|
536
|
+
result_1 = vfb.get_term_info('FBbt_00003748')
|
|
537
|
+
duration_1 = time.time() - start_time
|
|
538
|
+
|
|
539
|
+
# Test performance for VFB_00101567 (individual anatomy)
|
|
540
|
+
start_time = time.time()
|
|
541
|
+
result_2 = vfb.get_term_info('VFB_00101567')
|
|
542
|
+
duration_2 = time.time() - start_time
|
|
543
|
+
|
|
544
|
+
# Print performance metrics for GitHub Actions logs
|
|
545
|
+
print(f"\n" + "="*50)
|
|
546
|
+
print(f"Performance Test Results:")
|
|
547
|
+
print(f"="*50)
|
|
548
|
+
print(f"FBbt_00003748 query took: {duration_1:.4f} seconds")
|
|
549
|
+
print(f"VFB_00101567 query took: {duration_2:.4f} seconds")
|
|
550
|
+
print(f"Total time for both queries: {duration_1 + duration_2:.4f} seconds")
|
|
551
|
+
|
|
552
|
+
# Performance categories
|
|
553
|
+
total_time = duration_1 + duration_2
|
|
554
|
+
if total_time < 1.5:
|
|
555
|
+
performance_level = "🟢 Excellent (< 1.5 seconds)"
|
|
556
|
+
elif total_time < 3.0:
|
|
557
|
+
performance_level = "🟡 Good (1.5-3 seconds)"
|
|
558
|
+
elif total_time < 6.0:
|
|
559
|
+
performance_level = "🟠 Acceptable (3-6 seconds)"
|
|
560
|
+
else:
|
|
561
|
+
performance_level = "🔴 Slow (> 6 seconds)"
|
|
562
|
+
|
|
563
|
+
print(f"Performance Level: {performance_level}")
|
|
564
|
+
print(f"="*50)
|
|
565
|
+
|
|
566
|
+
# Basic assertions to ensure the queries succeeded
|
|
567
|
+
self.assertIsNotNone(result_1, "FBbt_00003748 query returned None")
|
|
568
|
+
self.assertIsNotNone(result_2, "VFB_00101567 query returned None")
|
|
569
|
+
|
|
570
|
+
# Performance assertions - fail if queries take too long
|
|
571
|
+
# These thresholds are based on observed performance characteristics
|
|
572
|
+
max_single_query_time = 3.0 # seconds (increased from 2.0 to account for SOLR cache overhead)
|
|
573
|
+
max_total_time = 6.0 # seconds (2 queries * 3 seconds each)
|
|
574
|
+
|
|
575
|
+
self.assertLess(duration_1, max_single_query_time,
|
|
576
|
+
f"FBbt_00003748 query took {duration_1:.4f}s, exceeding {max_single_query_time}s threshold")
|
|
577
|
+
self.assertLess(duration_2, max_single_query_time,
|
|
578
|
+
f"VFB_00101567 query took {duration_2:.4f}s, exceeding {max_single_query_time}s threshold")
|
|
579
|
+
self.assertLess(duration_1 + duration_2, max_total_time,
|
|
580
|
+
f"Total query time {duration_1 + duration_2:.4f}s exceeds {max_total_time}s threshold")
|
|
581
|
+
|
|
582
|
+
# Log success
|
|
583
|
+
print("Performance test completed successfully!")
|
|
584
|
+
|
|
527
585
|
|
|
528
586
|
class TestVariable:
|
|
529
587
|
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test VFBquery default caching functionality.
|
|
3
|
+
|
|
4
|
+
These tests ensure that the default 3-month TTL, 2GB memory caching
|
|
5
|
+
system works correctly and provides expected performance benefits.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
import os
|
|
10
|
+
import time
|
|
11
|
+
from unittest.mock import MagicMock
|
|
12
|
+
import sys
|
|
13
|
+
|
|
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',
|
|
18
|
+
'navis.plotting.vispy', 'navis.plotting.vispy.viewer']:
|
|
19
|
+
sys.modules[module] = MagicMock()
|
|
20
|
+
|
|
21
|
+
# Set environment variables
|
|
22
|
+
os.environ.update({
|
|
23
|
+
'MPLBACKEND': 'Agg',
|
|
24
|
+
'VISPY_GL_LIB': 'osmesa',
|
|
25
|
+
'VISPY_USE_EGL': '0',
|
|
26
|
+
'VFBQUERY_CACHE_ENABLED': 'true'
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestDefaultCaching(unittest.TestCase):
|
|
31
|
+
"""Test default caching behavior in VFBquery."""
|
|
32
|
+
|
|
33
|
+
def setUp(self):
|
|
34
|
+
"""Set up test environment."""
|
|
35
|
+
# Clear any existing cache before each test
|
|
36
|
+
try:
|
|
37
|
+
import vfbquery
|
|
38
|
+
if hasattr(vfbquery, 'clear_vfbquery_cache'):
|
|
39
|
+
vfbquery.clear_vfbquery_cache()
|
|
40
|
+
except ImportError:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
def test_caching_enabled_by_default(self):
|
|
44
|
+
"""Test that caching is automatically enabled when importing vfbquery."""
|
|
45
|
+
import vfbquery
|
|
46
|
+
|
|
47
|
+
# Check that caching functions are available
|
|
48
|
+
self.assertTrue(hasattr(vfbquery, 'get_vfbquery_cache_stats'))
|
|
49
|
+
self.assertTrue(hasattr(vfbquery, 'enable_vfbquery_caching'))
|
|
50
|
+
|
|
51
|
+
# Check that cache stats show caching is enabled
|
|
52
|
+
stats = vfbquery.get_vfbquery_cache_stats()
|
|
53
|
+
self.assertTrue(stats['enabled'])
|
|
54
|
+
self.assertEqual(stats['cache_ttl_days'], 90.0) # 3 months
|
|
55
|
+
self.assertEqual(stats['memory_cache_limit_mb'], 2048) # 2GB
|
|
56
|
+
|
|
57
|
+
def test_cache_performance_improvement(self):
|
|
58
|
+
"""Test that caching provides performance improvement."""
|
|
59
|
+
import vfbquery
|
|
60
|
+
|
|
61
|
+
test_term = 'FBbt_00003748' # medulla
|
|
62
|
+
|
|
63
|
+
# First call (cold - populates cache)
|
|
64
|
+
start_time = time.time()
|
|
65
|
+
result1 = vfbquery.get_term_info(test_term)
|
|
66
|
+
cold_time = time.time() - start_time
|
|
67
|
+
|
|
68
|
+
# Verify we got a result
|
|
69
|
+
self.assertIsNotNone(result1)
|
|
70
|
+
if result1 is not None:
|
|
71
|
+
self.assertIn('Name', result1)
|
|
72
|
+
|
|
73
|
+
# Second call (warm - should hit cache)
|
|
74
|
+
start_time = time.time()
|
|
75
|
+
result2 = vfbquery.get_term_info(test_term)
|
|
76
|
+
warm_time = time.time() - start_time
|
|
77
|
+
|
|
78
|
+
# Verify cache hit
|
|
79
|
+
self.assertIsNotNone(result2)
|
|
80
|
+
self.assertEqual(result1, result2) # Should be identical
|
|
81
|
+
|
|
82
|
+
# Verify performance improvement (warm should be faster)
|
|
83
|
+
self.assertLess(warm_time, cold_time)
|
|
84
|
+
|
|
85
|
+
# Check cache statistics
|
|
86
|
+
stats = vfbquery.get_vfbquery_cache_stats()
|
|
87
|
+
self.assertGreater(stats['hits'], 0) # Should have cache hits
|
|
88
|
+
self.assertGreater(stats['hit_rate_percent'], 0) # Positive hit rate
|
|
89
|
+
|
|
90
|
+
def test_cache_statistics_tracking(self):
|
|
91
|
+
"""Test that cache statistics are properly tracked."""
|
|
92
|
+
import vfbquery
|
|
93
|
+
|
|
94
|
+
# Clear cache and get fresh baseline
|
|
95
|
+
vfbquery.clear_vfbquery_cache()
|
|
96
|
+
initial_stats = vfbquery.get_vfbquery_cache_stats()
|
|
97
|
+
initial_items = initial_stats['memory_cache_items']
|
|
98
|
+
initial_total = initial_stats['misses'] + initial_stats['hits']
|
|
99
|
+
|
|
100
|
+
# Make a unique query that won't be cached
|
|
101
|
+
unique_term = 'FBbt_00005106' # Use a different term
|
|
102
|
+
result = vfbquery.get_term_info(unique_term)
|
|
103
|
+
self.assertIsNotNone(result)
|
|
104
|
+
|
|
105
|
+
# Check that stats were updated
|
|
106
|
+
updated_stats = vfbquery.get_vfbquery_cache_stats()
|
|
107
|
+
updated_total = updated_stats['misses'] + updated_stats['hits']
|
|
108
|
+
|
|
109
|
+
self.assertGreaterEqual(updated_stats['memory_cache_items'], initial_items)
|
|
110
|
+
self.assertGreater(updated_total, initial_total) # More total requests
|
|
111
|
+
self.assertGreaterEqual(updated_stats['memory_cache_size_mb'], 0)
|
|
112
|
+
|
|
113
|
+
def test_memory_size_tracking(self):
|
|
114
|
+
"""Test that memory usage is properly tracked."""
|
|
115
|
+
import vfbquery
|
|
116
|
+
|
|
117
|
+
# Clear cache to start fresh
|
|
118
|
+
vfbquery.clear_vfbquery_cache()
|
|
119
|
+
|
|
120
|
+
# Cache a few different terms
|
|
121
|
+
test_terms = ['FBbt_00003748', 'VFB_00101567']
|
|
122
|
+
|
|
123
|
+
for term in test_terms:
|
|
124
|
+
vfbquery.get_term_info(term)
|
|
125
|
+
stats = vfbquery.get_vfbquery_cache_stats()
|
|
126
|
+
|
|
127
|
+
# Memory size should be tracked
|
|
128
|
+
self.assertGreaterEqual(stats['memory_cache_size_mb'], 0)
|
|
129
|
+
self.assertLessEqual(stats['memory_cache_size_mb'], stats['memory_cache_limit_mb'])
|
|
130
|
+
|
|
131
|
+
def test_cache_ttl_configuration(self):
|
|
132
|
+
"""Test that cache TTL is properly configured."""
|
|
133
|
+
import vfbquery
|
|
134
|
+
|
|
135
|
+
stats = vfbquery.get_vfbquery_cache_stats()
|
|
136
|
+
|
|
137
|
+
# Should be configured for 3 months (90 days)
|
|
138
|
+
self.assertEqual(stats['cache_ttl_days'], 90.0)
|
|
139
|
+
self.assertEqual(stats['cache_ttl_hours'], 2160) # 90 * 24
|
|
140
|
+
|
|
141
|
+
def test_transparent_caching(self):
|
|
142
|
+
"""Test that regular VFBquery functions are transparently cached."""
|
|
143
|
+
import vfbquery
|
|
144
|
+
|
|
145
|
+
# Test that get_term_info and get_instances are using cached versions
|
|
146
|
+
test_term = 'FBbt_00003748'
|
|
147
|
+
|
|
148
|
+
# These should work with caching transparently
|
|
149
|
+
term_info = vfbquery.get_term_info(test_term)
|
|
150
|
+
self.assertIsNotNone(term_info)
|
|
151
|
+
|
|
152
|
+
instances = vfbquery.get_instances(test_term, limit=5)
|
|
153
|
+
self.assertIsNotNone(instances)
|
|
154
|
+
|
|
155
|
+
# Cache should show activity
|
|
156
|
+
stats = vfbquery.get_vfbquery_cache_stats()
|
|
157
|
+
self.assertGreater(stats['misses'] + stats['hits'], 0)
|
|
158
|
+
|
|
159
|
+
def test_cache_disable_environment_variable(self):
|
|
160
|
+
"""Test that caching can be disabled via environment variable."""
|
|
161
|
+
# This test would need to be run in a separate process to test
|
|
162
|
+
# the environment variable behavior at import time
|
|
163
|
+
# For now, just verify the current state respects the env var
|
|
164
|
+
|
|
165
|
+
cache_enabled = os.getenv('VFBQUERY_CACHE_ENABLED', 'true').lower()
|
|
166
|
+
if cache_enabled not in ('false', '0', 'no', 'off'):
|
|
167
|
+
import vfbquery
|
|
168
|
+
stats = vfbquery.get_vfbquery_cache_stats()
|
|
169
|
+
self.assertTrue(stats['enabled'])
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == '__main__':
|
|
173
|
+
unittest.main(verbosity=2)
|
test/test_examples_diff.py
CHANGED
|
@@ -113,7 +113,12 @@ def remove_nulls(data):
|
|
|
113
113
|
new_dict[k] = cleaned
|
|
114
114
|
return new_dict
|
|
115
115
|
elif isinstance(data, list):
|
|
116
|
-
|
|
116
|
+
filtered = []
|
|
117
|
+
for item in data:
|
|
118
|
+
cleaned_item = remove_nulls(item)
|
|
119
|
+
if cleaned_item is not None and cleaned_item != {} and cleaned_item != []:
|
|
120
|
+
filtered.append(cleaned_item)
|
|
121
|
+
return filtered
|
|
117
122
|
return data
|
|
118
123
|
|
|
119
124
|
def main():
|
vfbquery/__init__.py
CHANGED
|
@@ -1,4 +1,86 @@
|
|
|
1
1
|
from .vfb_queries import *
|
|
2
|
+
from .solr_result_cache import get_solr_cache
|
|
3
|
+
|
|
4
|
+
# Caching enhancements (optional import - don't break if dependencies missing)
|
|
5
|
+
try:
|
|
6
|
+
from .cache_enhancements import (
|
|
7
|
+
enable_vfbquery_caching,
|
|
8
|
+
disable_vfbquery_caching,
|
|
9
|
+
clear_vfbquery_cache,
|
|
10
|
+
get_vfbquery_cache_stats,
|
|
11
|
+
set_cache_ttl,
|
|
12
|
+
set_cache_memory_limit,
|
|
13
|
+
set_cache_max_items,
|
|
14
|
+
enable_disk_cache,
|
|
15
|
+
disable_disk_cache,
|
|
16
|
+
get_cache_config,
|
|
17
|
+
CacheConfig
|
|
18
|
+
)
|
|
19
|
+
from .cached_functions import (
|
|
20
|
+
get_term_info_cached,
|
|
21
|
+
get_instances_cached,
|
|
22
|
+
patch_vfbquery_with_caching,
|
|
23
|
+
unpatch_vfbquery_caching
|
|
24
|
+
)
|
|
25
|
+
__caching_available__ = True
|
|
26
|
+
|
|
27
|
+
# Enable caching by default with 3-month TTL and 2GB memory cache
|
|
28
|
+
import os
|
|
29
|
+
|
|
30
|
+
# Check if caching should be disabled via environment variable
|
|
31
|
+
cache_disabled = os.getenv('VFBQUERY_CACHE_ENABLED', 'true').lower() in ('false', '0', 'no', 'off')
|
|
32
|
+
|
|
33
|
+
if not cache_disabled:
|
|
34
|
+
# Enable caching with VFB_connect-like defaults
|
|
35
|
+
enable_vfbquery_caching(
|
|
36
|
+
cache_ttl_hours=2160, # 3 months (90 days)
|
|
37
|
+
memory_cache_size_mb=2048, # 2GB memory cache
|
|
38
|
+
max_items=10000, # Max 10k items as safeguard
|
|
39
|
+
disk_cache_enabled=True # Persistent across sessions
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Automatically patch existing functions for transparent caching
|
|
43
|
+
patch_vfbquery_with_caching()
|
|
44
|
+
|
|
45
|
+
print("VFBquery: Caching enabled by default (3-month TTL, 2GB memory)")
|
|
46
|
+
print(" Disable with: export VFBQUERY_CACHE_ENABLED=false")
|
|
47
|
+
|
|
48
|
+
except ImportError:
|
|
49
|
+
__caching_available__ = False
|
|
50
|
+
print("VFBquery: Caching not available (dependencies missing)")
|
|
51
|
+
|
|
52
|
+
# Convenience function for clearing SOLR cache entries
|
|
53
|
+
def clear_solr_cache(query_type: str, term_id: str) -> bool:
|
|
54
|
+
"""
|
|
55
|
+
Clear a specific SOLR cache entry to force refresh
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
query_type: Type of query ('term_info', 'instances', etc.)
|
|
59
|
+
term_id: Term identifier (e.g., 'FBbt_00003748')
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
True if successfully cleared, False otherwise
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
>>> import vfbquery as vfb
|
|
66
|
+
>>> vfb.clear_solr_cache('term_info', 'FBbt_00003748')
|
|
67
|
+
>>> result = vfb.get_term_info('FBbt_00003748') # Will fetch fresh data
|
|
68
|
+
"""
|
|
69
|
+
cache = get_solr_cache()
|
|
70
|
+
return cache.clear_cache_entry(query_type, term_id)
|
|
71
|
+
|
|
72
|
+
# SOLR-based result caching (experimental - for cold start optimization)
|
|
73
|
+
try:
|
|
74
|
+
from .solr_cache_integration import (
|
|
75
|
+
enable_solr_result_caching,
|
|
76
|
+
disable_solr_result_caching,
|
|
77
|
+
warmup_solr_cache,
|
|
78
|
+
get_solr_cache_stats as get_solr_cache_stats_func,
|
|
79
|
+
cleanup_solr_cache
|
|
80
|
+
)
|
|
81
|
+
__solr_caching_available__ = True
|
|
82
|
+
except ImportError:
|
|
83
|
+
__solr_caching_available__ = False
|
|
2
84
|
|
|
3
85
|
# Version information
|
|
4
|
-
__version__ = "0.
|
|
86
|
+
__version__ = "0.4.1"
|