Spaces:
Running
Running
Download scripts/append_to_sidecar.py from siddhm11/ResearchIT: direct link, hf CLI and curl.
- Browser
- Download file 6.39 kB
-
https://huggingface.co/spaces/siddhm11/ResearchIT/resolve/main/scripts/append_to_sidecar.py
- Command line
-
hf download hf://spaces/siddhm11/ResearchIT/scripts/append_to_sidecar.py
-
curl -L -o append_to_sidecar.py https://huggingface.co/spaces/siddhm11/ResearchIT/resolve/main/scripts/append_to_sidecar.py
6.39 kB
| #!/usr/bin/env python3 | |
| """ | |
| Append papers that Turso has and the metadata sidecar does not. | |
| Why this exists | |
| --------------- | |
| The sidecar ships inside the Docker image, and the starter feed and keyword | |
| search only see what it holds. build_metadata_sidecar.py rebuilds it from | |
| scratch, which re-reads 1.8M rows; after an ingest only the new rows are | |
| needed. New Turso rows have rowids above the last ingest's, so they are | |
| fetched by `rowid > --after-rowid`. | |
| Three things must stay consistent, and this does all of them in one | |
| transaction: | |
| * papers one row per paper (plain INSERT; an existing arxiv_id is | |
| skipped, never replaced: INSERT OR REPLACE would give it | |
| a new rowid and desynchronise the search index) | |
| * paper_categories one row per (paper, arXiv code), as the builder writes | |
| * papers_fts external-content FTS5 over papers(rowid); new rows must | |
| be indexed explicitly, under the rowid they were given | |
| Run it on a COPY of the sidecar, then check with --verify. | |
| Usage | |
| ----- | |
| export TURSO_URL=... TURSO_DB_TOKEN=... | |
| python scripts/append_to_sidecar.py --sidecar publish/metadata.sqlite --after-rowid 1799348 | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import os | |
| import sqlite3 | |
| import sys | |
| import httpx | |
| COLUMNS = ("arxiv_id", "title", "authors", "abstract_preview", "categories", | |
| "primary_topic", "update_date", "citation_count", "influential_citations") | |
| def turso_rows_after(url: str, token: str, after: int, page: int = 2000): | |
| """Yield Turso paper rows with rowid > after, in rowid order.""" | |
| url = url.replace("libsql://", "https://").rstrip("/") | |
| last = after | |
| with httpx.Client(timeout=180) as client: | |
| while True: | |
| sql = (f"SELECT rowid, {', '.join(COLUMNS)} FROM papers " | |
| f"WHERE rowid > {int(last)} ORDER BY rowid LIMIT {int(page)}") | |
| r = client.post(f"{url}/v2/pipeline", headers={"Authorization": f"Bearer {token}"}, | |
| json={"requests": [{"type": "execute", "stmt": {"sql": sql}}, | |
| {"type": "close"}]}) | |
| r.raise_for_status() | |
| res = r.json()["results"][0] | |
| if res.get("type") != "ok": | |
| raise RuntimeError(str(res)[:200]) | |
| rows = [[c.get("value") for c in row] for row in res["response"]["result"]["rows"]] | |
| if not rows: | |
| return | |
| for row in rows: | |
| yield dict(zip(COLUMNS, row[1:])) | |
| last = int(rows[-1][0]) | |
| def append_rows(conn: sqlite3.Connection, rows) -> dict: | |
| """Insert papers, their category rows and their FTS entries. Returns counts.""" | |
| stats = {"inserted": 0, "skipped_existing": 0, "category_rows": 0} | |
| with conn: | |
| for r in rows: | |
| cit = int(r.get("citation_count") or 0) | |
| inf = int(r.get("influential_citations") or 0) | |
| cur = conn.execute( | |
| "INSERT OR IGNORE INTO papers (arxiv_id, title, authors, abstract_preview, " | |
| "categories, primary_topic, update_date, citation_count, influential_citations) " | |
| "VALUES (?,?,?,?,?,?,?,?,?)", | |
| (r["arxiv_id"], r["title"], r["authors"], r["abstract_preview"], r["categories"], | |
| r["primary_topic"], r["update_date"], cit, inf)) | |
| if cur.rowcount != 1: | |
| stats["skipped_existing"] += 1 | |
| continue | |
| rowid = cur.lastrowid | |
| stats["inserted"] += 1 | |
| codes = str(r.get("categories") or "").split() | |
| conn.executemany("INSERT INTO paper_categories VALUES (?,?,?,?)", | |
| [(code, r["arxiv_id"], cit, r["update_date"]) for code in codes]) | |
| stats["category_rows"] += len(codes) | |
| conn.execute("INSERT INTO papers_fts(rowid, title, abstract_preview) VALUES (?,?,?)", | |
| (rowid, r["title"], r["abstract_preview"])) | |
| return stats | |
| def verify(conn: sqlite3.Connection) -> dict: | |
| """Consistency checks the published file must pass.""" | |
| out = { | |
| "quick_check": conn.execute("PRAGMA quick_check").fetchone()[0], | |
| "papers": conn.execute("SELECT COUNT(*) FROM papers").fetchone()[0], | |
| "fts_rows": conn.execute("SELECT COUNT(*) FROM papers_fts").fetchone()[0], | |
| "papers_without_categories": conn.execute( | |
| "SELECT COUNT(*) FROM papers p WHERE categories IS NOT NULL AND categories != '' " | |
| "AND NOT EXISTS (SELECT 1 FROM paper_categories c WHERE c.arxiv_id = p.arxiv_id)" | |
| ).fetchone()[0], | |
| "newest_update_date": conn.execute("SELECT MAX(update_date) FROM papers").fetchone()[0], | |
| } | |
| try: | |
| conn.execute("INSERT INTO papers_fts(papers_fts) VALUES('integrity-check')") | |
| out["fts_integrity"] = "ok" | |
| except sqlite3.DatabaseError as e: | |
| out["fts_integrity"] = f"FAILED: {e}" | |
| return out | |
| def main() -> int: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--sidecar", required=True, help="sidecar COPY to modify") | |
| ap.add_argument("--after-rowid", type=int, help="append Turso rows with rowid above this") | |
| ap.add_argument("--verify", action="store_true", help="only run the consistency checks") | |
| args = ap.parse_args() | |
| conn = sqlite3.connect(args.sidecar) | |
| if not args.verify: | |
| if args.after_rowid is None: | |
| print("--after-rowid is required to append", file=sys.stderr) | |
| return 2 | |
| url, token = os.environ.get("TURSO_URL", ""), os.environ.get("TURSO_DB_TOKEN", "") | |
| if not url or not token: | |
| print("TURSO_URL and TURSO_DB_TOKEN must be set", file=sys.stderr) | |
| return 2 | |
| stats = append_rows(conn, turso_rows_after(url, token, args.after_rowid)) | |
| print(f"[sidecar] appended {stats['inserted']:,} papers, {stats['category_rows']:,} " | |
| f"category rows; skipped {stats['skipped_existing']:,} already present") | |
| conn.execute("PRAGMA wal_checkpoint(TRUNCATE)") | |
| report = verify(conn) | |
| print("[sidecar] verify:", report) | |
| ok = (report["quick_check"] == "ok" and report["fts_integrity"] == "ok" | |
| and report["fts_rows"] == report["papers"] and report["papers_without_categories"] == 0) | |
| return 0 if ok else 1 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |