vfbquery 0.3.2__py3-none-any.whl → 0.3.4__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.
@@ -12,7 +12,7 @@ class TermInfoQueriesTest(unittest.TestCase):
12
12
 
13
13
  def test_term_info_deserialization(self):
14
14
  terminfo_json = """
15
- {"term": {"core": {"iri": "http://purl.obolibrary.org/obo/FBbt_00048514", "symbol": "BM-Taste", "types": ["Entity", "Adult", "Anatomy", "Cell", "Class", "Mechanosensory_system", "Nervous_system", "Neuron", "Sensory_neuron"], "short_form": "FBbt_00048514", "unique_facets": ["Adult", "Mechanosensory_system", "Nervous_system", "Sensory_neuron"], "label": "labial taste bristle mechanosensory neuron"}, "description": ["Any mechanosensory neuron (FBbt:00005919) that has sensory dendrite in some labellar taste bristle (FBbt:00004162)."], "comment": []}, "query": "Get JSON for Neuron Class", "version": "3d2a474", "parents": [{"symbol": "", "iri": "http://purl.obolibrary.org/obo/FBbt_00048508", "types": ["Entity", "Anatomy", "Cell", "Class", "Mechanosensory_system", "Nervous_system", "Neuron", "Sensory_neuron"], "short_form": "FBbt_00048508", "unique_facets": ["Mechanosensory_system", "Nervous_system", "Sensory_neuron"], "label": "mechanosensory neuron of chaeta"}, {"symbol": "", "iri": "http://purl.obolibrary.org/obo/FBbt_00051420", "types": ["Entity", "Adult", "Anatomy", "Cell", "Class", "Mechanosensory_system", "Nervous_system", "Neuron", "Sensory_neuron"], "short_form": "FBbt_00051420", "unique_facets": ["Adult", "Mechanosensory_system", "Nervous_system", "Sensory_neuron"], "label": "adult mechanosensory neuron"}, {"symbol": "", "iri": "http://purl.obolibrary.org/obo/FBbt_00048029", "types": ["Entity", "Adult", "Anatomy", "Cell", "Class", "Nervous_system", "Neuron", "Sensory_neuron"], "short_form": "FBbt_00048029", "unique_facets": ["Adult", "Nervous_system", "Sensory_neuron"], "label": "labellar taste bristle sensory neuron"}], "relationships": [{"relation": {"iri": "http://purl.obolibrary.org/obo/BFO_0000050", "label": "is part of", "type": "part_of"}, "object": {"symbol": "", "iri": "http://purl.obolibrary.org/obo/FBbt_00005892", "types": ["Entity", "Adult", "Anatomy", "Class", "Nervous_system"], "short_form": "FBbt_00005892", "unique_facets": ["Adult", "Nervous_system"], "label": "adult peripheral nervous system"}}], "xrefs": [], "anatomy_channel_image": [], "pub_syn": [{"synonym": {"scope": "has_exact_synonym", "label": "labellar taste bristle mechanosensitive neuron", "type": ""}, "pub": {"core": {"symbol": "", "iri": "http://flybase.org/reports/Unattributed", "types": ["Entity", "Individual", "pub"], "short_form": "Unattributed", "unique_facets": ["pub"], "label": ""}, "FlyBase": "", "PubMed": "", "DOI": ""}}, {"synonym": {"scope": "has_exact_synonym", "label": "labellar taste bristle mechanosensory neuron", "type": ""}, "pub": {"core": {"symbol": "", "iri": "http://flybase.org/reports/Unattributed", "types": ["Entity", "Individual", "pub"], "short_form": "Unattributed", "unique_facets": ["pub"], "label": ""}, "FlyBase": "", "PubMed": "", "DOI": ""}}, {"synonym": {"scope": "has_exact_synonym", "label": "labial taste bristle mechanosensitive neuron", "type": ""}, "pub": {"core": {"symbol": "", "iri": "http://flybase.org/reports/Unattributed", "types": ["Entity", "Individual", "pub"], "short_form": "Unattributed", "unique_facets": ["pub"], "label": ""}, "FlyBase": "", "PubMed": "", "DOI": ""}}], "def_pubs": [{"core": {"symbol": "", "iri": "http://flybase.org/reports/FBrf0242472", "types": ["Entity", "Individual", "pub"], "short_form": "FBrf0242472", "unique_facets": ["pub"], "label": "Zhou et al., 2019, Sci. Adv. 5(5): eaaw5141"}, "FlyBase": "", "PubMed": "31131327", "DOI": "10.1126/sciadv.aaw5141"}], "targeting_splits": []}
15
+ {"term": {"core": {"iri": "http://purl.obolibrary.org/obo/FBbt_00048514", "symbol": "BM-Taste", "types": ["Entity", "Adult", "Anatomy", "Cell", "Class", "Mechanosensory_system", "Nervous_system", "Neuron", "Sensory_neuron"], "short_form": "FBbt_00048514", "unique_facets": ["Adult", "Mechanosensory_system", "Nervous_system", "Sensory_neuron"], "label": "labial taste bristle mechanosensory neuron"}, "description": ["Any mechanosensory neuron (FBbt:00005919) that has sensory dendrite in some labellar taste bristle (FBbt:00004162)."], "comment": []}, "query": "Get JSON for Neuron Class", "version": "3d2a474", "parents": [{"symbol": "", "iri": "http://purl.obolibrary.org/obo/FBbt_00048508", "types": ["Entity", "Anatomy", "Cell", "Class", "Mechanosensory_system", "Nervous_system", "Neuron", "Sensory_neuron"], "short_form": "FBbt_00048508", "unique_facets": ["Mechanosensory_system", "Nervous_system", "Sensory_neuron"], "label": "mechanosensory neuron of chaeta"}, {"symbol": "", "iri": "http://purl.obolibrary.org/obo/FBbt_00051420", "types": ["Entity", "Adult", "Anatomy", "Cell", "Class", "Mechanosensory_system", "Nervous_system", "Neuron", "Sensory_neuron"], "short_form": "FBbt_00051420", "unique_facets": ["Adult", "Mechanosensory_system", "Nervous_system", "Sensory_neuron"], "label": "adult mechanosensory neuron"}, {"symbol": "", "iri": "http://purl.obolibrary.org/obo/FBbt_00048029", "types": ["Entity", "Adult", "Anatomy", "Cell", "Class", "Nervous_system", "Neuron", "Sensory_neuron"], "short_form": "FBbt_00048029", "unique_facets": ["Adult", "Nervous_system", "Sensory_neuron"], "label": "labellar taste bristle sensory neuron"}], "relationships": [{"relation": {"iri": "http://purl.obolibrary.org/obo/BFO_0000050", "label": "is part of", "type": "part_of"}, "object": {"symbol": "", "iri": "http://purl.obolibrary.org/obo/FBbt_00005892", "types": ["Entity", "Adult", "Anatomy", "Class", "Nervous_system"], "short_form": "FBbt_00005892", "unique_facets": ["Adult", "Nervous_system"], "label": "adult peripheral nervous system"}}], "xrefs": [], "anatomy_channel_image": [], "pub_syn": [{"synonym": {"scope": "has_exact_synonym", "label": "labellar taste bristle mechanosensitive neuron", "type": ""}, "pub": {"core": {"symbol": "", "iri": "http://flybase.org/reports/Unattributed", "types": ["Entity", "Individual", "pub"], "short_form": "Unattributed", "unique_facets": ["pub"], "label": ""}, "FlyBase": "", "PubMed": "", "DOI": ""}}, {"synonym": {"scope": "has_exact_synonym", "label": "labellar taste bristle mechanosensitive neuron", "type": ""}, "pub": {"core": {"symbol": "", "iri": "http://flybase.org/reports/Unattributed", "types": ["Entity", "Individual", "pub"], "short_form": "Unattributed", "unique_facets": ["pub"], "label": ""}, "FlyBase": "", "PubMed": "", "DOI": ""}}, {"synonym": {"scope": "has_exact_synonym", "label": "labial taste bristle mechanosensitive neuron", "type": ""}, "pub": {"core": {"symbol": "", "iri": "http://flybase.org/reports/Unattributed", "types": ["Entity", "Individual", "pub"], "short_form": "Unattributed", "unique_facets": ["pub"], "label": ""}, "FlyBase": "", "PubMed": "", "DOI": ""}}], "def_pubs": [{"core": {"symbol": "", "iri": "http://flybase.org/reports/FBrf0242472", "types": ["Entity", "Individual", "pub"], "short_form": "FBrf0242472", "unique_facets": ["pub"], "label": "Zhou et al., 2019, Sci. Adv. 5(5): eaaw5141"}, "FlyBase": "", "PubMed": "31131327", "DOI": "10.1126/sciadv.aaw5141"}], "targeting_splits": []}
16
16
  """
