Skip to content

Commit 7d01235

Browse files
committed
Remove ExpressionOverlapsHere — canonical name is AnatomyExpressedIn
Per the XMI cleanup: the v2 XMI no longer carries a CompoundRefQuery id="ExpressionOverlapsHere", so the wire-side alias here is dead weight. Drop it cleanly: - ha_api.QUERY_TYPE_MAP: remove the "ExpressionOverlapsHere" key. - vfb_queries.py: remove the ExpressionOverlapsHere_to_schema module alias; AnatomyExpressedIn_to_schema is the sole entry point. - Strip the now-stale "renamed from / legacy alias" comments at the two schema emit sites and in the function docstring. - Tests: delete src/test/test_expression_overlaps.py and replace with src/test/test_anatomy_expressed_in.py. The schema test now asserts the legacy alias is *absent* (hasattr False); a new wire- mapping test asserts QUERY_TYPE_MAP carries AnatomyExpressedIn only. Perf test label updated to match. - get_expression_overlaps_here function body unchanged (inverse- direction Cypher landed in v1.13.6); only naming/comments and the docstring shrink. Pre-existing bookmarks against ?q=...,ExpressionOverlapsHere stop resolving — intentional. There is no v2 production URL that exercises that path (Clare's URL was a one-off from the audit) and the XMI side no longer publishes the id either. TransgeneExpressionHere is untouched.
1 parent d6f8724 commit 7d01235

4 files changed

Lines changed: 101 additions & 126 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
"""
2-
Test suite for ExpressionOverlapsHere query (get_expression_overlaps_here).
2+
Test suite for AnatomyExpressedIn query (get_expression_overlaps_here).
33
4-
INVERSE-direction query as of VFBquery v1.13.6 — given an expression
5-
pattern, return the anatomy classes whose Individuals overlap with the
6-
pattern's Individuals. The forward direction (anatomy -> expression
7-
patterns) is now solely owned by TransgeneExpressionHere.
4+
INVERSE-direction query — given an expression pattern, return the anatomy
5+
classes whose Individuals overlap with the pattern's Individuals. The
6+
forward direction (anatomy -> expression patterns) is solely owned by
7+
TransgeneExpressionHere.
88
99
XMI Source: https://raw.githubusercontent.com/VirtualFlyBrain/geppetto-vfb/master/model/vfb.xmi
10-
Query: ExpressionOverlapsHere ("Anatomy $NAME is expressed in")
10+
Query: AnatomyExpressedIn ("Anatomy where $NAME is expressed")
1111
"""
1212

1313
import unittest
@@ -20,174 +20,173 @@
2020
from vfbquery import vfb_queries as vq
2121

2222

23-
class TestExpressionOverlapsHere(unittest.TestCase):
23+
class TestAnatomyExpressedIn(unittest.TestCase):
2424
"""Test cases for get_expression_overlaps_here function"""
2525

26-
def test_expression_overlaps_basic_dataframe(self):
26+
def test_anatomy_expressed_in_basic_dataframe(self):
2727
"""Test basic query returns DataFrame with expected columns"""
28-
# Test with adult brain (FBbt_00003982) - known to have expression patterns
2928
result = vq.get_expression_overlaps_here('VFBexp_FBtp0001321', return_dataframe=True)
30-
29+
3130
self.assertIsInstance(result, pd.DataFrame, "Should return pandas DataFrame")
32-
31+
3332
if not result.empty:
34-
# Check for expected columns
3533
expected_columns = ['id', 'name', 'tags', 'pubs']
3634
for col in expected_columns:
3735
self.assertIn(col, result.columns, f"DataFrame should contain '{col}' column")
38-
39-
# Verify data types
36+
4037
self.assertTrue(all(isinstance(x, str) for x in result['id']), "IDs should be strings")
4138
self.assertTrue(all(isinstance(x, str) for x in result['name']), "Names should be strings")
42-
43-
print(f"\n✓ Found {len(result)} expression patterns overlapping FBbt_00003982")
44-
print(f"✓ Sample results: {result.head(3)[['id', 'name']].to_dict('records')}")
4539

46-
def test_expression_overlaps_formatted_output(self):
40+
print(f"\nFound {len(result)} anatomy classes where VFBexp_FBtp0001321 is expressed")
41+
print(f"Sample results: {result.head(3)[['id', 'name']].to_dict('records')}")
42+
43+
def test_anatomy_expressed_in_formatted_output(self):
4744
"""Test query returns properly formatted dictionary output"""
4845
result = vq.get_expression_overlaps_here('VFBexp_FBtp0001321', return_dataframe=False)
49-
46+
5047
self.assertIsInstance(result, dict, "Should return dictionary when return_dataframe=False")
51-
52-
# Check structure
48+
5349
self.assertIn('headers', result, "Result should contain 'headers'")
5450
self.assertIn('rows', result, "Result should contain 'rows'")
5551
self.assertIn('count', result, "Result should contain 'count'")
56-
57-
# Check headers structure
52+
5853
headers = result['headers']
5954
expected_headers = ['id', 'name', 'tags', 'pubs']
6055
for header in expected_headers:
6156
self.assertIn(header, headers, f"Headers should contain '{header}'")
6257
self.assertIn('title', headers[header], f"Header '{header}' should have 'title'")
6358
self.assertIn('type', headers[header], f"Header '{header}' should have 'type'")
6459
self.assertIn('order', headers[header], f"Header '{header}' should have 'order'")
65-
66-
# Verify header types
60+
6761
self.assertEqual(headers['id']['type'], 'selection_id')
6862
self.assertEqual(headers['name']['type'], 'markdown')
6963
self.assertEqual(headers['tags']['type'], 'tags')
7064
self.assertEqual(headers['pubs']['type'], 'metadata')
71-
65+
7266
if result['rows']:
73-
# Check row structure
7467
first_row = result['rows'][0]
7568
for key in expected_headers:
7669
self.assertIn(key, first_row, f"Row should contain '{key}'")
77-
78-
print(f"\n✓ Formatted output contains {result['count']} expression patterns")
79-
print(f"✓ Sample row keys: {list(first_row.keys())}")
8070

81-
def test_expression_overlaps_limit(self):
71+
print(f"\nFormatted output contains {result['count']} anatomy classes")
72+
print(f"Sample row keys: {list(first_row.keys())}")
73+
74+
def test_anatomy_expressed_in_limit(self):
8275
"""Test limit parameter restricts number of results"""
8376
limit = 3
8477
result = vq.get_expression_overlaps_here('VFBexp_FBtp0001321', return_dataframe=True, limit=limit)
85-
78+
8679
if not result.empty:
8780
self.assertLessEqual(len(result), limit, f"Should return at most {limit} results")
88-
print(f"\n✓ Limit parameter working: requested {limit}, got {len(result)}")
81+
print(f"\nLimit parameter working: requested {limit}, got {len(result)}")
82+
83+
def test_anatomy_expressed_in_empty_result(self):
84+
"""Test query with an id that has no expression overlaps"""
85+
result = vq.get_expression_overlaps_here('VFBexp_99999999', return_dataframe=True)
8986

90-
def test_expression_overlaps_empty_result(self):
91-
"""Test query with anatomy that has no expression patterns"""
92-
# Use a very specific anatomy term unlikely to have expression patterns
93-
result = vq.get_expression_overlaps_here('FBbt_99999999', return_dataframe=True)
94-
9587
# Should return empty DataFrame, not error
9688
self.assertIsInstance(result, pd.DataFrame, "Should return DataFrame even for no results")
97-
print(f"\n✓ Empty result handling works correctly")
89+
print(f"\nEmpty result handling works correctly")
9890

99-
def test_expression_overlaps_publication_data(self):
91+
def test_anatomy_expressed_in_publication_data(self):
10092
"""Test that publication data is properly formatted when present"""
10193
result = vq.get_expression_overlaps_here('VFBexp_FBtp0001321', return_dataframe=True, limit=10)
102-
94+
10395
if not result.empty:
104-
# Check if pubs column exists and contains data
10596
self.assertIn('pubs', result.columns, "Should have 'pubs' column")
106-
107-
# Check structure of publication data
97+
10898
for idx, row in result.iterrows():
109-
if row['pubs']: # If publications exist
99+
if row['pubs']:
110100
pubs = row['pubs']
111101
self.assertIsInstance(pubs, list, "Publications should be a list")
112-
113-
if pubs: # If list is not empty
102+
103+
if pubs:
114104
first_pub = pubs[0]
115105
self.assertIsInstance(first_pub, dict, "Publication should be a dict")
116-
117-
# Check expected publication fields
106+
118107
if 'core' in first_pub:
119108
self.assertIn('short_form', first_pub['core'], "Publication should have short_form")
120-
121-
print(f"\n✓ Publication data properly structured")
109+
110+
print(f"\nPublication data properly structured")
122111
break
123112

124-
def test_expression_overlaps_markdown_encoding(self):
113+
def test_anatomy_expressed_in_markdown_encoding(self):
125114
"""Test that markdown links are properly formatted"""
126115
result = vq.get_expression_overlaps_here('VFBexp_FBtp0001321', return_dataframe=True, limit=5)
127-
116+
128117
if not result.empty:
129-
# Check that names contain markdown link format [label](url)
130118
for name in result['name']:
131-
# Should have markdown link format
132119
self.assertIn('[', name, "Name should contain markdown link start")
133120
self.assertIn('](', name, "Name should contain markdown link separator")
134121
self.assertIn(')', name, "Name should contain markdown link end")
135-
136-
print(f"\n✓ Markdown links properly formatted")
137122

138-
def test_expression_overlaps_tags_format(self):
123+
print(f"\nMarkdown links properly formatted")
124+
125+
def test_anatomy_expressed_in_tags_format(self):
139126
"""Test that tags are properly formatted as pipe-separated strings"""
140127
result = vq.get_expression_overlaps_here('VFBexp_FBtp0001321', return_dataframe=True, limit=5)
141-
128+
142129
if not result.empty and 'tags' in result.columns:
143130
for tags in result['tags']:
144131
if pd.notna(tags) and tags:
145-
# Tags should be pipe-separated strings
146132
self.assertIsInstance(tags, str, "Tags should be string type")
147-
# Could contain pipes for multiple tags
148133
parts = tags.split('|')
149134
self.assertTrue(all(isinstance(p, str) for p in parts), "Tag parts should be strings")
150-
151-
print(f"\n✓ Tags format verified")
135+
136+
print(f"\nTags format verified")
152137

153138

154139
class TestAnatomyExpressedInSchema(unittest.TestCase):
155-
"""Test cases for AnatomyExpressedIn_to_schema (renamed from
156-
ExpressionOverlapsHere_to_schema in v1.13.7). The legacy name is
157-
kept as a back-compat alias and must continue to import.
158-
"""
140+
"""Test cases for AnatomyExpressedIn_to_schema."""
159141

160142
def test_schema_function_exists(self):
161-
"""Test that both the canonical and legacy schema functions are defined."""
143+
"""Canonical schema function is defined; legacy alias is gone."""
162144
self.assertTrue(hasattr(vq, 'AnatomyExpressedIn_to_schema'),
163-
"AnatomyExpressedIn_to_schema function should exist")
164-
self.assertTrue(hasattr(vq, 'ExpressionOverlapsHere_to_schema'),
165-
"Legacy ExpressionOverlapsHere_to_schema alias should still exist")
145+
"AnatomyExpressedIn_to_schema function should exist")
146+
self.assertFalse(hasattr(vq, 'ExpressionOverlapsHere_to_schema'),
147+
"Legacy ExpressionOverlapsHere_to_schema alias must be removed")
166148

