From 32b2ad654867884cde352e9a601a8f5c749c8462 Mon Sep 17 00:00:00 2001 From: Robbie Court Date: Thu, 16 Jul 2026 15:08:54 +0000 Subject: [PATCH] Fix: templates with no painted domains no longer resolve to null get_term_info returned null (HTTP 200) for templates that carry no painted anatomical domains, e.g. VFB_00050000 (L1EM larval template, 'Seymour'). Such templates still emit a single empty template_domain from the pipeline (index: [], null anatomical_individual). term_info_parse_object ran int(image.index[0]) on every domain unconditionally, so the empty index list raised IndexError: list index out of range. That propagated to get_term_info's except IndexError handler, which mis-reported it as 'Error accessing SOLR server!' and returned None -> the endpoint served null for a valid, indexed template. The doc is present in vfb_json (numFound=1); SOLR was never at fault. Guard the template_domains loop by skipping degenerate/empty domains, mirroring the existing 'if len(image.index) > 0' guard already used for template_channel. Adds a live regression test (VFB_00050000 must resolve, empty domain dropped). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PFzAZQZDiqVDkGkSprSwRw --- src/test/test_template_no_painted_domains.py | 61 ++++++++++++++++++++ src/vfbquery/vfb_queries.py | 10 ++++ 2 files changed, 71 insertions(+) create mode 100644 src/test/test_template_no_painted_domains.py diff --git a/src/test/test_template_no_painted_domains.py b/src/test/test_template_no_painted_domains.py new file mode 100644 index 0000000..74da553 --- /dev/null +++ b/src/test/test_template_no_painted_domains.py @@ -0,0 +1,61 @@ +""" +Regression test for get_term_info on templates that have NO painted anatomical +domains. + +VFB_00050000 (L1 larval CNS ssTEM - Cardona/Janelia, the "Seymour" L1EM larval +template) carries a single *empty* template_domain from the pipeline +(index: [], null anatomical_individual). The term_info parser used to do +``int(image.index[0])`` on every domain unconditionally, so the empty index +list raised ``IndexError: list index out of range``. That exception propagated +to get_term_info's ``except IndexError`` handler, which mis-reported it as a +SOLR access failure ("Error accessing SOLR server!") and returned ``None`` — +i.e. the endpoint served ``null`` for a perfectly valid, indexed template. + +This test asserts the template resolves to a real term-info object with the +degenerate domain skipped rather than crashing the whole lookup. +""" + +import os +import unittest +import sys + +# Add src directory to path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) + +from vfbquery.vfb_queries import get_term_info + + +class TemplateNoPaintedDomainsTest(unittest.TestCase): + """A template with no painted domains must still resolve, not return null.""" + + NO_DOMAIN_TEMPLATE = 'VFB_00050000' # L1EM larval template ("Seymour") + + def setUp(self): + # Force the live, synchronous parse path (bypass the two-phase preview + # warm and the shared production cache) so the fix is actually exercised. + self._prev_cache = os.environ.get('VFBQUERY_CACHE_ENABLED') + os.environ['VFBQUERY_CACHE_ENABLED'] = 'false' + + def tearDown(self): + if self._prev_cache is None: + os.environ.pop('VFBQUERY_CACHE_ENABLED', None) + else: + os.environ['VFBQUERY_CACHE_ENABLED'] = self._prev_cache + + def test_template_without_painted_domains_resolves(self): + result = get_term_info(self.NO_DOMAIN_TEMPLATE) + # The regression: this used to be None (-> null over HTTP). + self.assertIsNotNone( + result, + f"get_term_info('{self.NO_DOMAIN_TEMPLATE}') returned None; the empty " + f"template_domain must be skipped, not crash the whole lookup." + ) + self.assertEqual(result.get('Id'), self.NO_DOMAIN_TEMPLATE) + self.assertTrue(result.get('IsTemplate'), "Template flag should be set") + # An empty/degenerate domain must be dropped, never emitted with a bogus index. + for idx in (result.get('Domains') or {}): + self.assertIsNotNone(idx) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/vfbquery/vfb_queries.py b/src/vfbquery/vfb_queries.py index 16321fe..38ede4b 100644 --- a/src/vfbquery/vfb_queries.py +++ b/src/vfbquery/vfb_queries.py @@ -997,6 +997,16 @@ def term_info_parse_object(results, short_form): images = {} termInfo["IsTemplate"] = True for image in vfbTerm.template_domains: + # Skip degenerate/placeholder domains. Templates that carry no + # painted anatomical domains (e.g. VFB_00050000, the L1EM larval + # template) still emit a single empty domain from the pipeline + # (index: [], null anatomical_individual). Without this guard the + # int(image.index[0]) below raised IndexError, which propagated to + # get_term_info's `except IndexError` and was mis-reported as a SOLR + # access failure, returning null for the whole term. Mirrors the + # `if len(image.index) > 0` guard already used for template_channel. + if not image.index or len(image.index) == 0: + continue record = {} record["id"] = image.anatomical_individual.short_form label = image.anatomical_individual.label