Spaces:
Sleeping
Sleeping
Create graphdb.py
Browse files- genesis/graphdb.py +81 -0
genesis/graphdb.py
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# genesis/graphdb.py
|
2 |
+
from __future__ import annotations
|
3 |
+
|
4 |
+
import os
|
5 |
+
from typing import Dict, List
|
6 |
+
|
7 |
+
from neo4j import GraphDatabase
|
8 |
+
|
9 |
+
NEO4J_URI = os.getenv("NEO4J_URI")
|
10 |
+
NEO4J_USER = os.getenv("NEO4J_USER")
|
11 |
+
NEO4J_PASSWORD = os.getenv("NEO4J_PASSWORD")
|
12 |
+
|
13 |
+
|
14 |
+
def _require_config():
|
15 |
+
if not (NEO4J_URI and NEO4J_USER and NEO4J_PASSWORD):
|
16 |
+
raise RuntimeError(
|
17 |
+
"Neo4j not configured. Set NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD in Space Secrets."
|
18 |
+
)
|
19 |
+
|
20 |
+
|
21 |
+
async def write_topic_and_papers(topic: str, citations: List[Dict[str, str]]) -> Dict[str, int]:
|
22 |
+
"""
|
23 |
+
Create/merge a Topic node and Paper nodes, and connect them with MENTIONS edges.
|
24 |
+
Schema:
|
25 |
+
(t:Topic {id})-[:MENTIONS]->(p:Paper {url}) with p.title set/updated.
|
26 |
+
Returns: {"nodes": <int>, "rels": <int>}
|
27 |
+
"""
|
28 |
+
_require_config()
|
29 |
+
driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USER, NEO4J_PASSWORD))
|
30 |
+
|
31 |
+
nodes = 0
|
32 |
+
rels = 0
|
33 |
+
try:
|
34 |
+
with driver.session() as sess:
|
35 |
+
# Topic
|
36 |
+
sess.run(
|
37 |
+
"MERGE (t:Topic {id:$id}) SET t.title=$title",
|
38 |
+
id=(topic or "Topic")[:200],
|
39 |
+
title=(topic or "Topic")[:500],
|
40 |
+
)
|
41 |
+
nodes += 1
|
42 |
+
|
43 |
+
# Papers + edges
|
44 |
+
for c in citations or []:
|
45 |
+
title = (c.get("title") or "citation")[:500]
|
46 |
+
url = (c.get("url") or "")[:1000]
|
47 |
+
if not url and not title:
|
48 |
+
continue
|
49 |
+
|
50 |
+
sess.run(
|
51 |
+
"""
|
52 |
+
MERGE (p:Paper {url:$url})
|
53 |
+
ON CREATE SET p.title=$title
|
54 |
+
ON MATCH SET p.title = coalesce(p.title, $title)
|
55 |
+
WITH p
|
56 |
+
MATCH (t:Topic {id:$topic})
|
57 |
+
MERGE (t)-[:MENTIONS]->(p)
|
58 |
+
""",
|
59 |
+
url=url,
|
60 |
+
title=title,
|
61 |
+
topic=(topic or "Topic")[:200],
|
62 |
+
)
|
63 |
+
nodes += 1
|
64 |
+
rels += 1
|
65 |
+
|
66 |
+
return {"nodes": nodes, "rels": rels}
|
67 |
+
finally:
|
68 |
+
driver.close()
|
69 |
+
|
70 |
+
|
71 |
+
# Optional helper for debugging connectivity in a notebook/Space shell
|
72 |
+
def verify_connection() -> str:
|
73 |
+
_require_config()
|
74 |
+
driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USER, NEO4J_PASSWORD))
|
75 |
+
try:
|
76 |
+
with driver.session() as sess:
|
77 |
+
res = sess.run("RETURN 1 AS ok").single()
|
78 |
+
assert res and res["ok"] == 1
|
79 |
+
return "Neo4j connection OK."
|
80 |
+
finally:
|
81 |
+
driver.close()
|