vfbquery 0.4.0__py3-none-any.whl → 0.5.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.
- test/readme_parser.py +35 -1
- test/term_info_queries_test.py +11 -11
- test/test_dataset_template_queries.py +138 -0
- test/test_default_caching.py +15 -11
- 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 +204 -0
- test/test_new_owlery_queries.py +282 -0
- test/test_publication_transgene_queries.py +101 -0
- test/test_query_performance.py +743 -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 +22 -1
- vfbquery/neo4j_client.py +120 -0
- vfbquery/owlery_client.py +463 -0
- vfbquery/solr_fetcher.py +1 -1
- vfbquery/solr_result_cache.py +238 -53
- vfbquery/vfb_queries.py +2969 -638
- {vfbquery-0.4.0.dist-info → vfbquery-0.5.0.dist-info}/METADATA +1023 -65
- vfbquery-0.5.0.dist-info/RECORD +39 -0
- vfbquery-0.4.0.dist-info/RECORD +0 -19
- {vfbquery-0.4.0.dist-info → vfbquery-0.5.0.dist-info}/LICENSE +0 -0
- {vfbquery-0.4.0.dist-info → vfbquery-0.5.0.dist-info}/WHEEL +0 -0
- {vfbquery-0.4.0.dist-info → vfbquery-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test suite for SimilarMorphologyTo query.
|
|
4
|
+
|
|
5
|
+
Tests the query that finds neurons with similar morphology using NBLAST scoring.
|
|
6
|
+
This implements the SimilarMorphologyTo query from the VFB XMI specification.
|
|
7
|
+
|
|
8
|
+
Test cases:
|
|
9
|
+
1. Query execution with known neuron with NBLAST data
|
|
10
|
+
2. Schema generation and validation
|
|
11
|
+
3. Term info integration
|
|
12
|
+
4. Preview results validation
|
|
13
|
+
5. Score ordering validation
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import unittest
|
|
17
|
+
import sys
|
|
18
|
+
import os
|
|
19
|
+
|
|
20
|
+
# Add the src directory to the path
|
|
21
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
22
|
+
|
|
23
|
+
from vfbquery.vfb_queries import (
|
|
24
|
+
get_similar_neurons,
|
|
25
|
+
SimilarMorphologyTo_to_schema,
|
|
26
|
+
get_term_info
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
class SimilarMorphologyTest(unittest.TestCase):
|
|
30
|
+
"""Test suite for SimilarMorphologyTo query"""
|
|
31
|
+
|
|
32
|
+
def setUp(self):
|
|
33
|
+
"""Set up test fixtures"""
|
|
34
|
+
# Test neuron: LPC1 (FlyEM-HB:1775513344) [VFB_jrchk00s] - has both NBLAST and connectivity data
|
|
35
|
+
self.test_neuron = "VFB_jrchk00s"
|
|
36
|
+
self.similarity_score = "NBLAST_score"
|
|
37
|
+
|
|
38
|
+
def test_query_execution(self):
|
|
39
|
+
"""Test that the query executes successfully"""
|
|
40
|
+
print(f"\n=== Testing SimilarMorphologyTo execution ===")
|
|
41
|
+
result = get_similar_neurons(
|
|
42
|
+
self.test_neuron,
|
|
43
|
+
similarity_score=self.similarity_score,
|
|
44
|
+
return_dataframe=False,
|
|
45
|
+
limit=5
|
|
46
|
+
)
|
|
47
|
+
self.assertIsNotNone(result, "Query should return a result")
|
|
48
|
+
self.assertIsInstance(result, dict, "Result should be a dictionary")
|
|
49
|
+
print(f"Query returned {result.get('count', 0)} total results")
|
|
50
|
+
|
|
51
|
+
if 'rows' in result and len(result['rows']) > 0:
|
|
52
|
+
first_result = result['rows'][0]
|
|
53
|
+
self.assertIn('id', first_result, "Result should contain 'id' field")
|
|
54
|
+
self.assertIn('name', first_result, "Result should contain 'name' field")
|
|
55
|
+
self.assertIn('score', first_result, "Result should contain 'score' field")
|
|
56
|
+
print(f"First result: {first_result.get('name', 'N/A')} (score: {first_result.get('score', 0)})")
|
|
57
|
+
else:
|
|
58
|
+
print("No similar neurons found (this is OK if none exist)")
|
|
59
|
+
|
|
60
|
+
def test_schema_generation(self):
|
|
61
|
+
"""Test that the schema function works correctly"""
|
|
62
|
+
print(f"\n=== Testing SimilarMorphologyTo schema generation ===")
|
|
63
|
+
|
|
64
|
+
# Get term info for the test neuron
|
|
65
|
+
term_info = get_term_info(self.test_neuron)
|
|
66
|
+
if term_info:
|
|
67
|
+
neuron_name = term_info.get('Name', self.test_neuron)
|
|
68
|
+
else:
|
|
69
|
+
neuron_name = self.test_neuron
|
|
70
|
+
|
|
71
|
+
# Generate schema
|
|
72
|
+
schema = SimilarMorphologyTo_to_schema(neuron_name, self.test_neuron)
|
|
73
|
+
|
|
74
|
+
# Validate schema structure
|
|
75
|
+
self.assertIsNotNone(schema, "Schema should not be None")
|
|
76
|
+
self.assertEqual(schema.query, "SimilarMorphologyTo", "Query name should match")
|
|
77
|
+
self.assertEqual(schema.function, "get_similar_neurons", "Function name should match")
|
|
78
|
+
self.assertEqual(schema.preview, 5, "Preview should show 5 results")
|
|
79
|
+
self.assertIn("score", schema.preview_columns, "Preview should include 'score' column")
|
|
80
|
+
self.assertIn("name", schema.preview_columns, "Preview should include 'name' column")
|
|
81
|
+
|
|
82
|
+
print(f"Schema label: {schema.label}")
|
|
83
|
+
print(f"Preview columns: {schema.preview_columns}")
|
|
84
|
+
|
|
85
|
+
def test_term_info_integration(self):
|
|
86
|
+
"""Test that term info lookup works for the test neuron"""
|
|
87
|
+
print(f"\n=== Testing term_info integration ===")
|
|
88
|
+
term_info = get_term_info(self.test_neuron)
|
|
89
|
+
|
|
90
|
+
self.assertIsNotNone(term_info, "Term info should not be None")
|
|
91
|
+
if term_info:
|
|
92
|
+
# get_term_info returns a dict with 'Name', 'Id', 'Tags', etc.
|
|
93
|
+
self.assertIn('Name', term_info, "Term info should contain 'Name'")
|
|
94
|
+
self.assertIn('Id', term_info, "Term info should contain 'Id'")
|
|
95
|
+
print(f"Neuron name: {term_info.get('Name', 'N/A')}")
|
|
96
|
+
print(f"Neuron tags: {term_info.get('Tags', [])}")
|
|
97
|
+
else:
|
|
98
|
+
print(f"Note: Term info not found for {self.test_neuron} (may not be in SOLR)")
|
|
99
|
+
|
|
100
|
+
def test_preview_validation(self):
|
|
101
|
+
"""Test that preview results are properly formatted"""
|
|
102
|
+
print(f"\n=== Testing preview results ===")
|
|
103
|
+
result = get_similar_neurons(
|
|
104
|
+
self.test_neuron,
|
|
105
|
+
similarity_score=self.similarity_score,
|
|
106
|
+
return_dataframe=False,
|
|
107
|
+
limit=5
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if 'rows' in result and len(result['rows']) > 0:
|
|
111
|
+
# Check that all preview columns exist in the results
|
|
112
|
+
expected_columns = ['id', 'name', 'score', 'tags']
|
|
113
|
+
for item in result['rows']:
|
|
114
|
+
for col in expected_columns:
|
|
115
|
+
self.assertIn(col, item, f"Result should contain '{col}' field")
|
|
116
|
+
|
|
117
|
+
print(f"✓ All {len(result['rows'])} results have required preview columns")
|
|
118
|
+
|
|
119
|
+
# Print sample results
|
|
120
|
+
for i, item in enumerate(result['rows'][:3], 1):
|
|
121
|
+
print(f"{i}. {item.get('name', 'N/A')} - Score: {item.get('score', 0)}")
|
|
122
|
+
else:
|
|
123
|
+
print("No preview data available (query returned no results)")
|
|
124
|
+
|
|
125
|
+
def test_score_ordering(self):
|
|
126
|
+
"""Test that results are ordered by score descending"""
|
|
127
|
+
print(f"\n=== Testing score ordering ===")
|
|
128
|
+
result = get_similar_neurons(
|
|
129
|
+
self.test_neuron,
|
|
130
|
+
similarity_score=self.similarity_score,
|
|
131
|
+
return_dataframe=False,
|
|
132
|
+
limit=10
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if 'rows' in result and len(result['rows']) > 1:
|
|
136
|
+
scores = [float(row.get('score', 0)) for row in result['rows']]
|
|
137
|
+
# Check that scores are in descending order
|
|
138
|
+
for i in range(len(scores) - 1):
|
|
139
|
+
self.assertGreaterEqual(
|
|
140
|
+
scores[i],
|
|
141
|
+
scores[i + 1],
|
|
142
|
+
f"Scores should be in descending order: {scores[i]} >= {scores[i+1]}"
|
|
143
|
+
)
|
|
144
|
+
print(f"✓ Scores are properly ordered (descending)")
|
|
145
|
+
print(f" Highest score: {scores[0]}")
|
|
146
|
+
print(f" Lowest score: {scores[-1]}")
|
|
147
|
+
else:
|
|
148
|
+
print("Not enough results to test ordering")
|
|
149
|
+
|
|
150
|
+
def test_dataframe_output(self):
|
|
151
|
+
"""Test that DataFrame output format works"""
|
|
152
|
+
print(f"\n=== Testing DataFrame output ===")
|
|
153
|
+
result = get_similar_neurons(
|
|
154
|
+
self.test_neuron,
|
|
155
|
+
similarity_score=self.similarity_score,
|
|
156
|
+
return_dataframe=True,
|
|
157
|
+
limit=5
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Should return a pandas DataFrame
|
|
161
|
+
import pandas as pd
|
|
162
|
+
self.assertIsInstance(result, pd.DataFrame, "Should return DataFrame when return_dataframe=True")
|
|
163
|
+
|
|
164
|
+
if not result.empty:
|
|
165
|
+
# Check for expected columns
|
|
166
|
+
expected_columns = ['id', 'name', 'score', 'tags']
|
|
167
|
+
for col in expected_columns:
|
|
168
|
+
self.assertIn(col, result.columns, f"DataFrame should contain '{col}' column")
|
|
169
|
+
|
|
170
|
+
print(f"✓ DataFrame has {len(result)} rows and {len(result.columns)} columns")
|
|
171
|
+
print(f" Columns: {list(result.columns)}")
|
|
172
|
+
else:
|
|
173
|
+
print("DataFrame is empty (no similar neurons found)")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == '__main__':
|
|
177
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test suite for TractsNervesInnervatingHere query.
|
|
4
|
+
|
|
5
|
+
Tests the query that finds tracts and nerves that innervate a synaptic neuropil.
|
|
6
|
+
This implements the TractsNervesInnervatingHere query from the VFB XMI specification.
|
|
7
|
+
|
|
8
|
+
Test cases:
|
|
9
|
+
1. Query execution with known neuropil
|
|
10
|
+
2. Schema generation and validation
|
|
11
|
+
3. Term info integration
|
|
12
|
+
4. Preview results validation
|
|
13
|
+
5. Cache functionality
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import unittest
|
|
17
|
+
import sys
|
|
18
|
+
import os
|
|
19
|
+
|
|
20
|
+
# Add the src directory to the path
|
|
21
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
22
|
+
|
|
23
|
+
from vfbquery.vfb_queries import (
|
|
24
|
+
get_tracts_nerves_innervating_here,
|
|
25
|
+
TractsNervesInnervatingHere_to_schema,
|
|
26
|
+
get_term_info
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TractsNervesInnervatingTest(unittest.TestCase):
|
|
31
|
+
"""Test suite for TractsNervesInnervatingHere query"""
|
|
32
|
+
|
|
33
|
+
def setUp(self):
|
|
34
|
+
"""Set up test fixtures"""
|
|
35
|
+
# Example synaptic neuropil: adult antennal lobe (FBbt_00007401)
|
|
36
|
+
self.test_neuropil = "FBbt_00007401" # antennal lobe
|
|
37
|
+
|
|
38
|
+
def test_query_execution(self):
|
|
39
|
+
"""Test that the query executes successfully"""
|
|
40
|
+
print(f"\n=== Testing TractsNervesInnervatingHere query execution ===")
|
|
41
|
+
|
|
42
|
+
# Execute the query
|
|
43
|
+
result = get_tracts_nerves_innervating_here(self.test_neuropil, return_dataframe=False, limit=5)
|
|
44
|
+
|
|
45
|
+
# Validate result structure
|
|
46
|
+
self.assertIsNotNone(result, "Query should return a result")
|
|
47
|
+
self.assertIsInstance(result, dict, "Result should be a dictionary")
|
|
48
|
+
|
|
49
|
+
# Check for expected keys
|
|
50
|
+
if result:
|
|
51
|
+
print(f"Query returned {len(result.get('data', []))} results")
|
|
52
|
+
|
|
53
|
+
# Validate data structure
|
|
54
|
+
if 'data' in result and len(result['data']) > 0:
|
|
55
|
+
first_result = result['data'][0]
|
|
56
|
+
self.assertIn('id', first_result, "Result should contain 'id' field")
|
|
57
|
+
self.assertIn('label', first_result, "Result should contain 'label' field")
|
|
58
|
+
print(f"First result: {first_result.get('label', 'N/A')} ({first_result.get('id', 'N/A')})")
|
|
59
|
+
|
|
60
|
+
def test_schema_generation(self):
|
|
61
|
+
"""Test schema function generates correct structure"""
|
|
62
|
+
print(f"\n=== Testing TractsNervesInnervatingHere schema generation ===")
|
|
63
|
+
|
|
64
|
+
test_name = "Test Neuropil"
|
|
65
|
+
test_takes = {"short_form": self.test_neuropil}
|
|
66
|
+
|
|
67
|
+
schema = TractsNervesInnervatingHere_to_schema(test_name, test_takes)
|
|
68
|
+
|
|
69
|
+
# Validate schema structure
|
|
70
|
+
self.assertIsNotNone(schema, "Schema should not be None")
|
|
71
|
+
self.assertEqual(schema.query, "TractsNervesInnervatingHere", "Query name should match")
|
|
72
|
+
self.assertEqual(schema.label, f"Tracts/nerves innervating {test_name}", "Label should be formatted correctly")
|
|
73
|
+
self.assertEqual(schema.function, "get_tracts_nerves_innervating_here", "Function name should match")
|
|
74
|
+
self.assertEqual(schema.preview, 5, "Preview should be 5")
|
|
75
|
+
|
|
76
|
+
# Check preview columns
|
|
77
|
+
expected_columns = ["id", "label", "tags", "thumbnail"]
|
|
78
|
+
self.assertEqual(schema.preview_columns, expected_columns, f"Preview columns should be {expected_columns}")
|
|
79
|
+
|
|
80
|
+
print(f"Schema generated successfully: {schema.label}")
|
|
81
|
+
|
|
82
|
+
def test_term_info_integration(self):
|
|
83
|
+
"""Test that query appears in term info for appropriate terms"""
|
|
84
|
+
print(f"\n=== Testing term info integration ===")
|
|
85
|
+
|
|
86
|
+
# Get term info for a synaptic neuropil
|
|
87
|
+
term_info = get_term_info(self.test_neuropil, preview=False)
|
|
88
|
+
|
|
89
|
+
self.assertIsNotNone(term_info, "Term info should not be None")
|
|
90
|
+
self.assertIn("Queries", term_info, "Term info should contain Queries")
|
|
91
|
+
|
|
92
|
+
# Check if our query is present
|
|
93
|
+
queries = term_info.get("Queries", [])
|
|
94
|
+
query_names = [q.get('query') for q in queries]
|
|
95
|
+
|
|
96
|
+
print(f"Available queries for {self.test_neuropil}: {query_names}")
|
|
97
|
+
|
|
98
|
+
# For synaptic neuropils, this query should be available
|
|
99
|
+
if "Synaptic_neuropil" in term_info.get("SuperTypes", []) or \
|
|
100
|
+
"Synaptic_neuropil_domain" in term_info.get("SuperTypes", []):
|
|
101
|
+
self.assertIn("TractsNervesInnervatingHere", query_names,
|
|
102
|
+
"TractsNervesInnervatingHere should be available for Synaptic_neuropil")
|
|
103
|
+
print("✓ Query correctly appears for Synaptic_neuropil type")
|
|
104
|
+
else:
|
|
105
|
+
print(f"Warning: {self.test_neuropil} does not have Synaptic_neuropil type")
|
|
106
|
+
print(f"SuperTypes: {term_info.get('SuperTypes', [])}")
|
|
107
|
+
|
|
108
|
+
def test_preview_results(self):
|
|
109
|
+
"""Test that preview results are properly formatted"""
|
|
110
|
+
print(f"\n=== Testing preview results ===")
|
|
111
|
+
|
|
112
|
+
# Get term info with preview enabled
|
|
113
|
+
term_info = get_term_info(self.test_neuropil, preview=True)
|
|
114
|
+
|
|
115
|
+
self.assertIsNotNone(term_info, "Term info should not be None")
|
|
116
|
+
|
|
117
|
+
# Find our query in the results
|
|
118
|
+
queries = term_info.get("Queries", [])
|
|
119
|
+
innervating_query = None
|
|
120
|
+
for q in queries:
|
|
121
|
+
if q.get('query') == "TractsNervesInnervatingHere":
|
|
122
|
+
innervating_query = q
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
if innervating_query:
|
|
126
|
+
print(f"Found TractsNervesInnervatingHere query")
|
|
127
|
+
|
|
128
|
+
# Check if preview_results exist
|
|
129
|
+
if innervating_query.get('preview_results'):
|
|
130
|
+
preview = innervating_query['preview_results']
|
|
131
|
+
data_key = 'data' if 'data' in preview else 'rows'
|
|
132
|
+
print(f"Preview contains {len(preview.get(data_key, []))} results")
|
|
133
|
+
|
|
134
|
+
# Validate preview structure
|
|
135
|
+
self.assertIn(data_key, preview, f"Preview should contain '{data_key}' key")
|
|
136
|
+
self.assertIn('headers', preview, "Preview should contain 'headers' key")
|
|
137
|
+
|
|
138
|
+
# Check first result if available
|
|
139
|
+
if preview.get(data_key) and len(preview[data_key]) > 0:
|
|
140
|
+
first_result = preview[data_key][0]
|
|
141
|
+
print(f"First preview result: {first_result.get('label', 'N/A')}")
|
|
142
|
+
|
|
143
|
+
# Validate required fields
|
|
144
|
+
self.assertIn('id', first_result, "Preview result should have 'id'")
|
|
145
|
+
self.assertIn('label', first_result, "Preview result should have 'label'")
|
|
146
|
+
else:
|
|
147
|
+
print("No preview results available (this is OK if no innervating tracts exist)")
|
|
148
|
+
else:
|
|
149
|
+
print("TractsNervesInnervatingHere query not found in term info")
|
|
150
|
+
|
|
151
|
+
def test_with_different_neuropils(self):
|
|
152
|
+
"""Test with multiple synaptic neuropil types"""
|
|
153
|
+
print(f"\n=== Testing with different neuropils ===")
|
|
154
|
+
|
|
155
|
+
test_neuropils = [
|
|
156
|
+
("FBbt_00007401", "antennal lobe"),
|
|
157
|
+
("FBbt_00003982", "medulla"),
|
|
158
|
+
("FBbt_00003679", "mushroom body"),
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
for neuropil_id, neuropil_name in test_neuropils:
|
|
162
|
+
print(f"\nTesting {neuropil_name} ({neuropil_id})...")
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
result = get_tracts_nerves_innervating_here(neuropil_id, return_dataframe=False, limit=3)
|
|
166
|
+
|
|
167
|
+
if result and 'data' in result:
|
|
168
|
+
print(f" ✓ Query successful, found {len(result['data'])} results")
|
|
169
|
+
else:
|
|
170
|
+
print(f" ✓ Query successful, no results found")
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
print(f" ✗ Query failed: {str(e)}")
|
|
174
|
+
# Don't fail the test, just log the error
|
|
175
|
+
# raise
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def run_tests():
|
|
179
|
+
"""Run the test suite"""
|
|
180
|
+
suite = unittest.TestLoader().loadTestsFromTestCase(TractsNervesInnervatingTest)
|
|
181
|
+
runner = unittest.TextTestRunner(verbosity=2)
|
|
182
|
+
result = runner.run(suite)
|
|
183
|
+
return result.wasSuccessful()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
if __name__ == '__main__':
|
|
187
|
+
success = run_tests()
|
|
188
|
+
sys.exit(0 if success else 1)
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for transcriptomics queries (scRNAseq data)
|
|
3
|
+
|
|
4
|
+
Tests the following VFB queries:
|
|
5
|
+
1. anatScRNAseqQuery - scRNAseq data for anatomical regions
|
|
6
|
+
2. clusterExpression - genes expressed in a cluster
|
|
7
|
+
3. expressionCluster - clusters expressing a gene
|
|
8
|
+
4. scRNAdatasetData - all clusters in a scRNAseq dataset
|
|
9
|
+
|
|
10
|
+
XMI Source: https://raw.githubusercontent.com/VirtualFlyBrain/geppetto-vfb/master/model/vfb.xmi
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import unittest
|
|
14
|
+
import pandas as pd
|
|
15
|
+
from vfbquery import vfb_queries as vfb
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TranscriptomicsQueriesTest(unittest.TestCase):
|
|
19
|
+
"""Tests for scRNAseq/transcriptomics queries"""
|
|
20
|
+
|
|
21
|
+
# Test data - known terms with scRNAseq data
|
|
22
|
+
# These are examples from the VFB knowledge base
|
|
23
|
+
ANATOMY_WITH_SCRNASEQ = "FBbt_00003982" # adult brain - should have scRNAseq data
|
|
24
|
+
CLUSTER_ID = "VFBc_00101567" # Example cluster ID (may need to be updated with real data)
|
|
25
|
+
GENE_ID = "FBgn_00000024" # Example gene ID (may need to be updated with real data)
|
|
26
|
+
DATASET_ID = "VFBds_00001234" # Example dataset ID (may need to be updated with real data)
|
|
27
|
+
|
|
28
|
+
def test_anatomy_scrnaseq_basic_dataframe(self):
|
|
29
|
+
"""Test anatScRNAseqQuery returns DataFrame"""
|
|
30
|
+
result = vfb.get_anatomy_scrnaseq(self.ANATOMY_WITH_SCRNASEQ, return_dataframe=True)
|
|
31
|
+
|
|
32
|
+
self.assertIsInstance(result, pd.DataFrame)
|
|
33
|
+
|
|
34
|
+
# If data exists, check structure
|
|
35
|
+
if not result.empty:
|
|
36
|
+
self.assertIn('id', result.columns)
|
|
37
|
+
self.assertIn('name', result.columns)
|
|
38
|
+
self.assertIn('tags', result.columns)
|
|
39
|
+
self.assertIn('dataset', result.columns)
|
|
40
|
+
self.assertIn('pubs', result.columns)
|
|
41
|
+
|
|
42
|
+
# Check that all IDs start with expected prefix
|
|
43
|
+
for idx, row in result.iterrows():
|
|
44
|
+
self.assertTrue(row['id'].startswith('VFB'),
|
|
45
|
+
f"Cluster ID should start with VFB, got: {row['id']}")
|
|
46
|
+
|
|
47
|
+
def test_anatomy_scrnaseq_formatted_output(self):
|
|
48
|
+
"""Test anatScRNAseqQuery returns properly formatted dict"""
|
|
49
|
+
result = vfb.get_anatomy_scrnaseq(self.ANATOMY_WITH_SCRNASEQ, return_dataframe=False)
|
|
50
|
+
|
|
51
|
+
self.assertIsInstance(result, dict)
|
|
52
|
+
self.assertIn('headers', result)
|
|
53
|
+
self.assertIn('rows', result)
|
|
54
|
+
self.assertIn('count', result)
|
|
55
|
+
|
|
56
|
+
# Check headers structure
|
|
57
|
+
headers = result['headers']
|
|
58
|
+
self.assertIn('id', headers)
|
|
59
|
+
self.assertIn('name', headers)
|
|
60
|
+
self.assertIn('tags', headers)
|
|
61
|
+
self.assertIn('dataset', headers)
|
|
62
|
+
self.assertIn('pubs', headers)
|
|
63
|
+
|
|
64
|
+
def test_anatomy_scrnaseq_limit(self):
|
|
65
|
+
"""Test anatScRNAseqQuery respects limit parameter"""
|
|
66
|
+
result = vfb.get_anatomy_scrnaseq(self.ANATOMY_WITH_SCRNASEQ, return_dataframe=True, limit=5)
|
|
67
|
+
|
|
68
|
+
self.assertIsInstance(result, pd.DataFrame)
|
|
69
|
+
if not result.empty:
|
|
70
|
+
self.assertLessEqual(len(result), 5)
|
|
71
|
+
|
|
72
|
+
def test_cluster_expression_basic_dataframe(self):
|
|
73
|
+
"""Test clusterExpression returns DataFrame"""
|
|
74
|
+
# Note: This test may need adjustment based on actual cluster IDs in the database
|
|
75
|
+
# For now, we'll just test that the function runs without error
|
|
76
|
+
try:
|
|
77
|
+
result = vfb.get_cluster_expression(self.CLUSTER_ID, return_dataframe=True)
|
|
78
|
+
self.assertIsInstance(result, pd.DataFrame)
|
|
79
|
+
|
|
80
|
+
# If data exists, check structure
|
|
81
|
+
if not result.empty:
|
|
82
|
+
self.assertIn('id', result.columns)
|
|
83
|
+
self.assertIn('name', result.columns)
|
|
84
|
+
self.assertIn('tags', result.columns)
|
|
85
|
+
self.assertIn('expression_level', result.columns)
|
|
86
|
+
self.assertIn('expression_extent', result.columns)
|
|
87
|
+
self.assertIn('anatomy', result.columns)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
# Skip test if cluster ID doesn't exist in current database
|
|
90
|
+
self.skipTest(f"Cluster ID {self.CLUSTER_ID} may not exist in database: {e}")
|
|
91
|
+
|
|
92
|
+
def test_cluster_expression_formatted_output(self):
|
|
93
|
+
"""Test clusterExpression returns properly formatted dict"""
|
|
94
|
+
try:
|
|
95
|
+
result = vfb.get_cluster_expression(self.CLUSTER_ID, return_dataframe=False)
|
|
96
|
+
|
|
97
|
+
self.assertIsInstance(result, dict)
|
|
98
|
+
self.assertIn('headers', result)
|
|
99
|
+
self.assertIn('rows', result)
|
|
100
|
+
self.assertIn('count', result)
|
|
101
|
+
|
|
102
|
+
# Check headers structure
|
|
103
|
+
headers = result['headers']
|
|
104
|
+
self.assertIn('id', headers)
|
|
105
|
+
self.assertIn('name', headers)
|
|
106
|
+
self.assertIn('expression_level', headers)
|
|
107
|
+
self.assertIn('expression_extent', headers)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
self.skipTest(f"Cluster ID {self.CLUSTER_ID} may not exist in database: {e}")
|
|
110
|
+
|
|
111
|
+
def test_expression_cluster_basic_dataframe(self):
|
|
112
|
+
"""Test expressionCluster returns DataFrame"""
|
|
113
|
+
try:
|
|
114
|
+
result = vfb.get_expression_cluster(self.GENE_ID, return_dataframe=True)
|
|
115
|
+
self.assertIsInstance(result, pd.DataFrame)
|
|
116
|
+
|
|
117
|
+
# If data exists, check structure
|
|
118
|
+
if not result.empty:
|
|
119
|
+
self.assertIn('id', result.columns)
|
|
120
|
+
self.assertIn('name', result.columns)
|
|
121
|
+
self.assertIn('tags', result.columns)
|
|
122
|
+
self.assertIn('expression_level', result.columns)
|
|
123
|
+
self.assertIn('expression_extent', result.columns)
|
|
124
|
+
self.assertIn('anatomy', result.columns)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
self.skipTest(f"Gene ID {self.GENE_ID} may not exist in database: {e}")
|
|
127
|
+
|
|
128
|
+
def test_expression_cluster_formatted_output(self):
|
|
129
|
+
"""Test expressionCluster returns properly formatted dict"""
|
|
130
|
+
try:
|
|
131
|
+
result = vfb.get_expression_cluster(self.GENE_ID, return_dataframe=False)
|
|
132
|
+
|
|
133
|
+
self.assertIsInstance(result, dict)
|
|
134
|
+
self.assertIn('headers', result)
|
|
135
|
+
self.assertIn('rows', result)
|
|
136
|
+
self.assertIn('count', result)
|
|
137
|
+
|
|
138
|
+
# Check headers structure
|
|
139
|
+
headers = result['headers']
|
|
140
|
+
self.assertIn('id', headers)
|
|
141
|
+
self.assertIn('name', headers)
|
|
142
|
+
self.assertIn('expression_level', headers)
|
|
143
|
+
self.assertIn('expression_extent', headers)
|
|
144
|
+
except Exception as e:
|
|
145
|
+
self.skipTest(f"Gene ID {self.GENE_ID} may not exist in database: {e}")
|
|
146
|
+
|
|
147
|
+
def test_scrnaseq_dataset_basic_dataframe(self):
|
|
148
|
+
"""Test scRNAdatasetData returns DataFrame"""
|
|
149
|
+
try:
|
|
150
|
+
result = vfb.get_scrnaseq_dataset_data(self.DATASET_ID, return_dataframe=True)
|
|
151
|
+
self.assertIsInstance(result, pd.DataFrame)
|
|
152
|
+
|
|
153
|
+
# If data exists, check structure
|
|
154
|
+
if not result.empty:
|
|
155
|
+
self.assertIn('id', result.columns)
|
|
156
|
+
self.assertIn('name', result.columns)
|
|
157
|
+
self.assertIn('tags', result.columns)
|
|
158
|
+
self.assertIn('anatomy', result.columns)
|
|
159
|
+
self.assertIn('pubs', result.columns)
|
|
160
|
+
except Exception as e:
|
|
161
|
+
self.skipTest(f"Dataset ID {self.DATASET_ID} may not exist in database: {e}")
|
|
162
|
+
|
|
163
|
+
def test_scrnaseq_dataset_formatted_output(self):
|
|
164
|
+
"""Test scRNAdatasetData returns properly formatted dict"""
|
|
165
|
+
try:
|
|
166
|
+
result = vfb.get_scrnaseq_dataset_data(self.DATASET_ID, return_dataframe=False)
|
|
167
|
+
|
|
168
|
+
self.assertIsInstance(result, dict)
|
|
169
|
+
self.assertIn('headers', result)
|
|
170
|
+
self.assertIn('rows', result)
|
|
171
|
+
self.assertIn('count', result)
|
|
172
|
+
|
|
173
|
+
# Check headers structure
|
|
174
|
+
headers = result['headers']
|
|
175
|
+
self.assertIn('id', headers)
|
|
176
|
+
self.assertIn('name', headers)
|
|
177
|
+
self.assertIn('anatomy', headers)
|
|
178
|
+
self.assertIn('pubs', headers)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
self.skipTest(f"Dataset ID {self.DATASET_ID} may not exist in database: {e}")
|
|
181
|
+
|
|
182
|
+
def test_anatomy_scrnaseq_empty_result(self):
|
|
183
|
+
"""Test anatScRNAseqQuery with anatomy that has no scRNAseq data"""
|
|
184
|
+
# Use an anatomy term that likely has no scRNAseq data
|
|
185
|
+
result = vfb.get_anatomy_scrnaseq("FBbt_00000001", return_dataframe=True) # root term
|
|
186
|
+
|
|
187
|
+
self.assertIsInstance(result, pd.DataFrame)
|
|
188
|
+
# Empty results are acceptable - just check it doesn't error
|
|
189
|
+
|
|
190
|
+
def test_schema_functions_exist(self):
|
|
191
|
+
"""Test that all schema functions are defined"""
|
|
192
|
+
self.assertTrue(hasattr(vfb, 'anatScRNAseqQuery_to_schema'))
|
|
193
|
+
self.assertTrue(hasattr(vfb, 'clusterExpression_to_schema'))
|
|
194
|
+
self.assertTrue(hasattr(vfb, 'expressionCluster_to_schema'))
|
|
195
|
+
self.assertTrue(hasattr(vfb, 'scRNAdatasetData_to_schema'))
|
|
196
|
+
|
|
197
|
+
def test_schema_structure(self):
|
|
198
|
+
"""Test that schema functions return proper Query objects"""
|
|
199
|
+
schema_funcs = [
|
|
200
|
+
vfb.anatScRNAseqQuery_to_schema,
|
|
201
|
+
vfb.clusterExpression_to_schema,
|
|
202
|
+
vfb.expressionCluster_to_schema,
|
|
203
|
+
vfb.scRNAdatasetData_to_schema
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
for schema_func in schema_funcs:
|
|
207
|
+
query_obj = schema_func("Test Name", {"short_form": "FBbt_00000001"})
|
|
208
|
+
|
|
209
|
+
# Check required attributes
|
|
210
|
+
self.assertTrue(hasattr(query_obj, 'query'))
|
|
211
|
+
self.assertTrue(hasattr(query_obj, 'label'))
|
|
212
|
+
self.assertTrue(hasattr(query_obj, 'function'))
|
|
213
|
+
self.assertTrue(hasattr(query_obj, 'takes'))
|
|
214
|
+
self.assertTrue(hasattr(query_obj, 'preview'))
|
|
215
|
+
self.assertTrue(hasattr(query_obj, 'preview_columns'))
|
|
216
|
+
|
|
217
|
+
# Check preview columns are defined
|
|
218
|
+
self.assertIsInstance(query_obj.preview_columns, list)
|
|
219
|
+
self.assertGreater(len(query_obj.preview_columns), 0)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
if __name__ == '__main__':
|
|
223
|
+
unittest.main()
|
vfbquery/__init__.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from .vfb_queries import *
|
|
2
|
+
from .solr_result_cache import get_solr_cache
|
|
2
3
|
|
|
3
4
|
# Caching enhancements (optional import - don't break if dependencies missing)
|
|
4
5
|
try:
|
|
@@ -48,6 +49,26 @@ except ImportError:
|
|
|
48
49
|
__caching_available__ = False
|
|
49
50
|
print("VFBquery: Caching not available (dependencies missing)")
|
|
50
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
|
+
|
|
51
72
|
# SOLR-based result caching (experimental - for cold start optimization)
|
|
52
73
|
try:
|
|
53
74
|
from .solr_cache_integration import (
|
|
@@ -62,4 +83,4 @@ except ImportError:
|
|
|
62
83
|
__solr_caching_available__ = False
|
|
63
84
|
|
|
64
85
|
# Version information
|
|
65
|
-
__version__ = "0.
|
|
86
|
+
__version__ = "0.5.0"
|