17
17
 
18
18
  terminfo = deserialize_term_info(terminfo_json)
@@ -38,15 +38,21 @@ class TermInfoQueriesTest(unittest.TestCase):
38
38
  self.assertEqual("", terminfo.pub_syn[0].pub.PubMed)
39
39
 
40
40
  def test_term_info_deserialization_from_dict(self):
41
+ import pkg_resources
42
+ print("vfb_connect version:", pkg_resources.get_distribution("vfb_connect").version)
41
43
  vfbTerm = self.vc.get_TermInfo(['FBbt_00048514'], return_dataframe=False, summary=False)[0]
42
44
  start_time = time.time()
43
45
  terminfo = deserialize_term_info_from_dict(vfbTerm)
44
46
  print("--- %s seconds ---" % (time.time() - start_time))
45
- print(vfbTerm)
46
- print(terminfo)
47
+ print("vfbTerm:", vfbTerm)
48
+ print("terminfo:", terminfo)
49
+ # Add debug for unique_facets
50
+ if hasattr(terminfo.term.core, 'unique_facets'):
51
+ print("unique_facets:", terminfo.term.core.unique_facets)
52
+ else:
53
+ print("unique_facets attribute NOT present!")
47
54
 
48
55
  self.assertEqual("Get JSON for Neuron Class", terminfo.query)
49
-
50
56
  self.assertEqual("http://purl.obolibrary.org/obo/FBbt_00048514", terminfo.term.core.iri)
51
57
  self.assertEqual("BM-Taste", terminfo.term.core.symbol)
52
58
  # TODO: XXX unique facets are not in vfb_connect release
@@ -58,13 +64,24 @@ class TermInfoQueriesTest(unittest.TestCase):
58
64
 
59
65
  self.assertEqual(0, len(terminfo.xrefs))
60
66
 
61
- self.assertEqual(6, len(terminfo.pub_syn))
62
- # TODO: XXX check vfb_connect version
63
- # self.assertEqual("labellar taste bristle mechanosensitive neuron", terminfo.pub_syn[0].synonym.label)
64
- self.assertTrue("labellar taste bristle mechanosensory neuron" == terminfo.pub_syn[0].synonym.label or "labellar hMSN" == terminfo.pub_syn[0].synonym.label, "not matching synonym")
65
- self.assertEqual("FBrf0248869", terminfo.pub_syn[0].pub.core.short_form)
66
- # Update to expect the PubMed ID
67
- self.assertEqual("33657409", terminfo.pub_syn[0].pub.PubMed)
67
+ self.assertEqual(7, len(terminfo.pub_syn))
68
+
69
+ # Check that we have the expected synonym labels (order-independent)
70
+ synonym_labels = [entry.synonym.label for entry in terminfo.pub_syn]
71
+ expected_labels = ["labellar taste bristle mechanosensitive neuron", "labellar hMSN", "labial taste bristle mechanosensory neuron"]
72
+
73
+ # Check that at least one of the expected labels exists
74
+ found_labels = [label for label in expected_labels if label in synonym_labels]
75
+ self.assertTrue(len(found_labels) > 0, f"None of the expected synonym labels found. Found: {synonym_labels}")
76
+
77
+ # Check that entries with "Unattributed" pub exist (most entries should have this)
78
+ unattributed_entries = [entry for entry in terminfo.pub_syn if entry.pub.core.short_form == "Unattributed"]
79
+ self.assertTrue(len(unattributed_entries) > 0, "No entries with 'Unattributed' pub found")
80
+
81
+ # Check for the PubMed ID in the correct synonym entry (labellar hMSN)
82
+ labellar_hmsn_entry = next((entry for entry in terminfo.pub_syn if entry.synonym.label == "labellar hMSN"), None)
83
+ self.assertIsNotNone(labellar_hmsn_entry, "labellar hMSN entry not found")
84
+ self.assertEqual("33657409", labellar_hmsn_entry.pub.PubMed)
68
85
 
69
86
  def test_term_info_serialization_individual_anatomy(self):
70
87
  term_info_dict = self.vc.get_TermInfo(['VFB_00010001'], return_dataframe=False, summary=False)[0]
@@ -128,7 +145,7 @@ class TermInfoQueriesTest(unittest.TestCase):
128
145
  self.assertFalse("link" in serialized)
129
146
  self.assertEqual(4, len(serialized["types"]))
130
147
  self.assertTrue("Anatomy" in serialized["types"])
131
- 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"])
132
149
  self.assertTrue("synonyms" in serialized)
133
150
  self.assertEqual(1, len(serialized["synonyms"]))
134
151
  self.assertEqual("has_exact_synonym: germarial 2-cell cluster ([King, 1970](FBrf0021038))", serialized["synonyms"][0])
@@ -149,13 +166,10 @@ class TermInfoQueriesTest(unittest.TestCase):
149
166
  self.assertFalse("examples" in serialized)
150
167
  self.assertFalse("thumbnail" in serialized)
151
168
  self.assertTrue("references" in serialized)
152
- self.assertEqual(2, len(serialized["references"]))
153
- self.assertEqual({'link': '[Spradling, 1993, Bate, Martinez Arias, 1993: 1--70](FBrf0064777)',
154
- 'refs': ['http://flybase.org/reports/FBrf0064777'],
155
- 'types': ' pub'}, serialized["references"][0])
169
+ self.assertEqual(1, len(serialized["references"]))
156
170
  self.assertEqual({'link': '[King, 1970, Ovarian Development in Drosophila melanogaster. ](FBrf0021038)',
157
171
  'refs': ['http://flybase.org/reports/FBrf0021038'],
158
- 'types': ' pub'}, serialized["references"][1])
172
+ 'types': ' pub'}, serialized["references"][0])
159
173
  self.assertFalse("targetingSplits" in serialized)
160
174
  self.assertFalse("targetingNeurons" in serialized)
161
175
 
@@ -244,7 +258,7 @@ class TermInfoQueriesTest(unittest.TestCase):
244
258
  self.assertTrue("Turner-Evans et al., 2020" in description)
245
259
 
246
260
  self.assertTrue("synonyms" in serialized)
247
- self.assertEqual(9, len(serialized["synonyms"]))
261
+ self.assertEqual(10, len(serialized["synonyms"]))
248
262
  print(serialized["synonyms"][0])
249
263
  self.assertTrue("has_exact_synonym: EB-PB 1 glomerulus-D/Vgall neuron" in serialized["synonyms"])
250
264
  self.assertFalse("source" in serialized)
@@ -276,7 +290,7 @@ class TermInfoQueriesTest(unittest.TestCase):
276
290
  self.assertFalse("thumbnail" in serialized)
