GEXF export over the memory KGLite research tree.
A thin, read-only projection of the memory graph (entry nodes + has_child /
related edges) into a GEXF 1.2 document. It is a PURE function of a
KGLite graph handle — it queries via graph.cypher and mutates nothing — so
it is fully unit-testable without the MCP server (a test can build a
kglite.KnowledgeGraph, add entry nodes/edges, and export directly).
This module is standalone: memory must NEVER import zettelkasten (the dependency
is strictly one-way). It shares only the neutral, in-package
:mod:memory.gexf builder.
graph_to_gexf
graph_to_gexf(graph: 'kglite.KnowledgeGraph') -> str
Serialize the entry subgraph of a KGLite graph into GEXF 1.2.
Every LIVE entry node becomes a <node> (label = title, attributes
type/status); every has_child and related edge whose BOTH
endpoints are emitted entries becomes an <edge> (label = the
connection type). Discarded/tombstoned entries are filtered out (not emitted
as live nodes), and edges touching a non-entry node (e.g. the project root,
or a document) or a filtered entry are omitted so every emitted edge
references an emitted node.
Parameters:
| Name |
Type |
Description |
Default |
graph
|
'kglite.KnowledgeGraph'
|
a KGLite graph handle exposing .cypher(query) returning rows
with attribute access / dict-coercion.
|
required
|
Source code in memory/export.py
| def graph_to_gexf(graph: "kglite.KnowledgeGraph") -> str:
"""Serialize the entry subgraph of a KGLite ``graph`` into GEXF 1.2.
Every LIVE ``entry`` node becomes a ``<node>`` (``label`` = title, attributes
``type``/``status``); every ``has_child`` and ``related`` edge whose BOTH
endpoints are emitted entries becomes an ``<edge>`` (``label`` = the
connection type). Discarded/tombstoned entries are filtered out (not emitted
as live nodes), and edges touching a non-entry node (e.g. the project root,
or a document) or a filtered entry are omitted so every emitted edge
references an emitted node.
Args:
graph: a KGLite graph handle exposing ``.cypher(query)`` returning rows
with attribute access / ``dict``-coercion.
"""
node_rows = [
dict(r)
for r in graph.cypher(
"MATCH (e:entry) RETURN e.id AS id, e.title AS title, "
"e.type AS type, e.status AS status"
)
]
# Drop discarded/tombstoned entries: they are not part of the live graph.
node_rows = [
r
for r in node_rows
if r.get("id") and str(r.get("status") or "").lower() not in _DISCARDED_STATUSES
]
entry_ids = {str(r["id"]) for r in node_rows}
nodes = [
(
str(r["id"]),
str(r.get("title") or r["id"]),
{"type": r.get("type") or "", "status": r.get("status") or ""},
)
for r in node_rows
]
edges: list[tuple[str, str, str, object]] = []
for conn in ("has_child", "related"):
rows = [
dict(r)
for r in graph.cypher(
f"MATCH (a:entry)-[:{conn}]->(b:entry) "
"RETURN a.id AS source, b.id AS target"
)
]
for r in rows:
src, tgt = str(r.get("source") or ""), str(r.get("target") or "")
# Only emit edges between two entries we actually emitted as nodes.
if src in entry_ids and tgt in entry_ids:
edges.append((src, tgt, conn, None))
return build_gexf(nodes, edges, node_attributes=_NODE_ATTRIBUTES)
|