vfbquery 0.3.3__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.
@@ -64,7 +64,7 @@ class TermInfoQueriesTest(unittest.TestCase):
64
64
 
65
65
  self.assertEqual(0, len(terminfo.xrefs))
66
66
 
67
- self.assertEqual(6, len(terminfo.pub_syn))
67
+ self.assertEqual(7, len(terminfo.pub_syn))
68
68
 
69
69
  # Check that we have the expected synonym labels (order-independent)
70
70
  synonym_labels = [entry.synonym.label for entry in terminfo.pub_syn]
@@ -145,7 +145,7 @@ class TermInfoQueriesTest(unittest.TestCase):
145
145
  self.assertFalse("link" in serialized)
146
146
  self.assertEqual(4, len(serialized["types"]))
147
147
  self.assertTrue("Anatomy" in serialized["types"])
148
- self.assertEqual("Cyst composed of two cyst cells following the division of a newly-formed cystoblast in the germarium. The two cells are connected by a cytoplasmic bridge.\n([Spradling, 1993](FBrf0064777), [King, 1970](FBrf0021038))", serialized["description"])
148
+ self.assertEqual("Cyst composed of two cyst cells following the division of a newly-formed cystoblast in the germarium. The two cells are connected by a cytoplasmic bridge.\n([King, 1970](FBrf0021038))", serialized["description"])
149
149
  self.assertTrue("synonyms" in serialized)
150
150
  self.assertEqual(1, len(serialized["synonyms"]))
151
151
  self.assertEqual("has_exact_synonym: germarial 2-cell cluster ([King, 1970](FBrf0021038))", serialized["synonyms"][0])
@@ -166,13 +166,10 @@ class TermInfoQueriesTest(unittest.TestCase):
166
166
  self.assertFalse("examples" in serialized)
167
167
  self.assertFalse("thumbnail" in serialized)
168
168
  self.assertTrue("references" in serialized)
169
- self.assertEqual(2, len(serialized["references"]))
170
- self.assertEqual({'link': '[Spradling, 1993, Bate, Martinez Arias, 1993: 1--70](FBrf0064777)',
171
- 'refs': ['http://flybase.org/reports/FBrf0064777'],
172
- 'types': ' pub'}, serialized["references"][0])
169
+ self.assertEqual(1, len(serialized["references"]))
173
170
  self.assertEqual({'link': '[King, 1970, Ovarian Development in Drosophila melanogaster. ](FBrf0021038)',
174
171
  'refs': ['http://flybase.org/reports/FBrf0021038'],
175
- 'types': ' pub'}, serialized["references"][1])
172
+ 'types': ' pub'}, serialized["references"][0])
176
173
  self.assertFalse("targetingSplits" in serialized)
177
174
  self.assertFalse("targetingNeurons" in serialized)
178
175
 
@@ -261,7 +258,7 @@ class TermInfoQueriesTest(unittest.TestCase):
261
258
  self.assertTrue("Turner-Evans et al., 2020" in description)
262
259
 
263
260
  self.assertTrue("synonyms" in serialized)
264
- self.assertEqual(9, len(serialized["synonyms"]))
261
+ self.assertEqual(10, len(serialized["synonyms"]))
265
262
  print(serialized["synonyms"][0])
266
263
  self.assertTrue("has_exact_synonym: EB-PB 1 glomerulus-D/Vgall neuron" in serialized["synonyms"])
267
264
  self.assertFalse("source" in serialized)
@@ -293,7 +290,7 @@ class TermInfoQueriesTest(unittest.TestCase):
293
290
  self.assertFalse("thumbnail" in serialized)
294
291
 
295
292
  self.assertTrue("references" in serialized)
296
- self.assertEqual(6, len(serialized["references"]))
293
+ self.assertEqual(7, len(serialized["references"]))
297
294
 
298
295
  self.assertTrue("targetingSplits" in serialized)
299
296
  self.assertEqual(6, len(serialized["targetingSplits"]))
@@ -527,6 +524,64 @@ class TermInfoQueriesTest(unittest.TestCase):
527
524
  self.assertFalse("filemeta" in serialized)
528
525
  self.assertFalse("template" in serialized)
529
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.0:
555
+ performance_level = "🟢 Excellent (< 1 second)"
556
+ elif total_time < 2.0:
557
+ performance_level = "🟡 Good (1-2 seconds)"
558
+ elif total_time < 4.0:
559
+ performance_level = "🟠 Acceptable (2-4 seconds)"
560
+ else:
561
+ performance_level = "🔴 Slow (> 4 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 = 2.0 # seconds
573
+ max_total_time = 4.0 # seconds (2 queries * 2 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
+
530
585
 
531
586
  class TestVariable:
532
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)
@@ -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
- return [remove_nulls(item) for item in data if remove_nulls(item) not in [None, {}, []]]
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,65 @@
1
1
  from .vfb_queries import *
2
2
 
3
+ # Caching enhancements (optional import - don't break if dependencies missing)
4
+ try:
5
+ from .cache_enhancements import (
6
+ enable_vfbquery_caching,
7
+ disable_vfbquery_caching,
8
+ clear_vfbquery_cache,
9
+ get_vfbquery_cache_stats,
10
+ set_cache_ttl,
11
+ set_cache_memory_limit,
12
+ set_cache_max_items,
13
+ enable_disk_cache,
14
+ disable_disk_cache,
15
+ get_cache_config,
16
+ CacheConfig
17
+ )
18
+ from .cached_functions import (
19
+ get_term_info_cached,
20
+ get_instances_cached,
21
+ patch_vfbquery_with_caching,
22
+ unpatch_vfbquery_caching
23
+ )
24
+ __caching_available__ = True
25
+
26
+ # Enable caching by default with 3-month TTL and 2GB memory cache
27
+ import os
28
+
29
+ # Check if caching should be disabled via environment variable
30
+ cache_disabled = os.getenv('VFBQUERY_CACHE_ENABLED', 'true').lower() in ('false', '0', 'no', 'off')
31
+
32
+ if not cache_disabled:
33
+ # Enable caching with VFB_connect-like defaults
34
+ enable_vfbquery_caching(
35
+ cache_ttl_hours=2160, # 3 months (90 days)
36
+ memory_cache_size_mb=2048, # 2GB memory cache
37
+ max_items=10000, # Max 10k items as safeguard
38
+ disk_cache_enabled=True # Persistent across sessions
39
+ )
40
+
41
+ # Automatically patch existing functions for transparent caching
42
+ patch_vfbquery_with_caching()
43
+
44
+ print("VFBquery: Caching enabled by default (3-month TTL, 2GB memory)")
45
+ print(" Disable with: export VFBQUERY_CACHE_ENABLED=false")
46
+
47
+ except ImportError:
48
+ __caching_available__ = False
49
+ print("VFBquery: Caching not available (dependencies missing)")
50
+
51
+ # SOLR-based result caching (experimental - for cold start optimization)
52
+ try:
53
+ from .solr_cache_integration import (
54
+ enable_solr_result_caching,
55
+ disable_solr_result_caching,
56
+ warmup_solr_cache,
57
+ get_solr_cache_stats as get_solr_cache_stats_func,
58
+ cleanup_solr_cache
59
+ )
60
+ __solr_caching_available__ = True
61
+ except ImportError:
62
+ __solr_caching_available__ = False
63
+
3
64
  # Version information
4
- __version__ = "0.3.3"
65
+ __version__ = "0.4.0"