277
291
 
278
292
  self.assertTrue("references" in serialized)
279
- self.assertEqual(6, len(serialized["references"]))
293
+ self.assertEqual(7, len(serialized["references"]))
280
294
 
281
295
  self.assertTrue("targetingSplits" in serialized)
282
296
  self.assertEqual(6, len(serialized["targetingSplits"]))
@@ -329,7 +343,7 @@ class TermInfoQueriesTest(unittest.TestCase):
329
343
  '(http://splitgal4.janelia.org/cgi-bin/view_splitgal4_imagery.cgi?line=SS50574) '},
330
344
  serialized["xrefs"][0])
331
345
 
332
- self.assertFalse("examples" in serialized)
346
+ self.assertTrue("examples" in serialized)
333
347
  self.assertFalse("thumbnail" in serialized)
334
348
  self.assertFalse("references" in serialized)
335
349
  self.assertFalse("targetingSplits" in serialized)
vfbquery/__init__.py CHANGED
@@ -1 +1,4 @@
1
1
  from .vfb_queries import *
2
+
3
+ # Version information
4
+ __version__ = "0.3.4"
vfbquery/solr_fetcher.py CHANGED
@@ -3,7 +3,6 @@ import json
3
3
  import logging
4
4
  import pandas as pd
5
5
  from typing import List, Dict, Any, Optional, Union
6
- from vfb_connect import vfb
7
6
 
8
7
  class SolrTermInfoFetcher:
9
8
  """Fetches term information directly from the Solr server instead of using VfbConnect"""
@@ -12,7 +11,19 @@ class SolrTermInfoFetcher:
12
11
  """Initialize with the Solr server URL"""
13
12
  self.solr_url = solr_url
14
13
  self.logger = logging.getLogger(__name__)
15
- self.vfb = vfb
14
+ self._vfb = None # Lazy load vfb_connect
15
+
16
+ @property
17
+ def vfb(self):
18
+ """Lazy load vfb_connect to avoid import issues during testing"""
19
+ if self._vfb is None:
20
+ try:
21
+ from vfb_connect import vfb
22
+ self._vfb = vfb
23
+ except ImportError as e:
24
+ self.logger.error(f"Could not import vfb_connect: {e}")
25
+ raise ImportError("vfb_connect is required but could not be imported")
26
+ return self._vfb
16
27
 