167149
def test_schema_structure(self):
168-
"""Test that schema function returns proper Query object."""
150+
"""Schema function returns the expected Query object."""
169151
from vfbquery.vfb_queries import AnatomyExpressedIn_to_schema
170152

171153
schema = AnatomyExpressedIn_to_schema(
172154
"P{GAL4-per.BS} expression pattern",
173155
{"short_form": "VFBexp_FBtp0001321"},
174156
)
175157

176-
# Check Query object attributes
177158
self.assertEqual(schema.query, "AnatomyExpressedIn")
178159
self.assertEqual(schema.function, "get_expression_overlaps_here")
179160
self.assertIn("Anatomy where", schema.label)
180161
self.assertEqual(schema.preview, 5)
181162
self.assertEqual(schema.preview_columns, ["id", "name", "tags", "pubs"])
182-
183-
# Check takes structure
163+
164+
# takes constrains the input to an expression pattern (or fragment)
184165
self.assertIn("short_form", schema.takes)
185166
self.assertIn("default", schema.takes)
186-
self.assertEqual(schema.takes["short_form"], {"$and": ["Class", "Anatomy"]})
187-
188-
print("\n✓ Schema structure verified")
167+
self.assertEqual(
168+
schema.takes["short_form"],
169+
{"$or": [
170+
{"$and": ["Class", "Expression_pattern"]},
171+
{"$and": ["Class", "Expression_pattern_fragment"]},
172+
]},
173+
)
174+
175+
print("\nSchema structure verified")
176+
177+
178+
class TestAnatomyExpressedInWireMapping(unittest.TestCase):
179+
"""ha_api.QUERY_TYPE_MAP — canonical key only, legacy alias removed."""
180+
181+
def test_query_type_map(self):
182+
from vfbquery.ha_api import QUERY_TYPE_MAP
183+
self.assertIn("AnatomyExpressedIn", QUERY_TYPE_MAP,
184+
"AnatomyExpressedIn must be a recognised query_type")
185+
self.assertEqual(QUERY_TYPE_MAP["AnatomyExpressedIn"],
186+
"get_expression_overlaps_here")
187+
self.assertNotIn("ExpressionOverlapsHere", QUERY_TYPE_MAP,
188+
"Legacy ExpressionOverlapsHere alias must be removed")
189189

