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.
Files changed (39) hide show
  1. test/readme_parser.py +29 -27
  2. test/term_info_queries_test.py +46 -34
  3. test/test_dataset_template_queries.py +138 -0
  4. test/test_default_caching.py +89 -84
  5. test/test_examples_code.py +7 -0
  6. test/test_examples_diff.py +95 -172
  7. test/test_expression_overlaps.py +183 -0
  8. test/test_expression_pattern_fragments.py +123 -0
  9. test/test_images_neurons.py +152 -0
  10. test/test_images_that_develop_from.py +112 -0
  11. test/test_lineage_clones_in.py +190 -0
  12. test/test_nblast_queries.py +124 -0
  13. test/test_neuron_classes_fasciculating.py +187 -0
  14. test/test_neuron_inputs.py +193 -0
  15. test/test_neuron_neuron_connectivity.py +89 -0
  16. test/test_neuron_region_connectivity.py +117 -0
  17. test/test_neurons_part_here.py +203 -0
  18. test/test_new_owlery_queries.py +282 -0
  19. test/test_publication_transgene_queries.py +101 -0
  20. test/test_query_performance.py +739 -0
  21. test/test_similar_morphology.py +177 -0
  22. test/test_tracts_nerves_innervating.py +188 -0
  23. test/test_transcriptomics.py +223 -0
  24. vfbquery/__init__.py +47 -35
  25. vfbquery/cached_functions.py +772 -131
  26. vfbquery/neo4j_client.py +120 -0
  27. vfbquery/owlery_client.py +463 -0
  28. vfbquery/solr_cache_integration.py +34 -30
  29. vfbquery/solr_fetcher.py +1 -1
  30. vfbquery/solr_result_cache.py +338 -36
  31. vfbquery/term_info_queries.py +1 -1
  32. vfbquery/vfb_queries.py +2969 -627
  33. vfbquery-0.5.1.dist-info/METADATA +2806 -0
  34. vfbquery-0.5.1.dist-info/RECORD +40 -0
  35. vfbquery-0.4.1.dist-info/METADATA +0 -1315
  36. vfbquery-0.4.1.dist-info/RECORD +0 -19
  37. {vfbquery-0.4.1.dist-info → vfbquery-0.5.1.dist-info}/LICENSE +0 -0
  38. {vfbquery-0.4.1.dist-info → vfbquery-0.5.1.dist-info}/WHEEL +0 -0
  39. {vfbquery-0.4.1.dist-info → vfbquery-0.5.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,739 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Comprehensive performance test for all VFB queries.
4
+
5
+ Tests the execution time of all implemented queries to ensure they meet performance thresholds.
6
+ Results are formatted for GitHub Actions reporting.
7
+ """
8
+
9
+ import unittest
10
+ import time
11
+ import sys
12
+ import os
13
+
14
+ # Add the src directory to the path
15
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
16
+
17
+ from vfbquery.vfb_queries import (
18
+ get_term_info,
19
+ get_neurons_with_part_in,
20
+ get_neurons_with_synapses_in,
21
+ get_neurons_with_presynaptic_terminals_in,
22
+ get_neurons_with_postsynaptic_terminals_in,
23
+ get_components_of,
24
+ get_parts_of,
25
+ get_subclasses_of,
26
+ get_neuron_classes_fasciculating_here,
27
+ get_tracts_nerves_innervating_here,
28
+ get_lineage_clones_in,
29
+ get_images_neurons,
30
+ get_images_that_develop_from,
31
+ get_expression_pattern_fragments,
32
+ get_instances,
33
+ get_similar_neurons,
34
+ get_neuron_neuron_connectivity,
35
+ get_neuron_region_connectivity,
36
+ get_individual_neuron_inputs,
37
+ get_expression_overlaps_here,
38
+ get_anatomy_scrnaseq,
39
+ get_cluster_expression,
40
+ get_expression_cluster,
41
+ get_scrnaseq_dataset_data,
42
+ )
43
+
44
+
45
+ class QueryPerformanceTest(unittest.TestCase):
46
+ """Comprehensive performance tests for all VFB queries"""
47
+
48
+ # Performance thresholds (in seconds)
49
+ THRESHOLD_FAST = 1.0 # Fast queries (simple SOLR lookups)
50
+ THRESHOLD_MEDIUM = 3.0 # Medium queries (Owlery + SOLR)
51
+ THRESHOLD_SLOW = 15.0 # Slow queries (Neo4j + complex processing)
52
+ THRESHOLD_VERY_SLOW = 31.0 # Very slow queries (complex OWL reasoning - over 30 seconds)
53
+
54
+ @classmethod
55
+ def setUpClass(cls):
56
+ """Set up for performance tests"""
57
+ # SOLR caching is enabled by default
58
+ print("\n🔥 SOLR caching enabled for performance tests")
59
+
60
+ def setUp(self):
61
+ """Set up test data"""
62
+ self.test_terms = {
63
+ 'mushroom_body': 'FBbt_00003748', # Class - mushroom body
64
+ 'antennal_lobe': 'FBbt_00007401', # Synaptic neuropil
65
+ 'medulla': 'FBbt_00003982', # Visual system
66
+ 'broad_root': 'FBbt_00003987', # Neuron projection bundle (tract)
67
+ 'individual_neuron': 'VFB_00101567', # Individual anatomy
68
+ 'neuron_with_nblast': 'VFB_00017894', # Neuron with NBLAST data (alternative)
69
+ 'clone': 'FBbt_00050024', # Clone
70
+ 'connected_neuron': 'VFB_jrchk00s', # LPC1 neuron with connectivity AND NBLAST data
71
+ }
72
+
73
+ self.results = []
74
+
75
+ def _time_query(self, query_name, query_func, *args, **kwargs):
76
+ """Helper to time a query execution"""
77
+ start_time = time.time()
78
+ try:
79
+ result = query_func(*args, **kwargs)
80
+ duration = time.time() - start_time
81
+ success = result is not None
82
+ error = None
83
+ except Exception as e:
84
+ duration = time.time() - start_time
85
+ success = False
86
+ result = None
87
+ error = str(e)
88
+
89
+ self.results.append({
90
+ 'name': query_name,
91
+ 'duration': duration,
92
+ 'success': success,
93
+ 'error': error
94
+ })
95
+
96
+ return result, duration, success
97
+
98
+ def test_01_term_info_queries(self):
99
+ """Test term info query performance"""
100
+ print("\n" + "="*80)
101
+ print("TERM INFO QUERIES")
102
+ print("="*80)
103
+
104
+ # Test basic term info retrieval
105
+ result, duration, success = self._time_query(
106
+ "get_term_info (mushroom body)",
107
+ get_term_info,
108
+ self.test_terms['mushroom_body'],
109
+ preview=True
110
+ )
111
+ print(f"get_term_info (mushroom body): {duration:.4f}s {'✅' if success else '❌'}")
112
+ # term_info with preview can be very slow due to extensive sub-queries
113
+ self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "term_info query exceeded threshold")
114
+
115
+ result, duration, success = self._time_query(
116
+ "get_term_info (individual)",
117
+ get_term_info,
118
+ self.test_terms['individual_neuron'],
119
+ preview=True
120
+ )
121
+ print(f"get_term_info (individual): {duration:.4f}s {'✅' if success else '❌'}")
122
+ # term_info with preview can be very slow due to extensive sub-queries
123
+ self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "term_info query exceeded threshold")
124
+
125
+ def test_02_neuron_part_queries(self):
126
+ """Test neuron part overlap queries"""
127
+ print("\n" + "="*80)
128
+ print("NEURON PART OVERLAP QUERIES")
129
+ print("="*80)
130
+
131
+ result, duration, success = self._time_query(
132
+ "NeuronsPartHere (antennal lobe)",
133
+ get_neurons_with_part_in,
134
+ self.test_terms['antennal_lobe'],
135
+ return_dataframe=False,
136
+ limit=-1 # Changed to -1 to enable caching
137
+ )
138
+ print(f"NeuronsPartHere: {duration:.4f}s {'✅' if success else '❌'}")
139
+ self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "NeuronsPartHere exceeded threshold")
140
+
141
+ def test_03_synaptic_queries(self):
142
+ """Test synaptic terminal queries"""
143
+ print("\n" + "="*80)
144
+ print("SYNAPTIC TERMINAL QUERIES")
145
+ print("="*80)
146
+
147
+ test_term = self.test_terms['antennal_lobe']
148
+
149
+ result, duration, success = self._time_query(
150
+ "NeuronsSynaptic",
151
+ get_neurons_with_synapses_in,
152
+ test_term,
153
+ return_dataframe=False,
154
+ limit=-1 # Enable caching for performance tests
155
+ )
156
+ print(f"NeuronsSynaptic: {duration:.4f}s {'✅' if success else '❌'}")
157
+ self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "NeuronsSynaptic exceeded threshold")
158
+
159
+ result, duration, success = self._time_query(
160
+ "NeuronsPresynapticHere",
161
+ get_neurons_with_presynaptic_terminals_in,
162
+ test_term,
163
+ return_dataframe=False,
164
+ limit=-1 # Enable caching for performance tests
165
+ )
166
+ print(f"NeuronsPresynapticHere: {duration:.4f}s {'✅' if success else '❌'}")
167
+ self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "NeuronsPresynapticHere exceeded threshold")
168
+
169
+ result, duration, success = self._time_query(
170
+ "NeuronsPostsynapticHere",
171
+ get_neurons_with_postsynaptic_terminals_in,
172
+ test_term,
173
+ return_dataframe=False,
174
+ limit=-1 # Enable caching for performance tests
175
+ )
176
+ print(f"NeuronsPostsynapticHere: {duration:.4f}s {'✅' if success else '❌'}")
177
+ self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "NeuronsPostsynapticHere exceeded threshold")
178
+
179
+ # Test neuron-neuron connectivity query
180
+ result, duration, success = self._time_query(
181
+ "NeuronNeuronConnectivity",
182
+ get_neuron_neuron_connectivity,
183
+ self.test_terms['connected_neuron'],
184
+ return_dataframe=False,
185
+ limit=-1 # Enable caching for performance tests
186
+ )
187
+ print(f"NeuronNeuronConnectivity: {duration:.4f}s {'✅' if success else '❌'}")
188
+ self.assertLess(duration, self.THRESHOLD_SLOW, "NeuronNeuronConnectivity exceeded threshold")
189
+
190
+ def test_04_anatomy_hierarchy_queries(self):
191
+ """Test anatomical hierarchy queries"""
192
+ print("\n" + "="*80)
193
+ print("ANATOMICAL HIERARCHY QUERIES")
194
+ print("="*80)
195
+
196
+ test_term = self.test_terms['mushroom_body']
197
+
198
+ result, duration, success = self._time_query(
199
+ "ComponentsOf",
200
+ get_components_of,
201
+ test_term,
202
+ return_dataframe=False,
203
+ limit=-1 # Enable caching for performance tests
204
+ )
205
+ print(f"ComponentsOf: {duration:.4f}s {'✅' if success else '❌'}")
206
+ self.assertLess(duration, self.THRESHOLD_SLOW, "ComponentsOf exceeded threshold")
207
+
208
+ result, duration, success = self._time_query(
209
+ "PartsOf",
210
+ get_parts_of,
211
+ test_term,
212
+ return_dataframe=False,
213
+ limit=-1 # Enable caching for performance tests
214
+ )
215
+ print(f"PartsOf: {duration:.4f}s {'✅' if success else '❌'}")
216
+ self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "PartsOf exceeded threshold")
217
+
218
+ result, duration, success = self._time_query(
219
+ "SubclassesOf",
220
+ get_subclasses_of,
221
+ test_term,
222
+ return_dataframe=False,
223
+ limit=-1 # Enable caching for performance tests
224
+ )
225
+ print(f"SubclassesOf: {duration:.4f}s {'✅' if success else '❌'}")
226
+ # SubclassesOf can be very slow for complex terms like mushroom body
227
+ self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "SubclassesOf exceeded threshold")
228
+
229
+ def test_05_tract_lineage_queries(self):
230
+ """Test tract/nerve and lineage clone queries"""
231
+ print("\n" + "="*80)
232
+ print("TRACT/NERVE AND LINEAGE QUERIES")
233
+ print("="*80)
234
+
235
+ # NeuronClassesFasciculatingHere
236
+ result, duration, success = self._time_query(
237
+ "NeuronClassesFasciculatingHere",
238
+ get_neuron_classes_fasciculating_here,
239
+ self.test_terms['broad_root'],
240
+ return_dataframe=False,
241
+ limit=-1 # Enable caching for performance tests
242
+ )
243
+ print(f"NeuronClassesFasciculatingHere: {duration:.4f}s {'✅' if success else '❌'}")
244
+ self.assertLess(duration, self.THRESHOLD_SLOW, "NeuronClassesFasciculatingHere exceeded threshold")
245
+
246
+ # TractsNervesInnervatingHere
247
+ result, duration, success = self._time_query(
248
+ "TractsNervesInnervatingHere",
249
+ get_tracts_nerves_innervating_here,
250
+ self.test_terms['antennal_lobe'],
251
+ return_dataframe=False,
252
+ limit=-1 # Enable caching for performance tests
253
+ )
254
+ print(f"TractsNervesInnervatingHere: {duration:.4f}s {'✅' if success else '❌'}")
255
+ self.assertLess(duration, self.THRESHOLD_SLOW, "TractsNervesInnervatingHere exceeded threshold")
256
+
257
+ # LineageClonesIn
258
+ result, duration, success = self._time_query(
259
+ "LineageClonesIn",
260
+ get_lineage_clones_in,
261
+ self.test_terms['antennal_lobe'],
262
+ return_dataframe=False,
263
+ limit=-1 # Enable caching for performance tests
264
+ )
265
+ print(f"LineageClonesIn: {duration:.4f}s {'✅' if success else '❌'}")
266
+ # LineageClonesIn can be very slow due to complex OWL reasoning
267
+ self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "LineageClonesIn exceeded threshold")
268
+
269
+ def test_05b_image_queries(self):
270
+ """Test image and developmental lineage queries"""
271
+ print("\n" + "="*80)
272
+ print("IMAGE AND DEVELOPMENTAL QUERIES")
273
+ print("="*80)
274
+
275
+ # ImagesNeurons
276
+ result, duration, success = self._time_query(
277
+ "ImagesNeurons",
278
+ get_images_neurons,
279
+ self.test_terms['antennal_lobe'],
280
+ return_dataframe=False,
281
+ limit=-1 # Enable caching for performance tests
282
+ )
283
+ print(f"ImagesNeurons: {duration:.4f}s {'✅' if success else '❌'}")
284
+ self.assertLess(duration, self.THRESHOLD_SLOW, "ImagesNeurons exceeded threshold")
285
+
286
+ # ImagesThatDevelopFrom test (neuroblast developmental lineages)
287
+ result, duration, success = self._time_query(
288
+ "ImagesThatDevelopFrom",
289
+ get_images_that_develop_from,
290
+ "FBbt_00001419", # neuroblast MNB - has 336 neuron images
291
+ return_dataframe=False,
292
+ limit=-1 # Enable caching for performance tests
293
+ )
294
+ print(f"ImagesThatDevelopFrom: {duration:.4f}s {'✅' if success else '❌'}")
295
+ self.assertLess(duration, self.THRESHOLD_SLOW, "ImagesThatDevelopFrom exceeded threshold")
296
+
297
+ # epFrag test (expression pattern fragments)
298
+ result, duration, success = self._time_query(
299
+ "epFrag",
300
+ get_expression_pattern_fragments,
301
+ "FBtp0000001", # expression pattern example
302
+ return_dataframe=False,
303
+ limit=-1 # Enable caching for performance tests
304
+ )
305
+ print(f"epFrag: {duration:.4f}s {'✅' if success else '❌'}")
306
+ self.assertLess(duration, self.THRESHOLD_SLOW, "epFrag exceeded threshold")
307
+
308
+ def test_06_instance_queries(self):
309
+ """Test instance retrieval queries"""
310
+ print("\n" + "="*80)
311
+ print("INSTANCE QUERIES")
312
+ print("="*80)
313
+
314
+ result, duration, success = self._time_query(
315
+ "ListAllAvailableImages",
316
+ get_instances,
317
+ self.test_terms['medulla'],
318
+ return_dataframe=False,
319
+ limit=-1 # Enable caching for performance tests
320
+ )
321
+ print(f"ListAllAvailableImages: {duration:.4f}s {'✅' if success else '❌'}")
322
+ self.assertLess(duration, self.THRESHOLD_SLOW, "ListAllAvailableImages exceeded threshold")
323
+
324
+ def test_07_connectivity_queries(self):
325
+ """Test neuron connectivity queries"""
326
+ print("\n" + "="*80)
327
+ print("CONNECTIVITY QUERIES")
328
+ print("="*80)
329
+
330
+ # NeuronNeuronConnectivity
331
+ result, duration, success = self._time_query(
332
+ "NeuronNeuronConnectivityQuery",
333
+ get_neuron_neuron_connectivity,
334
+ self.test_terms['connected_neuron'],
335
+ return_dataframe=False,
336
+ limit=-1 # Enable caching for performance tests
337
+ )
338
+ print(f"NeuronNeuronConnectivityQuery: {duration:.4f}s {'✅' if success else '❌'}")
339
+ self.assertLess(duration, self.THRESHOLD_SLOW, "NeuronNeuronConnectivityQuery exceeded threshold")
340
+
341
+ # NeuronRegionConnectivity
342
+ result, duration, success = self._time_query(
343
+ "NeuronRegionConnectivityQuery",
344
+ get_neuron_region_connectivity,
345
+ self.test_terms['connected_neuron'],
346
+ return_dataframe=False,
347
+ limit=-1 # Enable caching for performance tests
348
+ )
349
+ print(f"NeuronRegionConnectivityQuery: {duration:.4f}s {'✅' if success else '❌'}")
350
+ self.assertLess(duration, self.THRESHOLD_SLOW, "NeuronRegionConnectivityQuery exceeded threshold")
351
+
352
+ def test_08_similarity_queries(self):
353
+ """Test NBLAST similarity queries"""
354
+ print("\n" + "="*80)
355
+ print("SIMILARITY QUERIES (Neo4j NBLAST)")
356
+ print("="*80)
357
+
358
+ # SimilarMorphologyTo (NBLAST)
359
+ result, duration, success = self._time_query(
360
+ "SimilarMorphologyTo",
361
+ get_similar_neurons,
362
+ self.test_terms['connected_neuron'], # VFB_jrchk00s has NBLAST data
363
+ similarity_score='NBLAST_score',
364
+ return_dataframe=False,
365
+ limit=5
366
+ )
367
+ print(f"SimilarMorphologyTo: {duration:.4f}s {'✅' if success else '❌'}")
368
+ # Legacy NBLAST similarity can be slower
369
+ self.assertLess(duration, self.THRESHOLD_SLOW, "SimilarMorphologyTo exceeded threshold")
370
+
371
+ def test_09_neuron_input_queries(self):
372
+ """Test neuron input/synapse queries"""
373
+ print("\n" + "="*80)
374
+ print("NEURON INPUT QUERIES (Neo4j)")
375
+ print("="*80)
376
+
377
+ # NeuronInputsTo
378
+ result, duration, success = self._time_query(
379
+ "NeuronInputsTo",
380
+ get_individual_neuron_inputs,
381
+ self.test_terms['connected_neuron'],
382
+ return_dataframe=False,
383
+ limit=5
384
+ )
385
+ print(f"NeuronInputsTo: {duration:.4f}s {'✅' if success else '❌'}")
386
+ self.assertLess(duration, self.THRESHOLD_SLOW, "NeuronInputsTo exceeded threshold")
387
+
388
+ def test_10_expression_queries(self):
389
+ """Test expression pattern queries"""
390
+ print("\n" + "="*80)
391
+ print("EXPRESSION PATTERN QUERIES (Neo4j)")
392
+ print("="*80)
393
+
394
+ # ExpressionOverlapsHere - test with adult brain which has many expression patterns
395
+ result, duration, success = self._time_query(
396
+ "ExpressionOverlapsHere (adult brain)",
397
+ get_expression_overlaps_here,
398
+ self.test_terms['medulla'], # FBbt_00003982 (adult brain/medulla)
399
+ return_dataframe=False,
400
+ limit=10 # Limit to 10 for performance test
401
+ )
402
+ print(f"ExpressionOverlapsHere: {duration:.4f}s {'✅' if success else '❌'}")
403
+ if success and result:
404
+ print(f" └─ Found {result.get('count', 0)} total expression patterns, returned 10")
405
+ self.assertLess(duration, self.THRESHOLD_SLOW, "ExpressionOverlapsHere exceeded threshold")
406
+
407
+ def test_11_transcriptomics_queries(self):
408
+ """Test scRNAseq transcriptomics queries"""
409
+ print("\n" + "="*80)
410
+ print("TRANSCRIPTOMICS QUERIES (Neo4j scRNAseq)")
411
+ print("="*80)
412
+
413
+ # Note: These tests use example IDs that may need to be updated based on actual database content
414
+ # The queries are designed to work even if no data is found (will return empty results)
415
+
416
+ # anatScRNAseqQuery - test with adult brain
417
+ result, duration, success = self._time_query(
418
+ "anatScRNAseqQuery (adult brain)",
419
+ get_anatomy_scrnaseq,
420
+ self.test_terms['medulla'], # FBbt_00003982 (adult brain/medulla)
421
+ return_dataframe=False,
422
+ limit=10
423
+ )
424
+ print(f"anatScRNAseqQuery: {duration:.4f}s {'✅' if success else '❌'}")
425
+ if success and result:
426
+ count = result.get('count', 0)
427
+ print(f" └─ Found {count} total clusters" + (", returned 10" if count > 10 else ""))
428
+ self.assertLess(duration, self.THRESHOLD_SLOW, "anatScRNAseqQuery exceeded threshold")
429
+
430
+ # clusterExpression - test with a cluster ID (may return empty if cluster doesn't exist)
431
+ # Using a dummy ID - test will pass even with empty results
432
+ try:
433
+ result, duration, success = self._time_query(
434
+ "clusterExpression (example cluster)",
435
+ get_cluster_expression,
436
+ "VFBc_00101567", # Example cluster ID
437
+ return_dataframe=False,
438
+ limit=10
439
+ )
440
+ print(f"clusterExpression: {duration:.4f}s {'✅' if success else '❌'}")
441
+ if success and result:
442
+ count = result.get('count', 0)
443
+ print(f" └─ Found {count} genes expressed" + (", returned 10" if count > 10 else ""))
444
+ self.assertLess(duration, self.THRESHOLD_SLOW, "clusterExpression exceeded threshold")
445
+ except Exception as e:
446
+ print(f"clusterExpression: Skipped (test data may not exist): {e}")
447
+
448
+ # expressionCluster - test with a gene ID (may return empty if no scRNAseq data)
449
+ try:
450
+ result, duration, success = self._time_query(
451
+ "expressionCluster (example gene)",
452
+ get_expression_cluster,
453
+ "FBgn_00000024", # Example gene ID
454
+ return_dataframe=False,
455
+ limit=10
456
+ )
457
+ print(f"expressionCluster: {duration:.4f}s {'✅' if success else '❌'}")
458
+ if success and result:
459
+ count = result.get('count', 0)
460
+ print(f" └─ Found {count} clusters expressing gene" + (", returned 10" if count > 10 else ""))
461
+ self.assertLess(duration, self.THRESHOLD_SLOW, "expressionCluster exceeded threshold")
462
+ except Exception as e:
463
+ print(f"expressionCluster: Skipped (test data may not exist): {e}")
464
+
465
+ # scRNAdatasetData - test with a dataset ID (may return empty if dataset doesn't exist)
466
+ try:
467
+ result, duration, success = self._time_query(
468
+ "scRNAdatasetData (example dataset)",
469
+ get_scrnaseq_dataset_data,
470
+ "VFBds_00001234", # Example dataset ID
471
+ return_dataframe=False,
472
+ limit=10
473
+ )
474
+ print(f"scRNAdatasetData: {duration:.4f}s {'✅' if success else '❌'}")
475
+ if success and result:
476
+ count = result.get('count', 0)
477
+ print(f" └─ Found {count} clusters in dataset" + (", returned 10" if count > 10 else ""))
478
+ self.assertLess(duration, self.THRESHOLD_SLOW, "scRNAdatasetData exceeded threshold")
479
+ except Exception as e:
480
+ print(f"scRNAdatasetData: Skipped (test data may not exist): {e}")
481
+
482
+ def test_12_nblast_queries(self):
483
+ """Test NBLAST similarity queries"""
484
+ print("\n" + "="*80)
485
+ print("NBLAST SIMILARITY QUERIES")
486
+ print("="*80)
487
+
488
+ # Import the new query functions
489
+ from vfbquery.vfb_queries import (
490
+ get_similar_morphology,
491
+ get_similar_morphology_part_of,
492
+ get_similar_morphology_part_of_exp,
493
+ get_similar_morphology_nb,
494
+ get_similar_morphology_nb_exp,
495
+ get_similar_morphology_userdata
496
+ )
497
+
498
+ # SimilarMorphologyTo - NBLAST matches
499
+ result, duration, success = self._time_query(
500
+ "SimilarMorphologyTo",
501
+ get_similar_morphology,
502
+ self.test_terms['connected_neuron'], # LPC1 neuron with NBLAST data
503
+ return_dataframe=False,
504
+ limit=10
505
+ )
506
+ print(f"SimilarMorphologyTo: {duration:.4f}s {'✅' if success else '❌'}")
507
+ if success and result:
508
+ count = result.get('count', 0)
509
+ print(f" └─ Found {count} NBLAST matches" + (", returned 10" if count > 10 else ""))
510
+ # NBLAST queries can be slightly slower due to database complexity
511
+ self.assertLess(duration, self.THRESHOLD_SLOW, "SimilarMorphologyTo exceeded threshold")
512
+
513
+ # SimilarMorphologyToPartOf - NBLASTexp to expression patterns
514
+ result, duration, success = self._time_query(
515
+ "SimilarMorphologyToPartOf",
516
+ get_similar_morphology_part_of,
517
+ self.test_terms['connected_neuron'],
518
+ return_dataframe=False,
519
+ limit=10
520
+ )
521
+ print(f"SimilarMorphologyToPartOf: {duration:.4f}s {'✅' if success else '❌'}")
522
+ if success and result:
523
+ count = result.get('count', 0)
524
+ print(f" └─ Found {count} NBLASTexp matches" + (", returned 10" if count > 10 else ""))
525
+ self.assertLess(duration, self.THRESHOLD_MEDIUM, "SimilarMorphologyToPartOf exceeded threshold")
526
+
527
+ # SimilarMorphologyToPartOfexp - Reverse NBLASTexp
528
+ result, duration, success = self._time_query(
529
+ "SimilarMorphologyToPartOfexp",
530
+ get_similar_morphology_part_of_exp,
531
+ self.test_terms['connected_neuron'],
532
+ return_dataframe=False,
533
+ limit=10
534
+ )
535
+ print(f"SimilarMorphologyToPartOfexp: {duration:.4f}s {'✅' if success else '❌'}")
536
+ if success and result:
537
+ count = result.get('count', 0)
538
+ print(f" └─ Found {count} reverse NBLASTexp matches" + (", returned 10" if count > 10 else ""))
539
+ self.assertLess(duration, self.THRESHOLD_MEDIUM, "SimilarMorphologyToPartOfexp exceeded threshold")
540
+
541
+ # SimilarMorphologyToNB - NeuronBridge matches
542
+ result, duration, success = self._time_query(
543
+ "SimilarMorphologyToNB",
544
+ get_similar_morphology_nb,
545
+ self.test_terms['connected_neuron'],
546
+ return_dataframe=False,
547
+ limit=10
548
+ )
549
+ print(f"SimilarMorphologyToNB: {duration:.4f}s {'✅' if success else '❌'}")
550
+ if success and result:
551
+ count = result.get('count', 0)
552
+ print(f" └─ Found {count} NeuronBridge matches" + (", returned 10" if count > 10 else ""))
553
+ self.assertLess(duration, self.THRESHOLD_MEDIUM, "SimilarMorphologyToNB exceeded threshold")
554
+
555
+ # SimilarMorphologyToNBexp - NeuronBridge for expression patterns
556
+ result, duration, success = self._time_query(
557
+ "SimilarMorphologyToNBexp",
558
+ get_similar_morphology_nb_exp,
559
+ self.test_terms['connected_neuron'],
560
+ return_dataframe=False,
561
+ limit=10
562
+ )
563
+ print(f"SimilarMorphologyToNBexp: {duration:.4f}s {'✅' if success else '❌'}")
564
+ if success and result:
565
+ count = result.get('count', 0)
566
+ print(f" └─ Found {count} NeuronBridge expression matches" + (", returned 10" if count > 10 else ""))
567
+ self.assertLess(duration, self.THRESHOLD_MEDIUM, "SimilarMorphologyToNBexp exceeded threshold")
568
+
569
+ print(f"✅ All NBLAST similarity queries completed")
570
+
571
+ def test_13_dataset_template_queries(self):
572
+ """Test dataset and template queries"""
573
+ print("\n" + "="*80)
574
+ print("DATASET/TEMPLATE QUERIES")
575
+ print("="*80)
576
+
577
+ # Import the new query functions
578
+ from vfbquery.vfb_queries import (
579
+ get_painted_domains,
580
+ get_dataset_images,
581
+ get_all_aligned_images,
582
+ get_aligned_datasets,
583
+ get_all_datasets
584
+ )
585
+
586
+ # Test terms for templates and datasets
587
+ template_term = 'VFBc_00050000' # Adult Brain template
588
+ dataset_term = 'VFBc_00101384' # Example dataset
589
+
590
+ # PaintedDomains - Template painted anatomy domains
591
+ result, duration, success = self._time_query(
592
+ "PaintedDomains",
593
+ get_painted_domains,
594
+ template_term,
595
+ return_dataframe=False,
596
+ limit=10
597
+ )
598
+ print(f"PaintedDomains: {duration:.4f}s {'✅' if success else '❌'}")
599
+ if success and result:
600
+ count = result.get('count', 0)
601
+ print(f" └─ Found {count} painted domains" + (", returned 10" if count > 10 else ""))
602
+ self.assertLess(duration, self.THRESHOLD_MEDIUM, "PaintedDomains exceeded threshold")
603
+
604
+ # DatasetImages - Images in a dataset
605
+ result, duration, success = self._time_query(
606
+ "DatasetImages",
607
+ get_dataset_images,
608
+ dataset_term,
609
+ return_dataframe=False,
610
+ limit=10
611
+ )
612
+ print(f"DatasetImages: {duration:.4f}s {'✅' if success else '❌'}")
613
+ if success and result:
614
+ count = result.get('count', 0)
615
+ print(f" └─ Found {count} images in dataset" + (", returned 10" if count > 10 else ""))
616
+ self.assertLess(duration, self.THRESHOLD_MEDIUM, "DatasetImages exceeded threshold")
617
+
618
+ # AllAlignedImages - All images aligned to template
619
+ result, duration, success = self._time_query(
620
+ "AllAlignedImages",
621
+ get_all_aligned_images,
622
+ template_term,
623
+ return_dataframe=False,
624
+ limit=10
625
+ )
626
+ print(f"AllAlignedImages: {duration:.4f}s {'✅' if success else '❌'}")
627
+ if success and result:
628
+ count = result.get('count', 0)
629
+ print(f" └─ Found {count} aligned images" + (", returned 10" if count > 10 else ""))
630
+ self.assertLess(duration, self.THRESHOLD_MEDIUM, "AllAlignedImages exceeded threshold")
631
+
632
+ # AlignedDatasets - All datasets aligned to template
633
+ result, duration, success = self._time_query(
634
+ "AlignedDatasets",
635
+ get_aligned_datasets,
636
+ template_term,
637
+ return_dataframe=False,
638
+ limit=10
639
+ )
640
+ print(f"AlignedDatasets: {duration:.4f}s {'✅' if success else '❌'}")
641
+ if success and result:
642
+ count = result.get('count', 0)
643
+ print(f" └─ Found {count} aligned datasets" + (", returned 10" if count > 10 else ""))
644
+ self.assertLess(duration, self.THRESHOLD_MEDIUM, "AlignedDatasets exceeded threshold")
645
+
646
+ # AllDatasets - All available datasets
647
+ result, duration, success = self._time_query(
648
+ "AllDatasets",
649
+ get_all_datasets,
650
+ return_dataframe=False,
651
+ limit=20
652
+ )
653
+ print(f"AllDatasets: {duration:.4f}s {'✅' if success else '❌'}")
654
+ if success and result:
655
+ count = result.get('count', 0)
656
+ print(f" └─ Found {count} total datasets" + (", returned 20" if count > 20 else ""))
657
+ self.assertLess(duration, self.THRESHOLD_MEDIUM, "AllDatasets exceeded threshold")
658
+
659
+ print(f"✅ All dataset/template queries completed")
660
+
661
+ def test_14_publication_transgene_queries(self):
662
+ """Test publication and transgene queries"""
663
+ print("\n" + "="*80)
664
+ print("PUBLICATION/TRANSGENE QUERIES")
665
+ print("="*80)
666
+
667
+ # Import the new query functions
668
+ from vfbquery.vfb_queries import (
669
+ get_terms_for_pub,
670
+ get_transgene_expression_here
671
+ )
672
+
673
+ # Test terms
674
+ pub_term = 'DOI_10_7554_eLife_04577' # Example publication
675
+ anatomy_term = self.test_terms['mushroom_body'] # mushroom body
676
+
677
+ # TermsForPub - Terms referencing a publication
678
+ result, duration, success = self._time_query(
679
+ "TermsForPub",
680
+ get_terms_for_pub,
681
+ pub_term,
682
+ return_dataframe=False,
683
+ limit=10
684
+ )
685
+ print(f"TermsForPub: {duration:.4f}s {'✅' if success else '❌'}")
686
+ if success and result:
687
+ count = result.get('count', 0)
688
+ print(f" └─ Found {count} terms for publication" + (", returned 10" if count > 10 else ""))
689
+ self.assertLess(duration, self.THRESHOLD_MEDIUM, "TermsForPub exceeded threshold")
690
+
691
+ # TransgeneExpressionHere - Complex transgene expression query
692
+ result, duration, success = self._time_query(
693
+ "TransgeneExpressionHere",
694
+ get_transgene_expression_here,
695
+ anatomy_term,
696
+ return_dataframe=False,
697
+ limit=10
698
+ )
699
+ print(f"TransgeneExpressionHere: {duration:.4f}s {'✅' if success else '❌'}")
700
+ if success and result:
701
+ count = result.get('count', 0)
702
+ print(f" └─ Found {count} transgene expressions" + (", returned 10" if count > 10 else ""))
703
+ self.assertLess(duration, self.THRESHOLD_SLOW, "TransgeneExpressionHere exceeded threshold")
704
+
705
+ print(f"✅ All publication/transgene queries completed")
706
+
707
+ def tearDown(self):
708
+ """Generate performance summary"""
709
+ pass
710
+
711
+ @classmethod
712
+ def tearDownClass(cls):
713
+ """Generate final performance report"""
714
+ print("\n" + "="*80)
715
+ print("PERFORMANCE TEST SUMMARY")
716
+ print("="*80)
717
+
718
+ # This will be populated by the test instance
719
+ # For now, just print a summary message
720
+ print("All performance tests completed!")
721
+ print("="*80)
722
+
723
+
724
+ def run_tests():
725
+ """Run the performance test suite"""
726
+ # Create test suite
727
+ loader = unittest.TestLoader()
728
+ suite = loader.loadTestsFromTestCase(QueryPerformanceTest)
729
+
730
+ # Run tests with detailed output
731
+ runner = unittest.TextTestRunner(verbosity=2)
732
+ result = runner.run(suite)
733
+
734
+ return result.wasSuccessful()
735
+
736
+
737
+ if __name__ == '__main__':
738
+ success = run_tests()
739
+ sys.exit(0 if success else 1)