from __future__ import annotations """Serve short glosses from the local Kaikki dump baked into atlas.sqlite.""" import json import sqlite3 MAX_SENSES = 4 MAX_GLOSSES = 4 def _trim_senses(raw: object) -> list[dict]: if isinstance(raw, str): try: raw = json.loads(raw) except json.JSONDecodeError: return [] if not isinstance(raw, list): return [] senses: list[dict] = [] for block in raw: if not isinstance(block, dict): continue pos = str(block.get("pos") or "Sense").strip() or "Sense" glosses: list[str] = [] for g in block.get("glosses") or []: text = " ".join(str(g).split()).strip() if text: glosses.append(text) if len(glosses) >= MAX_GLOSSES: break if glosses: senses.append({"pos": pos, "glosses": glosses}) if len(senses) >= MAX_SENSES: break return senses def _candidate_langs(lang: str, iso_639_3: str | None, lang_meta: dict[str, dict] | None) -> list[str]: out: list[str] = [] key = (lang or "").strip() if key: out.append(key) folded = key.casefold() if folded != key: out.append(folded) iso = (iso_639_3 or "").strip().casefold() if lang_meta and iso: for lk, meta in lang_meta.items(): if (meta.get("iso_639_3") or "").casefold() == iso: out.append(lk) if iso: out.append(iso) seen: set[str] = set() uniq: list[str] = [] for c in out: if c and c not in seen: seen.add(c) uniq.append(c) return uniq def lookup_definitions( conn: sqlite3.Connection, term: str, lang: str = "", ety: str | None = None, iso_639_3: str | None = None, lang_meta: dict[str, dict] | None = None, ) -> dict: term = (term or "").strip() if not term: return {"term": term, "senses": [], "error": "empty_term", "source": "kaikki"} candidates = _candidate_langs(lang, iso_639_3, lang_meta) row = None matched_lang = None matched_ety = None for cand in candidates: if ety is not None: row = conn.execute( "SELECT lang, ety, senses_json FROM definitions WHERE term = ? AND lang = ? AND ety = ?", (term, cand, ety), ).fetchone() else: row = conn.execute( """ SELECT lang, ety, senses_json FROM definitions WHERE term = ? AND lang = ? ORDER BY (ety = '') DESC, ety LIMIT 1 """, (term, cand), ).fetchone() if row is not None: matched_lang = row["lang"] if isinstance(row, sqlite3.Row) else row[0] matched_ety = row["ety"] if isinstance(row, sqlite3.Row) else row[1] break fallback = False if row is None: rows = conn.execute( "SELECT lang, ety, senses_json FROM definitions WHERE term = ? ORDER BY lang, ety LIMIT 8", (term,), ).fetchall() if rows: prefer = {c.casefold() for c in candidates} chosen = None for r in rows: rlang = r["lang"] if isinstance(r, sqlite3.Row) else r[0] if rlang.casefold() in prefer or rlang.casefold() == "english": chosen = r break row = chosen or rows[0] matched_lang = row["lang"] if isinstance(row, sqlite3.Row) else row[0] matched_ety = row["ety"] if isinstance(row, sqlite3.Row) else row[1] fallback = bool(candidates) and matched_lang not in candidates if row is None: return { "term": term, "lang": lang, "ety": ety or "", "matched_code": None, "senses": [], "fallback": False, "source": "kaikki", "error": "not_found", } senses_json = row["senses_json"] if isinstance(row, sqlite3.Row) else row[2] senses = _trim_senses(senses_json) return { "term": term, "lang": lang, "ety": matched_ety or "", "matched_code": matched_lang, "senses": senses, "fallback": fallback, "source": "kaikki", "error": None if senses else "no_senses", } def fetch_definitions( term: str, lang: str = "", ety: str | None = None, iso_639_3: str | None = None, *, atlas=None, ) -> dict: """Lookup glosses from the baked index. ``atlas`` must be a loaded Atlas.""" if atlas is None or getattr(atlas, "conn", None) is None: return { "term": term, "lang": lang, "ety": ety or "", "matched_code": None, "senses": [], "fallback": False, "source": "kaikki", "error": "index_unavailable", } return lookup_definitions( atlas.conn, term=term, lang=lang, ety=ety, iso_639_3=iso_639_3, lang_meta=getattr(atlas, "lang_meta", None), )