190190

191191
if __name__ == '__main__':
192-
# Run tests with verbose output
193192
unittest.main(verbosity=2)

src/test/test_query_performance.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -458,24 +458,26 @@ def test_10_expression_queries(self):
458458
print("EXPRESSION PATTERN QUERIES (Neo4j)")
459459
print("="*80)
460460

461-
# ExpressionOverlapsHere - test with adult brain which has many expression patterns
462-
# Warm up cache with full results
461+
# AnatomyExpressedIn - inverse direction (expression pattern ->
462+
# anatomy classes). Use VFBexp_FBtp0001321 (P{GAL4-per.BS}),
463+
# known to overlap ~50 anatomy classes in pdb.
464+
EP_EXAMPLE = 'VFBexp_FBtp0001321'
463465
try:
464-
get_expression_overlaps_here(self.test_terms['medulla'], return_dataframe=False, limit=-1)
466+
get_expression_overlaps_here(EP_EXAMPLE, return_dataframe=False, limit=-1)
465467
except Exception:
466468
pass # Ignore warm-up failures
467-
469+
468470
result, duration, success = self._time_query(
469-
"ExpressionOverlapsHere (adult brain)",
471+
"AnatomyExpressedIn (P{GAL4-per.BS} expression pattern)",
470472
get_expression_overlaps_here,
471-
self.test_terms['medulla'], # FBbt_00003982 (adult brain/medulla)
473+
EP_EXAMPLE,
472474
return_dataframe=False,
473475
limit=10 # Limit to 10 for performance test
474476
)
475-
print(f"ExpressionOverlapsHere: {duration:.4f}s {'✅' if success else '❌'}")
477+
print(f"AnatomyExpressedIn: {duration:.4f}s {'✅' if success else '❌'}")
476478
if success and result:
477-
print(f" └─ Found {result.get('count', 0)} total expression patterns, returned 10")
478-
self.assertLess(duration, self.THRESHOLD_SLOW, "ExpressionOverlapsHere exceeded threshold")
479+
print(f" └─ Found {result.get('count', 0)} total anatomy classes, returned 10")
480+
self.assertLess(duration, self.THRESHOLD_SLOW, "AnatomyExpressedIn exceeded threshold")
479481

480482
def test_11_transcriptomics_queries(self):
481483
"""Test scRNAseq transcriptomics queries"""

src/vfbquery/ha_api.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,6 @@ async def security_middleware(request, handler):
301301

302302
# Expression
303303
"AnatomyExpressedIn": "get_expression_overlaps_here",
304-
"ExpressionOverlapsHere": "get_expression_overlaps_here", # deprecated alias of AnatomyExpressedIn (kept for bookmarked URLs)
305304
"TransgeneExpressionHere": "get_transgene_expression_here",
306305

307306
# Transcriptomics

0 commit comments

Comments
 (0)