17
28
  def get_TermInfo(self, short_forms: List[str],
18
29
  return_dataframe: bool = False,
@@ -1,5 +1,21 @@
1
1
  import re
2
2
  import json
3
+ import numpy as np
4
+
5
+ # Custom JSON encoder to handle NumPy and pandas types
6
+ class NumpyEncoder(json.JSONEncoder):
7
+ def default(self, obj):
8
+ if isinstance(obj, np.integer):
9
+ return int(obj)
10
+ elif isinstance(obj, np.floating):
11
+ return float(obj)
12
+ elif isinstance(obj, np.ndarray):
13
+ return obj.tolist()
14
+ elif isinstance(obj, np.bool_):
15
+ return bool(obj)
16
+ elif hasattr(obj, 'item'): # Handle pandas scalar types
17
+ return obj.item()
18
+ return super(NumpyEncoder, self).default(obj)
3
19
  import requests
4
20
  from dataclasses import dataclass
5
21
  from dataclasses_json import dataclass_json
@@ -15,7 +31,7 @@ class Coordinates:
15
31
  Z: float
16
32
 
17
33
  def __str__(self):
18
- return json.dumps([str(self.X), str(self.Y), str(self.Z)])
34
+ return json.dumps([str(self.X), str(self.Y), str(self.Z)], cls=NumpyEncoder)
19
35
 
20
36
 
21
37
  class CoordinatesFactory:
@@ -1062,7 +1078,7 @@ def serialize_term_info_to_json(vfb_term: VfbTerminfo, show_types=False) -> str:
1062
1078
  :return: json string representation of the term info object
1063
1079
  """
1064
1080
  term_info_dict = serialize_term_info_to_dict(vfb_term, show_types)
1065
- return json.dumps(term_info_dict, indent=4)
1081
+ return json.dumps(term_info_dict, indent=4, cls=NumpyEncoder)
1066
1082
 
1067
1083
 
1068
1084
  def process(term_info_response: dict, variable, loaded_template: Optional[str] = None, show_types=False) -> dict:
vfbquery/test_utils.py CHANGED
@@ -1,6 +1,41 @@
1
1
  import pandas as pd
2
+ import json
3
+ import numpy as np
2
4
  from typing import Any, Dict, Union
3
5
 
6
+ # Custom JSON encoder to handle NumPy and pandas types
7
+ class NumpyEncoder(json.JSONEncoder):
8
+ def default(self, obj):
9
+ if isinstance(obj, np.integer):
10
+ return int(obj)
11
+ elif isinstance(obj, np.floating):
12
+ return float(obj)
13
+ elif isinstance(obj, np.ndarray):
14
+ return obj.tolist()
15
+ elif isinstance(obj, np.bool_):
16
+ return bool(obj)
17
+ elif hasattr(obj, 'item'): # Handle pandas scalar types
18
+ return obj.item()
19
+ return super(NumpyEncoder, self).default(obj)
20
+
21
+ def safe_to_dict(df, sort_by_id=True):
22
+ """Convert DataFrame to dict with numpy types converted to native Python types"""
23
+ if isinstance(df, pd.DataFrame):
24
+ # Convert numpy dtypes to native Python types
25
+ df_copy = df.copy()
26
+ for col in df_copy.columns:
27
+ if df_copy[col].dtype.name.startswith('int'):
28
+ df_copy[col] = df_copy[col].astype('object')
29
+ elif df_copy[col].dtype.name.startswith('float'):
30
+ df_copy[col] = df_copy[col].astype('object')
31
+
32
+ # Sort by id column in descending order if it exists and sort_by_id is True
33
+ if sort_by_id and 'id' in df_copy.columns:
34
+ df_copy = df_copy.sort_values('id', ascending=False)
35
+
36
+ return df_copy.to_dict("records")
37
+ return df
38
+
4
39
  def safe_extract_row(result: Any, index: int = 0) -> Dict:
5
40
  """
6
41
  Safely extract a row from a pandas DataFrame or return the object itself if not a DataFrame.
@@ -11,11 +46,83 @@ def safe_extract_row(result: Any, index: int = 0) -> Dict:
11
46
  """
12
47
  if isinstance(result, pd.DataFrame):
13
48
  if not result.empty and len(result.index) > index:
14
- return result.iloc[index].to_dict()
49
+ # Convert to dict using safe method to handle numpy types
50
+ row_series = result.iloc[index]
51
+ return {col: (val.item() if hasattr(val, 'item') else val) for col, val in row_series.items()}
15
52
  else:
16
53
  return {}
17
54
  return result
18
55
 
56
+ def sanitize_for_json(obj: Any) -> Any:
57
+ """
58
+ Recursively sanitize any data structure to make it JSON serializable.
59
+ Converts numpy types, pandas types, and other non-serializable types to native Python types.
60
+
61
+ :param obj: Object to sanitize
62
+ :return: JSON-serializable version of the object
63
+ """
64
+ if isinstance(obj, dict):
65
+ return {key: sanitize_for_json(value) for key, value in obj.items()}
66
+ elif isinstance(obj, (list, tuple)):
67
+ return [sanitize_for_json(item) for item in obj]
68
+ elif isinstance(obj, np.integer):
69
+ return int(obj)
70
+ elif isinstance(obj, np.floating):
71
+ return float(obj)
72
+ elif isinstance(obj, np.ndarray):
73
+ return obj.tolist()
74
+ elif isinstance(obj, np.bool_):
75
+ return bool(obj)
76
+ elif hasattr(obj, 'item'): # Handle pandas scalar types
77
+ return obj.item()
78
+ elif isinstance(obj, pd.DataFrame):
79
+ return safe_to_dict(obj)
80
+ elif hasattr(obj, '__dict__'): # Handle custom objects
81
+ return sanitize_for_json(obj.__dict__)
82
+ else:
83
+ return obj
84
+
85
+ def safe_json_dumps(obj: Any, **kwargs) -> str:
86
+ """
87
+ Safely serialize any object to JSON string, handling numpy and pandas types.
88
+
89
+ :param obj: Object to serialize
90
+ :param kwargs: Additional arguments to pass to json.dumps
91
+ :return: JSON string
92
+ """
93
+ # Set default arguments
94
+ default_kwargs = {'indent': 2, 'ensure_ascii': False, 'cls': NumpyEncoder}
95
+ default_kwargs.update(kwargs)
96
+
97
+ try:
98
+ # First try with the NumpyEncoder
99
+ return json.dumps(obj, **default_kwargs)
100
+ except (TypeError, ValueError):
101
+ # If that fails, sanitize the object first
102
+ sanitized_obj = sanitize_for_json(obj)
103
+ return json.dumps(sanitized_obj, **default_kwargs)
104
+
105
+ def pretty_print_vfb_result(result: Any, max_length: int = 1000) -> None:
106
+ """
107
+ Pretty print any VFB query result in a safe, readable format.
108
+
109
+ :param result: Result from any VFB query function
110
+ :param max_length: Maximum length of output (truncates if longer)
111
+ """
112
+ try:
113
+ json_str = safe_json_dumps(result)
114
+ if len(json_str) > max_length:
115
+ print(json_str[:max_length] + f'\n... (truncated, full length: {len(json_str)} characters)')
116
+ else:
117
+ print(json_str)
118
+ except Exception as e:
119
+ print(f'Error printing result: {e}')
120
+ print(f'Result type: {type(result)}')
121
+ if hasattr(result, '__dict__'):
122
+ print(f'Result attributes: {list(result.__dict__.keys())}')
123
+ else:
124
+ print(f'Result: {str(result)[:max_length]}...')
125
+
19
126
  def patch_vfb_connect_query_wrapper():
20
127
  """
21
128
  Apply monkey patches to VfbConnect.neo_query_wrapper to make it handle DataFrame results safely.
@@ -28,8 +135,8 @@ def patch_vfb_connect_query_wrapper():
28
135
  def patched_get_term_info(self, terms, *args, **kwargs):
29
136
  result = original_get_term_info(self, terms, *args, **kwargs)
30
137
  if isinstance(result, pd.DataFrame):
31
- # Return list of row dictionaries instead of DataFrame
32
- return [row.to_dict() for i, row in result.iterrows()]
138
+ # Return list of row dictionaries instead of DataFrame using safe conversion
139
+ return safe_to_dict(result)
33
140
  return result
34
141
 
35
142
  NeoQueryWrapper._get_TermInfo = patched_get_term_info
vfbquery/vfb_queries.py CHANGED
@@ -2,13 +2,55 @@ import pysolr
2
2
  from .term_info_queries import deserialize_term_info
3
3
  # Replace VfbConnect import with our new SolrTermInfoFetcher
4
4
  from .solr_fetcher import SolrTermInfoFetcher
5
- # Keep dict_cursor if it's used elsewhere
6
- from vfb_connect.cross_server_tools import dict_cursor
5
+ # Keep dict_cursor if it's used elsewhere - lazy import to avoid GUI issues
7
6
  from marshmallow import Schema, fields, post_load
8
7
  from typing import List, Tuple, Dict, Any, Union
9
8
  import pandas as pd
10
9
  from marshmallow import ValidationError
11
10
  import json
11
+ import numpy as np
12
+
13
+ # Custom JSON encoder to handle NumPy and pandas types
14
+ class NumpyEncoder(json.JSONEncoder):
15
+ def default(self, obj):
16
+ if isinstance(obj, np.integer):
17
+ return int(obj)
18
+ elif isinstance(obj, np.floating):
19
+ return float(obj)
20
+ elif isinstance(obj, np.ndarray):
21
+ return obj.tolist()
22
+ elif isinstance(obj, np.bool_):
23
+ return bool(obj)
24
+ elif hasattr(obj, 'item'): # Handle pandas scalar types
25
+ return obj.item()
26
+ return super(NumpyEncoder, self).default(obj)
27
+
28
+ def safe_to_dict(df, sort_by_id=True):
29
+ """Convert DataFrame to dict with numpy types converted to native Python types"""
30
+ if isinstance(df, pd.DataFrame):
31
+ # Convert numpy dtypes to native Python types
32
+ df_copy = df.copy()
33
+ for col in df_copy.columns:
34
+ if df_copy[col].dtype.name.startswith('int'):
35
+ df_copy[col] = df_copy[col].astype('object')
36
+ elif df_copy[col].dtype.name.startswith('float'):
37
+ df_copy[col] = df_copy[col].astype('object')
38
+
39
+ # Sort by id column in descending order if it exists and sort_by_id is True
40
+ if sort_by_id and 'id' in df_copy.columns:
41
+ df_copy = df_copy.sort_values('id', ascending=False)
42
+
43
+ return df_copy.to_dict("records")
44
+ return df
45
+
46
+ # Lazy import for dict_cursor to avoid GUI library issues
47
+ def get_dict_cursor():
48
+ """Lazy import dict_cursor to avoid import issues during testing"""
49
+ try:
50
+ from vfb_connect.cross_server_tools import dict_cursor
51
+ return dict_cursor
52
+ except ImportError as e:
53
+ raise ImportError(f"vfb_connect is required but could not be imported: {e}")
12
54
 
13
55
  # Connect to the VFB SOLR server
14
56
  vfb_solr = pysolr.Solr('http://solr.virtualflybrain.org/solr/vfb_json/', always_commit=False, timeout=990)
@@ -481,6 +523,11 @@ def term_info_parse_object(results, short_form):
481
523
  if "image_" in key and not ("thumbnail" in key or "folder" in key) and len(vars(image.channel_image.image)[key]) > 1:
482
524
  record[key.replace("image_","")] = vars(image.channel_image.image)[key].replace("http://","https://")
483
525
  images[image.channel_image.image.template_anatomy.short_form].append(record)
526
+
527
+ # Sort each template's images by id in descending order (newest first)
528
+ for template_key in images:
529
+ images[template_key] = sorted(images[template_key], key=lambda x: x["id"], reverse=True)
530
+
484
531
  termInfo["Examples"] = images
485
532
  # add a query to `queries` list for listing all available images
486
533
  q = ListAllAvailableImages_to_schema(termInfo["Name"], {"short_form":vfbTerm.term.core.short_form})
@@ -504,6 +551,11 @@ def term_info_parse_object(results, short_form):
504
551
  if "image_" in key and not ("thumbnail" in key or "folder" in key) and len(vars(image.image)[key]) > 1:
505
552
  record[key.replace("image_","")] = vars(image.image)[key].replace("http://","https://")
506
553
  images[image.image.template_anatomy.short_form].append(record)
554
+
555
+ # Sort each template's images by id in descending order (newest first)
556
+ for template_key in images:
557
+ images[template_key] = sorted(images[template_key], key=lambda x: x["id"], reverse=True)
558
+
507
559
  # Add the thumbnails to the term info
508
560
  termInfo["Images"] = images
509
561
 
@@ -525,13 +577,29 @@ def term_info_parse_object(results, short_form):
525
577
  images = {}
526
578
  image = vfbTerm.template_channel
527
579
  record = {}
528
- record["id"] = vfbTerm.template_channel.channel.short_form
529
- label = vfbTerm.template_channel.channel.label
580
+
581
+ # Validate that the channel ID matches the template ID (numeric part should be the same)
582
+ template_id = vfbTerm.term.core.short_form
583
+ channel_id = vfbTerm.template_channel.channel.short_form
584
+
585
+ # Extract numeric parts for validation
586
+ if template_id and channel_id:
587
+ template_numeric = template_id.replace("VFB_", "") if template_id.startswith("VFB_") else ""
588
+ channel_numeric = channel_id.replace("VFBc_", "") if channel_id.startswith("VFBc_") else ""
589
+
590
+ if template_numeric != channel_numeric:
591
+ print(f"Warning: Template ID {template_id} does not match channel ID {channel_id}")
592
+ label = vfbTerm.template_channel.channel.label
593
+ record["id"] = channel_id
594
+ else:
595
+ label = vfbTerm.term.core.label
596
+ record["id"] = template_id
597
+
530
598
  if vfbTerm.template_channel.channel.symbol != "" and len(vfbTerm.template_channel.channel.symbol) > 0:
531
599
  label = vfbTerm.template_channel.channel.symbol
532
600
  record["label"] = label
533
- if not vfbTerm.template_channel.channel.short_form in images.keys():
534
- images[vfbTerm.template_channel.channel.short_form]=[]
601
+ if not template_id in images.keys():
602
+ images[template_id]=[]
535
603
  record["thumbnail"] = image.image_thumbnail.replace("http://","https://").replace("thumbnailT.png","thumbnail.png")
536
604
  record["thumbnail_transparent"] = image.image_thumbnail.replace("http://","https://").replace("thumbnail.png","thumbnailT.png")
537
605
  for key in vars(image).keys():
@@ -549,7 +617,7 @@ def term_info_parse_object(results, short_form):
549
617
  record['voxel'] = image.get_voxel()
550
618
  if 'orientation' in image_vars.keys():
551
619
  record['orientation'] = image.orientation
552
- images[vfbTerm.template_channel.channel.short_form].append(record)
620
+ images[template_id].append(record)
553
621
 
554
622
  # Add the thumbnails to the term info
555
623
  termInfo["Images"] = images
@@ -756,8 +824,13 @@ def ListAllAvailableImages_to_schema(name, take_default):
756
824
  return Query(query=query, label=label, function=function, takes=takes, preview=preview, preview_columns=preview_columns)
757
825
 
758
826
  def serialize_solr_output(results):
759
- # Serialize the sanitized dictionary to JSON
760
- json_string = json.dumps(results.docs[0], ensure_ascii=False)
827
+ # Create a copy of the document and remove Solr-specific fields
828
+ doc = dict(results.docs[0])
829
+ # Remove the _version_ field which can cause serialization issues with large integers
830
+ doc.pop('_version_', None)
831
+
832
+ # Serialize the sanitized dictionary to JSON using NumpyEncoder
833
+ json_string = json.dumps(doc, ensure_ascii=False, cls=NumpyEncoder)
761
834
  json_string = json_string.replace('\\', '')
762
835
  json_string = json_string.replace('"{', '{')
763
836
  json_string = json_string.replace('}"', '}')
@@ -822,7 +895,7 @@ def get_instances(short_form: str, return_dataframe=True, limit: int = -1):
822
895
  RETURN COUNT(r) AS total_count
823
896
  """
824
897
  count_results = vc.nc.commit_list([count_query])
825
- count_df = pd.DataFrame.from_records(dict_cursor(count_results))
898
+ count_df = pd.DataFrame.from_records(get_dict_cursor()(count_results))
826
899
  total_count = count_df['total_count'][0] if not count_df.empty else 0
827
900
 
828
901
  # Define the main Cypher query
@@ -852,7 +925,7 @@ def get_instances(short_form: str, return_dataframe=True, limit: int = -1):
852
925
  results = vc.nc.commit_list([query])
853
926
 
854
927
  # Convert the results to a DataFrame
855
- df = pd.DataFrame.from_records(dict_cursor(results))
928
+ df = pd.DataFrame.from_records(get_dict_cursor()(results))
856
929
 
857
930
  columns_to_encode = ['label', 'parent', 'source', 'source_id', 'template', 'dataset', 'license', 'thumbnail']
858
931
  df = encode_markdown_links(df, columns_to_encode)
@@ -890,7 +963,7 @@ def get_instances(short_form: str, return_dataframe=True, limit: int = -1):
890
963
  "thumbnail"
891
964
  ]
892
965
  }
893
- for row in df.to_dict("records")
966
+ for row in safe_to_dict(df)
894
967
  ],
895
968
  "count": total_count
896
969
  }
@@ -910,7 +983,7 @@ def get_templates(limit: int = -1, return_dataframe: bool = False):
910
983
  RETURN COUNT(DISTINCT t) AS total_count"""
911
984
 
912
985
  count_results = vc.nc.commit_list([count_query])
913
- count_df = pd.DataFrame.from_records(dict_cursor(count_results))
986
+ count_df = pd.DataFrame.from_records(get_dict_cursor()(count_results))
914
987
  total_count = count_df['total_count'][0] if not count_df.empty else 0
915
988
 
916
989
  # Define the main Cypher query
@@ -935,7 +1008,7 @@ def get_templates(limit: int = -1, return_dataframe: bool = False):
935
1008
  results = vc.nc.commit_list([query])
936
1009
 
937
1010
  # Convert the results to a DataFrame
938
- df = pd.DataFrame.from_records(dict_cursor(results))
1011
+ df = pd.DataFrame.from_records(get_dict_cursor()(results))
939
1012
 
940
1013
  columns_to_encode = ['name', 'dataset', 'license', 'thumbnail']
941
1014
  df = encode_markdown_links(df, columns_to_encode)
@@ -978,7 +1051,7 @@ def get_templates(limit: int = -1, return_dataframe: bool = False):
978
1051
  "license"
979
1052
  ]
