Skip to content
Open

Ref api #3191

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
c084080
feat(ref api): endpoint and part of the view/
YishaiGlasner Jan 18, 2026
4cbd204
fix(ref api): add signature to get and wrap dict with jsonResponse
YishaiGlasner Mar 19, 2026
5c8a62b
feat(ref api): remove index data
YishaiGlasner Mar 19, 2026
2a076bd
feat(Ref): ref function returns its parts, and adding it to ref api
YishaiGlasner Mar 19, 2026
a887c41
feat(Ref): ref function returns its parts, and adding it to ref api
YishaiGlasner Mar 19, 2026
d0068dd
feat(Ref): change attr name for default node metadata.
YishaiGlasner Mar 19, 2026
da29cca
feat(Ref): add navigation_refs.
YishaiGlasner Mar 22, 2026
11d3a72
refactor(Ref): add ja param to more functionss for saving mongo calls.
YishaiGlasner Mar 22, 2026
c1680da
refactor(ref api): call the vstate when index node is ja, and use it …
YishaiGlasner Mar 22, 2026
ad09089
feat(ref api): add next and prev.
YishaiGlasner Mar 22, 2026
b1e7991
refactor(Ref): change ref_parts to get_lineage_titles_top_down, and d…
YishaiGlasner Mar 22, 2026
47f06c9
refactor(Ref): remove get_lineage_titles_top_down as we have `address…
YishaiGlasner Mar 22, 2026
aa6cc3f
feat(ref api): change re parts to get_lineage_titles_top_down and add…
YishaiGlasner Mar 22, 2026
f481237
feat(ref api): many things.
YishaiGlasner Mar 24, 2026
12175ea
chore(ref api): remove todo.
YishaiGlasner Mar 24, 2026
7ae1801
fix(ref api): move prev and next into navigation_refs.
YishaiGlasner Mar 24, 2026
a56151e
feat(ref api): add parent ref.
YishaiGlasner Mar 24, 2026
0c9e506
fix(ref api): some virtual nodes problems.
YishaiGlasner Mar 24, 2026
9a6245b
fix(Ref): fix next and prev segment for virtual node.
YishaiGlasner Mar 24, 2026
c1174b4
text(ref api)
YishaiGlasner Mar 24, 2026
536653b
docs(ref api): add documentation to openAPI.json.
YishaiGlasner Mar 25, 2026
d0cf2d3
refactor(ref api): `lineage_refs_top_down` rather than `lineage_title…
YishaiGlasner Mar 25, 2026
d90c186
feat(Ref): add `vstate` param to functions that already have `state_j…
YishaiGlasner Mar 25, 2026
4ccb547
feat(ref api): add `vstate` to improve performance.
YishaiGlasner Mar 25, 2026
d4d9e80
refactor(Ref): use vstate rather than state_ja.
YishaiGlasner Mar 25, 2026
043c5ab
refactor(Ref): use vstate in `is_empty` and `get_state_ja` rather tha…
YishaiGlasner Mar 25, 2026
7f4bdfc
feat(database): add QueryCounter class and use it in tests env to mon…
YishaiGlasner Mar 25, 2026
566274a
test(ref api): add get_ref function with assertion about number of ca…
YishaiGlasner Mar 25, 2026
93ca8aa
chore(ref api): remove redundant import.
YishaiGlasner Mar 25, 2026
9aed4a3
fix(ref api): check first available section exists before normalizing.
YishaiGlasner Mar 25, 2026
6d51a27
docs(ref api): first available section can be null
YishaiGlasner Mar 25, 2026
006b950
fix(Ref.prev_segment_ref): change 0 to -1 for it should be the last s…
YishaiGlasner Mar 25, 2026
1e82043
Merge remote-tracking branch 'origin/ref-api' into ref-api
YishaiGlasner Mar 25, 2026
d0dc947
fix(Ref.is_empty): fix call on segment ref with vstate.
YishaiGlasner Mar 25, 2026
b2094ea
feat(QueryCounter): do not count any command to avoid counting connec…
YishaiGlasner Mar 25, 2026
2e5240d
Merge branch 'master' into ref-api
YishaiGlasner Mar 25, 2026
28adb83
fix(tests): remove redundant non-existing import
YishaiGlasner Mar 26, 2026
004e891
fix(ref api): fix lineage refs to be top down.
YishaiGlasner Mar 26, 2026
e80cf89
docs(ref api): remove false comment.
YishaiGlasner Mar 26, 2026
d87135b
feat(ref api): add address types and scection names to default child.
YishaiGlasner Mar 26, 2026
0798337
docs(ref api): add nullable to prev_segment_ref.
YishaiGlasner Mar 26, 2026
4836c97
fix(ref api): handle DictionaryEntryNotFoundError as not being a ref.
YishaiGlasner Mar 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 251 additions & 0 deletions api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from reader.tests import SefariaTestCase
import json
from api.api_warnings import APIWarningCode
from sefaria.system.database import QueryCounter


c = Client()
Expand Down Expand Up @@ -224,3 +225,253 @@ def test_error_return_format(self):
self.assertEqual(400, response.status_code)
data = json.loads(response.content)
self.assertEqual(data['error'], "return_format should be one of those formats: ['default', 'wrap_all_entities', 'text_only', 'strip_only_footnotes'].")


class APIRefTests(SefariaTestCase):

def get_ref(self, tref, max_mongo_calls=1):
QueryCounter.reset(tracked_commands={'find', 'aggregate', 'count', 'distinct'})
response = c.get(f'/api/ref/{tref}')
data = json.loads(response.content)
if max_mongo_calls is not None:
if QueryCounter.count > max_mongo_calls:
for i, q in enumerate(QueryCounter.queries):
print(f"\n--- Query {i+1}: {q['command']} on {q['collection']} ---")
print(q['traceback'])
self.assertLessEqual(QueryCounter.count, max_mongo_calls,
f"Expected at most {max_mongo_calls} mongo call(s) for '{tref}', got {QueryCounter.count}")
return data

def test_not_ref(self):
data = self.get_ref('Not Ref', max_mongo_calls=0)
self.assertFalse(data['is_ref'])

def test_book_level_jagged_array(self):
"""Penei Moshe on Jerusalem Talmud Shabbat - book-level JaggedArrayNode depth 4"""
data = self.get_ref('Penei Moshe on Jerusalem Talmud Shabbat')
self.assertTrue(data['is_ref'])
self.assertEqual(data['node_type'], 'JaggedArrayNode')
self.assertEqual(data['index_title'], 'Penei Moshe on Jerusalem Talmud Shabbat')
self.assertEqual(data['depth'], 4)
self.assertEqual(data['address_types'], ['Perek', 'Halakhah', 'Integer', 'Integer'])
self.assertEqual(data['section_names'], ['Chapter', 'Halakhah', 'Segment', 'Comment'])
self.assertEqual(data['start_indexes'], [])
self.assertEqual(data['end_indexes'], [])
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'], [])
self.assertEqual(data['navigation_refs']['first_available_section_ref'], 'Penei Moshe on Jerusalem Talmud Shabbat 1:1:1')
self.assertEqual(data['navigation_refs']['first_subref'], 'Penei Moshe on Jerusalem Talmud Shabbat 1')
self.assertEqual(data['navigation_refs']['last_subref'], 'Penei Moshe on Jerusalem Talmud Shabbat 24')
self.assertNotIn('prev_section_ref', data['navigation_refs'])
self.assertNotIn('next_section_ref', data['navigation_refs'])
self.assertNotIn('prev_segment_ref', data['navigation_refs'])
self.assertNotIn('next_segment_ref', data['navigation_refs'])

def test_one_level_below_book(self):
"""Penei Moshe on Jerusalem Talmud Shabbat 2 - one level below book"""
data = self.get_ref('Penei Moshe on Jerusalem Talmud Shabbat 2')
self.assertTrue(data['is_ref'])
self.assertEqual(data['start_indexes'], [2])
self.assertEqual(data['start_labels'], ['2'])
self.assertEqual(data['end_indexes'], [2])
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'][-1], 'Penei Moshe on Jerusalem Talmud Shabbat')
self.assertEqual(data['navigation_refs']['first_available_section_ref'], 'Penei Moshe on Jerusalem Talmud Shabbat 2:1:1')
self.assertEqual(data['navigation_refs']['first_subref'], 'Penei Moshe on Jerusalem Talmud Shabbat 2:1')
self.assertEqual(data['navigation_refs']['last_subref'], 'Penei Moshe on Jerusalem Talmud Shabbat 2:7')
self.assertNotIn('prev_section_ref', data['navigation_refs'])
self.assertNotIn('next_section_ref', data['navigation_refs'])
self.assertNotIn('prev_segment_ref', data['navigation_refs'])
self.assertNotIn('next_segment_ref', data['navigation_refs'])

def test_two_levels_below_book(self):
"""Penei Moshe on Jerusalem Talmud Shabbat 3:2 - two levels below book"""
data = self.get_ref('Penei Moshe on Jerusalem Talmud Shabbat 3:2')
self.assertTrue(data['is_ref'])
self.assertEqual(data['start_indexes'], [3, 2])
self.assertEqual(data['start_labels'], ['3', '2'])
self.assertEqual(data['end_indexes'], [3, 2])
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'][-1], 'Penei Moshe on Jerusalem Talmud Shabbat 3')
self.assertEqual(data['navigation_refs']['first_subref'], 'Penei Moshe on Jerusalem Talmud Shabbat 3:2:1')
self.assertEqual(data['navigation_refs']['last_subref'], 'Penei Moshe on Jerusalem Talmud Shabbat 3:2:3')
self.assertNotIn('prev_section_ref', data['navigation_refs'])
self.assertNotIn('next_section_ref', data['navigation_refs'])
self.assertNotIn('prev_segment_ref', data['navigation_refs'])
self.assertNotIn('next_segment_ref', data['navigation_refs'])

def test_section_level(self):
"""Penei Moshe on Jerusalem Talmud Shabbat 2:3:2 - section-level with prev/next"""
data = self.get_ref('Penei Moshe on Jerusalem Talmud Shabbat 2:3:2')
self.assertTrue(data['is_ref'])
self.assertEqual(data['start_indexes'], [2, 3, 2])
self.assertEqual(data['end_indexes'], [2, 3, 2])
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'][-1], 'Penei Moshe on Jerusalem Talmud Shabbat 2:3')
self.assertEqual(data['navigation_refs']['prev_section_ref'], 'Penei Moshe on Jerusalem Talmud Shabbat 2:3:1')
self.assertEqual(data['navigation_refs']['next_section_ref'], 'Penei Moshe on Jerusalem Talmud Shabbat 2:3:3')
self.assertEqual(data['navigation_refs']['first_subref'], 'Penei Moshe on Jerusalem Talmud Shabbat 2:3:2:1')
self.assertEqual(data['navigation_refs']['last_subref'], 'Penei Moshe on Jerusalem Talmud Shabbat 2:3:2:7')
self.assertNotIn('prev_segment_ref', data['navigation_refs'])
self.assertNotIn('next_segment_ref', data['navigation_refs'])

def test_section_level_with_cross_node_navigation(self):
"""Penei Moshe on Jerusalem Talmud Shabbat 2:3:1 - section-level crossing into prev chapter"""
data = self.get_ref('Penei Moshe on Jerusalem Talmud Shabbat 2:3:1')
self.assertTrue(data['is_ref'])
self.assertEqual(data['navigation_refs']['prev_section_ref'], 'Penei Moshe on Jerusalem Talmud Shabbat 2:2:6')
self.assertEqual(data['navigation_refs']['next_section_ref'], 'Penei Moshe on Jerusalem Talmud Shabbat 2:3:2')
self.assertNotIn('prev_segment_ref', data['navigation_refs'])
self.assertNotIn('next_segment_ref', data['navigation_refs'])

def test_segment_level_first(self):
"""Penei Moshe on Jerusalem Talmud Shabbat 3:2:1:1 - first segment in section"""
data = self.get_ref('Penei Moshe on Jerusalem Talmud Shabbat 3:2:1:1')
self.assertTrue(data['is_ref'])
self.assertEqual(data['start_indexes'], [3, 2, 1, 1])
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'][-1], 'Penei Moshe on Jerusalem Talmud Shabbat 3:2:1')
self.assertEqual(data['navigation_refs']['prev_segment_ref'], 'Penei Moshe on Jerusalem Talmud Shabbat 3:1:14:2')
self.assertEqual(data['navigation_refs']['next_segment_ref'], 'Penei Moshe on Jerusalem Talmud Shabbat 3:2:1:2')
self.assertNotIn('first_subref', data['navigation_refs'])
self.assertNotIn('prev_section_ref', data['navigation_refs'])
self.assertNotIn('next_section_ref', data['navigation_refs'])

def test_segment_level_last_in_section(self):
"""Penei Moshe on Jerusalem Talmud Shabbat 3:2:1:2 - last segment in section"""
data = self.get_ref('Penei Moshe on Jerusalem Talmud Shabbat 3:2:1:2')
self.assertTrue(data['is_ref'])
self.assertEqual(data['navigation_refs']['prev_segment_ref'], 'Penei Moshe on Jerusalem Talmud Shabbat 3:2:1:1')
self.assertEqual(data['navigation_refs']['next_segment_ref'], 'Penei Moshe on Jerusalem Talmud Shabbat 3:2:2:1')
self.assertNotIn('prev_section_ref', data['navigation_refs'])
self.assertNotIn('next_section_ref', data['navigation_refs'])

def test_range_halakhah_level(self):
"""Penei Moshe on Jerusalem Talmud Shabbat 3:2-4:1 - range at halakhah level"""
data = self.get_ref('Penei Moshe on Jerusalem Talmud Shabbat 3:2-4:1')
self.assertTrue(data['is_ref'])
self.assertEqual(data['start_indexes'], [3, 2])
self.assertEqual(data['start_labels'], ['3', '2'])
self.assertEqual(data['end_indexes'], [4, 1])
self.assertEqual(data['end_labels'], ['4', '1'])
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'][-1], 'Penei Moshe on Jerusalem Talmud Shabbat 3-4')
self.assertNotIn('first_subref', data['navigation_refs'])
self.assertNotIn('prev_section_ref', data['navigation_refs'])
self.assertNotIn('next_section_ref', data['navigation_refs'])
self.assertNotIn('prev_segment_ref', data['navigation_refs'])
self.assertNotIn('next_segment_ref', data['navigation_refs'])

def test_range_section_level(self):
"""Penei Moshe on Jerusalem Talmud Shabbat 3:2:1-4:1:1 - range at section level"""
data = self.get_ref('Penei Moshe on Jerusalem Talmud Shabbat 3:2:1-4:1:1')
self.assertTrue(data['is_ref'])
self.assertEqual(data['start_indexes'], [3, 2, 1])
self.assertEqual(data['end_indexes'], [4, 1, 1])
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'][-1], 'Penei Moshe on Jerusalem Talmud Shabbat 3:2-4:1')
self.assertEqual(data['navigation_refs']['prev_section_ref'], 'Penei Moshe on Jerusalem Talmud Shabbat 3:1:14')
self.assertEqual(data['navigation_refs']['next_section_ref'], 'Penei Moshe on Jerusalem Talmud Shabbat 4:1:2')
self.assertNotIn('prev_segment_ref', data['navigation_refs'])
self.assertNotIn('next_segment_ref', data['navigation_refs'])

def test_talmud_section(self):
"""Berakhot 22a - Talmud section-level ref"""
data = self.get_ref('Berakhot 22a')
self.assertTrue(data['is_ref'])
self.assertEqual(data['index_title'], 'Berakhot')
self.assertEqual(data['node_type'], 'JaggedArrayNode')
self.assertEqual(data['depth'], 2)
self.assertEqual(data['address_types'], ['Talmud', 'Integer'])
self.assertEqual(data['section_names'], ['Daf', 'Line'])
self.assertEqual(data['start_indexes'], [43])
self.assertEqual(data['start_labels'], ['22a'])
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'][-1], 'Berakhot')
self.assertEqual(data['navigation_refs']['prev_section_ref'], 'Berakhot 21b')
self.assertEqual(data['navigation_refs']['next_section_ref'], 'Berakhot 22b')
self.assertEqual(data['navigation_refs']['first_subref'], 'Berakhot 22a:1')
self.assertEqual(data['navigation_refs']['last_subref'], 'Berakhot 22a:25')

def test_schema_node(self):
"""Siddur Ashkenaz, Weekday, Shacharit - SchemaNode"""
data = self.get_ref('Siddur Ashkenaz, Weekday, Shacharit')
self.assertTrue(data['is_ref'])
self.assertEqual(data['node_type'], 'SchemaNode')
self.assertEqual(data['index_title'], 'Siddur Ashkenaz')
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'][-1], 'Siddur Ashkenaz, Weekday')
self.assertIn('Preparatory Prayers', data['children'])
self.assertIn('Amidah', data['children'])

def test_deep_complex_segment(self):
"""Siddur Ashkenaz, Weekday, Shacharit, Preparatory Prayers, Modeh Ani 2 - deep segment"""
data = self.get_ref('Siddur Ashkenaz, Weekday, Shacharit, Preparatory Prayers, Modeh Ani 2', max_mongo_calls=1)
self.assertTrue(data['is_ref'])
self.assertEqual(data['node_type'], 'JaggedArrayNode')
self.assertEqual(data['depth'], 1)
self.assertEqual(data['start_indexes'], [2])
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'][-1], 'Siddur Ashkenaz, Weekday, Shacharit, Preparatory Prayers, Modeh Ani')
self.assertEqual(data['navigation_refs']['prev_segment_ref'], 'Siddur Ashkenaz, Weekday, Shacharit, Preparatory Prayers, Modeh Ani 1')
self.assertEqual(data['navigation_refs']['next_segment_ref'], 'Siddur Ashkenaz, Weekday, Shacharit, Preparatory Prayers, Netilat Yadayim 1')

def test_schema_node_with_default_child(self):
"""Ramban on Genesis - SchemaNode with default child JaggedArrayNode"""
data = self.get_ref('Ramban on Genesis')
self.assertTrue(data['is_ref'])
self.assertEqual(data['node_type'], 'SchemaNode')
self.assertIn('Introduction', data['children'])
self.assertIn('default_child_node', data)
self.assertEqual(data['default_child_node']['node_type'], 'JaggedArrayNode')
self.assertEqual(data['default_child_node']['depth'], 3)
self.assertEqual(data['default_child_node']['node_index'], 2)

def test_jagged_array_under_default_child(self):
"""Ramban on Genesis 1 - JaggedArrayNode under default child"""
data = self.get_ref('Ramban on Genesis 1')
self.assertTrue(data['is_ref'])
self.assertEqual(data['node_type'], 'JaggedArrayNode')
self.assertEqual(data['depth'], 3)
self.assertEqual(data['start_indexes'], [1])
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'][-1], 'Ramban on Genesis')

def test_dictionary_node(self):
"""BDB - SchemaNode with default DictionaryNode child"""
data = self.get_ref('BDB', max_mongo_calls=None)
self.assertTrue(data['is_ref'])
self.assertEqual(data['node_type'], 'SchemaNode')
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'], [])
self.assertIn('default_child_node', data)
self.assertEqual(data['default_child_node']['node_type'], 'DictionaryNode')

def test_dictionary_entry_node(self):
"""BDB, א - DictionaryEntryNode"""
data = self.get_ref('BDB, א', max_mongo_calls=None)
self.assertTrue(data['is_ref'])
self.assertEqual(data['node_type'], 'DictionaryEntryNode')
self.assertEqual(data['index_title'], 'BDB')
self.assertEqual(data['lexicon_name'], 'BDB Dictionary')
self.assertEqual(data['headword'], 'א')
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'][-1], 'BDB')
self.assertIsNone(data['navigation_refs']['prev_section_ref'])
self.assertIsNotNone(data['navigation_refs']['next_section_ref'])

def test_dictionary_entry_segment(self):
"""BDB, א 1 - DictionaryEntryNode segment-level"""
data = self.get_ref('BDB, אָב 1', max_mongo_calls=None)
self.assertTrue(data['is_ref'])
self.assertEqual(data['node_type'], 'DictionaryEntryNode')
self.assertEqual(data['start_indexes'], [1])
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'][-1], 'BDB, אָב')
self.assertIsNotNone(data['navigation_refs']['prev_segment_ref'])
self.assertIsNotNone(data['navigation_refs']['next_segment_ref'])
self.assertNotIn('prev_section_ref', data['navigation_refs'])
self.assertNotIn('next_section_ref', data['navigation_refs'])

def test_sheet_node(self):
"""Sheet 1 - SheetNode"""
data = self.get_ref('Sheet 1', max_mongo_calls=2)
self.assertTrue(data['is_ref'])
self.assertEqual(data['node_type'], 'SheetNode')
self.assertEqual(data['sheet_id'], 1)
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'][-1], 'Sheet')
self.assertEqual(data['navigation_refs']['first_subref'], 'Sheet 1:1')

def test_sheet_segment(self):
"""Sheet 1:1 - SheetNode segment-level"""
data = self.get_ref('Sheet 1:1', max_mongo_calls=1)
self.assertTrue(data['is_ref'])
self.assertEqual(data['node_type'], 'SheetNode')
self.assertEqual(data['sheet_id'], 1)
self.assertEqual(data['navigation_refs']['lineage_refs_top_down'][-1], 'Sheet 1')
Loading
Loading