980
1053
  }
981
- for row in df.to_dict("records")
1054
+ for row in safe_to_dict(df)
982
1055
  ],
983
1056
  "count": total_count
984
1057
  }
@@ -1037,7 +1110,7 @@ def get_similar_neurons(neuron, similarity_score='NBLAST_score', return_datafram
1037
1110
  RETURN COUNT(DISTINCT n2) AS total_count"""
1038
1111
 
1039
1112
  count_results = vc.nc.commit_list([count_query])
1040
- count_df = pd.DataFrame.from_records(dict_cursor(count_results))
1113
+ count_df = pd.DataFrame.from_records(get_dict_cursor()(count_results))
1041
1114
  total_count = count_df['total_count'][0] if not count_df.empty else 0
1042
1115
 
1043
1116
  main_query = f"""MATCH (c1:Class)<-[:INSTANCEOF]-(n1)-[r:has_similar_morphology_to]-(n2)-[:INSTANCEOF]->(c2:Class)
@@ -1063,7 +1136,7 @@ def get_similar_neurons(neuron, similarity_score='NBLAST_score', return_datafram
1063
1136
  results = vc.nc.commit_list([main_query])
1064
1137
 
1065
1138
  # Convert the results to a DataFrame
1066
- df = pd.DataFrame.from_records(dict_cursor(results))
1139
+ df = pd.DataFrame.from_records(get_dict_cursor()(results))
1067
1140
 
1068
1141
  columns_to_encode = ['name', 'source', 'source_id', 'thumbnail']
1069
1142
  df = encode_markdown_links(df, columns_to_encode)
@@ -1094,7 +1167,7 @@ def get_similar_neurons(neuron, similarity_score='NBLAST_score', return_datafram
1094
1167
  "thumbnail"
1095
1168
  ]
1096
1169
  }
1097
- for row in df.to_dict("records")
1170
+ for row in safe_to_dict(df)
1098
1171
  ],
1099
1172
  "count": total_count
1100
1173
  }
@@ -1127,7 +1200,7 @@ def get_individual_neuron_inputs(neuron_short_form: str, return_dataframe=True,
1127
1200
  RETURN COUNT(DISTINCT c) AS total_count"""
1128
1201
 
1129
1202
  count_results = vc.nc.commit_list([count_query])
1130
- count_df = pd.DataFrame.from_records(dict_cursor(count_results))
1203
+ count_df = pd.DataFrame.from_records(get_dict_cursor()(count_results))
1131
1204
  total_count = count_df['total_count'][0] if not count_df.empty else 0
1132
1205
 
1133
1206
  # Define the part of the query for normal mode
@@ -1166,7 +1239,7 @@ def get_individual_neuron_inputs(neuron_short_form: str, return_dataframe=True,
1166
1239
  results = vc.nc.commit_list([query])
1167
1240
 
1168
1241
  # Convert the results to a DataFrame
1169
- df = pd.DataFrame.from_records(dict_cursor(results))
1242
+ df = pd.DataFrame.from_records(get_dict_cursor()(results))
1170
1243
 
1171
1244
  columns_to_encode = ['Neurotransmitter', 'Type', 'Name', 'Template_Space', 'Imaging_Technique', 'thumbnail']
1172
1245
  df = encode_markdown_links(df, columns_to_encode)
@@ -1204,7 +1277,7 @@ def get_individual_neuron_inputs(neuron_short_form: str, return_dataframe=True,
1204
1277
  "Images"
1205
1278
  ]
1206
1279
  }
1207
- for row in df.to_dict("records")
1280
+ for row in safe_to_dict(df)
1208
1281
  ],
1209
1282
  "count": total_count
1210
1283
  }
@@ -1224,7 +1297,7 @@ def get_individual_neuron_inputs(neuron_short_form: str, return_dataframe=True,
1224
1297
  "Weight",
1225
1298
  ]
1226
1299
  }
1227
- for row in df.to_dict("records")
1300
+ for row in safe_to_dict(df)
1228
1301
  ],
1229
1302
  "count": total_count
1230
1303
  }
@@ -1289,7 +1362,7 @@ def fill_query_results(term_info):
1289
1362
  filtered_item = item
1290
1363
  filtered_result.append(filtered_item)
1291
1364
  elif isinstance(result, pd.DataFrame):
1292
- filtered_result = result[query['preview_columns']].to_dict('records')
1365
+ filtered_result = safe_to_dict(result[query['preview_columns']])
1293
1366
  else:
1294
1367
  print(f"Unsupported result format for filtering columns in {query['function']}")
1295
1368
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vfbquery
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Wrapper for querying VirtualFlyBrain knowledge graph.
5
5
  Home-page: https://github.com/VirtualFlyBrain/VFBquery
6
6
  Author: VirtualFlyBrain
@@ -53,7 +53,7 @@ vfb.get_term_info('FBbt_00003748')
53
53
  "Meta": {
54
54
  "Name": "[medulla](FBbt_00003748)",
55
55
  "Description": "The second optic neuropil, sandwiched between the lamina and the lobula complex. It is divided into 10 layers: 1-6 make up the outer (distal) medulla, the seventh (or serpentine) layer exhibits a distinct architecture and layers 8-10 make up the inner (proximal) medulla (Ito et al., 2014).",
56
- "Comment": "",
56
+ "Comment": "Nern et al. (2025) - doi:10.1038/s41586-025-08746-0 say distal is M1-5 and M6-7 is central medulla.",
57
57
  "Types": "[anterior ectoderm derivative](FBbt_00025991); [synaptic neuropil domain](FBbt_00040007)",
58
58
  "Relationships": "[develops from](RO_0002202): [medulla anlage](FBbt_00001935); [is part of](BFO_0000050): [adult optic lobe](FBbt_00003701)"
59
59
  },
@@ -143,19 +143,18 @@ vfb.get_term_info('FBbt_00003748')
143
143
  "count": 4
144
144
  }
145
145
  ],
146
- "IsIndividual": False,
147
- "Images": {},
148
- "IsClass": True,
146
+ "IsIndividual": false,
147
+ "IsClass": true,
149
148
  "Examples": {
150
- "VFB_00030786": [
149
+ "VFB_00101384": [
151
150
  {
152
- "id": "VFB_00030810",
153
- "label": "medulla on adult brain template Ito2014",
154
- "thumbnail": "https://www.virtualflybrain.org/data/VFB/i/0003/0810/VFB_00030786/thumbnail.png",
155
- "thumbnail_transparent": "https://www.virtualflybrain.org/data/VFB/i/0003/0810/VFB_00030786/thumbnailT.png",
156
- "nrrd": "https://www.virtualflybrain.org/data/VFB/i/0003/0810/volume.nrrd",
157
- "wlz": "https://www.virtualflybrain.org/data/VFB/i/0003/0810/volume.wlz",
158
- "obj": "https://www.virtualflybrain.org/data/VFB/i/0003/0810/volume_man.obj"
151
+ "id": "VFB_00101385",
152
+ "label": "ME(R) on JRC_FlyEM_Hemibrain",
153
+ "thumbnail": "https://www.virtualflybrain.org/data/VFB/i/0010/1385/VFB_00101384/thumbnail.png",
154
+ "thumbnail_transparent": "https://www.virtualflybrain.org/data/VFB/i/0010/1385/VFB_00101384/thumbnailT.png",
155
+ "nrrd": "https://www.virtualflybrain.org/data/VFB/i/0010/1385/VFB_00101384/volume.nrrd",
156
+ "wlz": "https://www.virtualflybrain.org/data/VFB/i/0010/1385/VFB_00101384/volume.wlz",
157
+ "obj": "https://www.virtualflybrain.org/data/VFB/i/0010/1385/VFB_00101384/volume_man.obj"
159
158
  }
160
159
  ],
161
160
  "VFB_00101567": [
@@ -180,22 +179,19 @@ vfb.get_term_info('FBbt_00003748')
180
179
  "obj": "https://www.virtualflybrain.org/data/VFB/i/0003/0624/VFB_00017894/volume_man.obj"
181
180
  }
182
181
  ],
183
- "VFB_00101384": [
182
+ "VFB_00030786": [
184
183
  {
185
- "id": "VFB_00101385",
186
- "label": "ME(R) on JRC_FlyEM_Hemibrain",
187
- "thumbnail": "https://www.virtualflybrain.org/data/VFB/i/0010/1385/VFB_00101384/thumbnail.png",
188
- "thumbnail_transparent": "https://www.virtualflybrain.org/data/VFB/i/0010/1385/VFB_00101384/thumbnailT.png",
189
- "nrrd": "https://www.virtualflybrain.org/data/VFB/i/0010/1385/VFB_00101384/volume.nrrd",
190
- "wlz": "https://www.virtualflybrain.org/data/VFB/i/0010/1385/VFB_00101384/volume.wlz",
191
- "obj": "https://www.virtualflybrain.org/data/VFB/i/0010/1385/VFB_00101384/volume_man.obj"
184
+ "id": "VFB_00030810",
185
+ "label": "medulla on adult brain template Ito2014",
186
+ "thumbnail": "https://www.virtualflybrain.org/data/VFB/i/0003/0810/VFB_00030786/thumbnail.png",
187
+ "thumbnail_transparent": "https://www.virtualflybrain.org/data/VFB/i/0003/0810/VFB_00030786/thumbnailT.png",
188
+ "nrrd": "https://www.virtualflybrain.org/data/VFB/i/0003/0810/VFB_00030786/volume.nrrd",
189
+ "wlz": "https://www.virtualflybrain.org/data/VFB/i/0003/0810/VFB_00030786/volume.wlz",
190
+ "obj": "https://www.virtualflybrain.org/data/VFB/i/0003/0810/VFB_00030786/volume_man.obj"
192
191
  }
193
192
  ]
194
193
  },
195
- "IsTemplate": False,
196
- "Domains": {},
197
- "Licenses": {},
198
- "Publications": [],
194
+ "IsTemplate": false,
199
195
  "Synonyms": [
200
196
  {
201
197
  "label": "ME",
@@ -203,12 +199,6 @@ vfb.get_term_info('FBbt_00003748')
203
199
  "type": "",
204
200
  "publication": "[Ito et al., 2014](FBrf0224194)"
205
201
  },
206
- {
207
- "label": "m",
208
- "scope": "has_related_synonym",
209
- "type": "",
210
- "publication": ""
211
- },
212
202
  {
213
203
  "label": "Med",
214
204
  "scope": "has_exact_synonym",
@@ -220,6 +210,12 @@ vfb.get_term_info('FBbt_00003748')
220
210
  "scope": "has_exact_synonym",
221
211
  "type": "",
222
212
  "publication": "[Venkatesh and Shyamala, 2010](FBrf0212889)"
213
+ },
214
+ {
215
+ "label": "m",
216
+ "scope": "has_related_synonym",
217
+ "type": "",
218
+ "publication": ""
223
219
  }
224
220
  ]
225
221
  }
@@ -259,7 +255,8 @@ vfb.get_term_info('VFB_00000001')
259
255
  "Tags": [
260
256
  "Adult",
261
257
  "Expression_pattern_fragment",
262
- "Neuron"
258
+ "Neuron",
259
+ "lineage_CM3"
263
260
  ],
264
261
  "Queries": [
265
262
  {
@@ -325,41 +322,41 @@ vfb.get_term_info('VFB_00000001')
325
322
  "id": "VFB_00000333",
326
323
  "score": "0.61",
327
324
  "name": "[fru-M-000204](VFB_00000333)",
328
- "tags": "Expression_pattern_fragment|Neuron|Adult",
329
- "thumbnail": "[![fru-M-000204 aligned to JFRC2](http://virtualflybrain.org/reports/VFB_00000333/thumbnail.png 'fru-M-000204 aligned to JFRC2')](VFB_00017894,VFB_00000333)"
325
+ "tags": "Expression_pattern_fragment|Neuron|Adult|lineage_CM3",
326
+ "thumbnail": "[![fru-M-000204 aligned to JFRC2](http://www.virtualflybrain.org/data/VFB/i/0000/0333/VFB_00017894/thumbnail.png 'fru-M-000204 aligned to JFRC2')](VFB_00017894,VFB_00000333)"
330
327
  },
331
328
  {
332
329
  "id": "VFB_00000333",
333
330
  "score": "0.61",
334
331
  "name": "[fru-M-000204](VFB_00000333)",
335
- "tags": "Expression_pattern_fragment|Neuron|Adult",
336
- "thumbnail": "[![fru-M-000204 aligned to JRC2018U](http://virtualflybrain.org/reports/VFB_00000333/thumbnail.png 'fru-M-000204 aligned to JRC2018U')](VFB_00101567,VFB_00000333)"
332
+ "tags": "Expression_pattern_fragment|Neuron|Adult|lineage_CM3",
333
+ "thumbnail": "[![fru-M-000204 aligned to JRC2018U](http://www.virtualflybrain.org/data/VFB/i/0000/0333/VFB_00101567/thumbnail.png 'fru-M-000204 aligned to JRC2018U')](VFB_00101567,VFB_00000333)"
337
334
  },
338
335
  {
339
336
  "id": "VFB_00002439",
340
337
  "score": "0.6",
341
338
  "name": "[fru-M-900020](VFB_00002439)",
342
- "tags": "Expression_pattern_fragment|Neuron|Adult",
343
- "thumbnail": "[![fru-M-900020 aligned to JFRC2](http://www.virtualflybrain.org/data/VFB/i/0000/2439/VFB_00017894/thumbnail.png 'fru-M-900020 aligned to JFRC2')](VFB_00017894,VFB_00002439)"
339
+ "tags": "Expression_pattern_fragment|Neuron|Adult|lineage_CM3",
340
+ "thumbnail": "[![fru-M-900020 aligned to JRC2018U](http://www.virtualflybrain.org/data/VFB/i/0000/2439/VFB_00101567/thumbnail.png 'fru-M-900020 aligned to JRC2018U')](VFB_00101567,VFB_00002439)"
344
341
  },
345
342
  {
346
343
  "id": "VFB_00002439",
347
344
  "score": "0.6",
348
345
  "name": "[fru-M-900020](VFB_00002439)",
349
- "tags": "Expression_pattern_fragment|Neuron|Adult",
350
- "thumbnail": "[![fru-M-900020 aligned to JRC2018U](http://www.virtualflybrain.org/data/VFB/i/0000/2439/VFB_00101567/thumbnail.png 'fru-M-900020 aligned to JRC2018U')](VFB_00101567,VFB_00002439)"
346
+ "tags": "Expression_pattern_fragment|Neuron|Adult|lineage_CM3",
347
+ "thumbnail": "[![fru-M-900020 aligned to JFRC2](http://www.virtualflybrain.org/data/VFB/i/0000/2439/VFB_00017894/thumbnail.png 'fru-M-900020 aligned to JFRC2')](VFB_00017894,VFB_00002439)"
351
348
  },
352
349
  {
353
- "id": "VFB_00001880",
350
+ "id": "VFB_00000845",
354
351
  "score": "0.59",
355
- "name": "[fru-M-100041](VFB_00001880)",
356
- "tags": "Expression_pattern_fragment|Neuron|Adult",
357
- "thumbnail": "[![fru-M-100041 aligned to JFRC2](http://www.virtualflybrain.org/data/VFB/i/0000/1880/VFB_00017894/thumbnail.png 'fru-M-100041 aligned to JFRC2')](VFB_00017894,VFB_00001880)"
352
+ "name": "[fru-M-100191](VFB_00000845)",
353
+ "tags": "Expression_pattern_fragment|Neuron|Adult|lineage_CM3",
354
+ "thumbnail": "[![fru-M-100191 aligned to JRC2018U](http://www.virtualflybrain.org/data/VFB/i/0000/0845/VFB_00101567/thumbnail.png 'fru-M-100191 aligned to JRC2018U')](VFB_00101567,VFB_00000845)"
358
355
  }
359
356
  ]
360
357
  },
361
358
  "output_format": "table",
362
- "count": 46
359
+ "count": 60
363
360
  }
364
361
  ],
365
362
  "IsIndividual": True,
@@ -368,11 +365,11 @@ vfb.get_term_info('VFB_00000001')
368
365
  {
369
366
  "id": "VFB_00000001",
370
367
  "label": "fru-M-200266",
371
- "thumbnail": "https://virtualflybrain.org/reports/VFB_00000001/thumbnail.png",
372
- "thumbnail_transparent": "https://virtualflybrain.org/reports/VFB_00000001/thumbnailT.png",
368
+ "thumbnail": "https://www.virtualflybrain.org/data/VFB/i/0000/0001/VFB_00017894/thumbnail.png",
369
+ "thumbnail_transparent": "https://www.virtualflybrain.org/data/VFB/i/0000/0001/VFB_00017894/thumbnailT.png",
373
370
  "nrrd": "https://www.virtualflybrain.org/data/VFB/i/0000/0001/VFB_00017894/volume.nrrd",
374
- "wlz": "https://virtualflybrain.org/reports/VFB_00000001/volume.wlz",
375
- "obj": "https://virtualflybrain.org/reports/VFB_00000001/volume.obj",
371
+ "wlz": "https://www.virtualflybrain.org/data/VFB/i/0000/0001/VFB_00017894/volume.wlz",
372
+ "obj": "https://www.virtualflybrain.org/data/VFB/i/0000/0001/VFB_00017894/volume.obj",
376
373
  "swc": "https://www.virtualflybrain.org/data/VFB/i/0000/0001/VFB_00017894/volume.swc"
377
374
  }
378
375
  ],
@@ -380,19 +377,17 @@ vfb.get_term_info('VFB_00000001')
380
377
  {
381
378
  "id": "VFB_00000001",
382
379
  "label": "fru-M-200266",
383
- "thumbnail": "https://virtualflybrain.org/reports/VFB_00000001/thumbnail.png",
384
- "thumbnail_transparent": "https://virtualflybrain.org/reports/VFB_00000001/thumbnailT.png",
380
+ "thumbnail": "https://www.virtualflybrain.org/data/VFB/i/0000/0001/VFB_00101567/thumbnail.png",
381
+ "thumbnail_transparent": "https://www.virtualflybrain.org/data/VFB/i/0000/0001/VFB_00101567/thumbnailT.png",
385
382
  "nrrd": "https://www.virtualflybrain.org/data/VFB/i/0000/0001/VFB_00101567/volume.nrrd",
386
- "wlz": "https://virtualflybrain.org/reports/VFB_00000001/volume.wlz",
387
- "obj": "https://virtualflybrain.org/reports/VFB_00000001/volume.obj",
383
+ "wlz": "https://www.virtualflybrain.org/data/VFB/i/0000/0001/VFB_00101567/volume.wlz",
384
+ "obj": "https://www.virtualflybrain.org/data/VFB/i/0000/0001/VFB_00101567/volume.obj",
388
385
  "swc": "https://www.virtualflybrain.org/data/VFB/i/0000/0001/VFB_00101567/volume.swc"
389
386
  }
390
387
  ]
391
388
  },
392
389
  "IsClass": False,
393
- "Examples": {},
394
390
  "IsTemplate": False,
395
- "Domains": {},
396
391
  "Licenses": {
397
392
  "0": {
398
393
  "iri": "http://virtualflybrain.org/reports/VFBlicense_FlyCircuit_License",
@@ -402,9 +397,7 @@ vfb.get_term_info('VFB_00000001')
402
397
  "source": "FlyCircuit 1.0 - single neurons (Chiang2010)",
403
398
  "source_iri": "http://virtualflybrain.org/reports/Chiang2010"
404
399
  }
405
- },
406
- "Publications": [],
407
- "Synonyms": []
400
+ }
408
401
  }
409
402
  ```
410
403
 
@@ -412,7 +405,6 @@ Template example:
412
405
  ```python
413
406
  vfb.get_term_info('VFB_00101567')
414
407
  ```
415
-
416
408
  ```json
417
409
  {
418
410
  "Name": "JRC2018U",
@@ -441,10 +433,10 @@ vfb.get_term_info('VFB_00101567')
441
433
  "Queries": [],
442
434
  "IsIndividual": True,
443
435
  "Images": {
444
- "VFBc_00101567": [
436
+ "VFB_00101567": [
445
437
  {
446
- "id": "VFBc_00101567",
447
- "label": "JRC2018Unisex_c",
438
+ "id": "VFB_00101567",
439
+ "label": "JRC2018Unisex",
448
440
  "thumbnail": "https://www.virtualflybrain.org/data/VFB/i/0010/1567/VFB_00101567/thumbnail.png",
449
441
  "thumbnail_transparent": "https://www.virtualflybrain.org/data/VFB/i/0010/1567/VFB_00101567/thumbnailT.png",
450
442
  "nrrd": "https://www.virtualflybrain.org/data/VFB/i/0010/1567/VFB_00101567/volume.nrrd",
@@ -0,0 +1,14 @@
1
+ test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ test/readme_parser.py,sha256=puvcq4_oEltjx_faw1kQJ8mmIWiQU-40oLJjtJBQCsQ,4170
3
+ test/term_info_queries_test.py,sha256=EiL6Od5L9W6Xm6MPRMU4V_4TfoVsSSWjO0wh0F2ITH0,34242
4
+ test/test_examples_diff.py,sha256=ep_BzA-7az2OUPxUIsS3ReFV8LwuzGv8yIL0HirOGsc,15699
5
+ vfbquery/__init__.py,sha256=ZirzaFa-rgWYvqvK08rwxachUEz_qjhKrm3C8s3tlZY,76
6
+ vfbquery/solr_fetcher.py,sha256=U8mHaBJrwjncl1eU_gnNj5CGhEb-s9dCpcUTXTifQOY,3984
7
+ vfbquery/term_info_queries.py,sha256=oE-Ogm7jCPPlKtD3W3EtttYZcHnInwDOpOj-phAEOaI,42009
8
+ vfbquery/test_utils.py,sha256=7wUA3xgaGu3eLnjC98msNYt1wL538nOimVJjkC0ZLjU,5791
9
+ vfbquery/vfb_queries.py,sha256=NnkWB3shgnv2ovG-WimcuzXZtQCjRzIqdWPnQvoY4Hs,66014
10
+ vfbquery-0.3.4.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
11
+ vfbquery-0.3.4.dist-info/METADATA,sha256=T4Kxnz0tLOR_hmgIc-ZwfNWbkW_twWUYnMsy9t5jmzc,63097
12
+ vfbquery-0.3.4.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
13
+ vfbquery-0.3.4.dist-info/top_level.txt,sha256=UgaRTTOy4JBdKbkr_gkeknT4eaibm3ztF520G4NTQZs,14
14
+ vfbquery-0.3.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.3.2)
2
+ Generator: bdist_wheel (0.45.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,14 +0,0 @@
1
- test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- test/readme_parser.py,sha256=puvcq4_oEltjx_faw1kQJ8mmIWiQU-40oLJjtJBQCsQ,4170
3
- test/term_info_queries_test.py,sha256=Rd7KeS6dVLjIB74s3poj5jr7h5s_DIViVpuEWnhzBB4,33423
4
- test/test_examples_diff.py,sha256=ep_BzA-7az2OUPxUIsS3ReFV8LwuzGv8yIL0HirOGsc,15699
5
- vfbquery/__init__.py,sha256=KPkQWJsiUtew3IrygX17djJJfCxJtqw3cy3rB-e3cL4,28
6
- vfbquery/solr_fetcher.py,sha256=_e0W87_tLwGeXSmok0FfBnpjIiM2lqTelKNkpdzxL1k,3529
7
- vfbquery/term_info_queries.py,sha256=79Bm2RJzAZyVPQE5HWhsvybeBYrz2AbFgbM0ympIxao,41399
8
- vfbquery/test_utils.py,sha256=HKFsQ2wqZYxR_wS9V6RIM3SguIi9kX5kyYDAXgpfp1A,1623
9
- vfbquery/vfb_queries.py,sha256=VE5-RBzgVMdadcWTjSmF5oQxefQ3fEDNwaQevG69Img,62760
10
- vfbquery-0.3.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
11
- vfbquery-0.3.2.dist-info/METADATA,sha256=xllJx3ZOH91sagWa-_2O6_DI86wAjs0TQt8xsuEoUXA,62863
12
- vfbquery-0.3.2.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
13
- vfbquery-0.3.2.dist-info/top_level.txt,sha256=UgaRTTOy4JBdKbkr_gkeknT4eaibm3ztF520G4NTQZs,14
14
- vfbquery-0.3.2.dist-info/RECORD,,