From 73489502c59053a0073f72d709b102eaf7e3661f Mon Sep 17 00:00:00 2001 From: hofmockel <82425084+hofmockel@users.noreply.github.com> Date: Sun, 12 Jul 2026 11:05:12 -0700 Subject: [PATCH] fix: to_graphml crashes on dict/list-valued graph, node, and edge attributes nx.write_graphml only accepts scalar attribute values. to_graphml already coerces None -> "" (fixing a prior ValueError, #1502), but a dict or list value still raises TypeError: GraphML does not support type (or 'list') as data values. This hits real graphs in practice: a per-node "metadata" dict, or attach_hyperedges()'s graph-level "hyperedges" list, both crash `graphify export graphml` outright. Fixed by extending the existing scrub step to also JSON-serialize any dict/list-valued attribute (graph-level, node, and edge) before calling nx.write_graphml, alongside the existing None handling. Regression test: test_to_graphml_tolerates_dict_and_list_attribute_values (tests/test_export.py), mirroring the existing None-value test. Verified against a synthetic graph with a node metadata dict, an edge list attribute, and a graph-level hyperedges list, and against a real 2,300+ node project graph that previously crashed on all three. --- graphify/export.py | 18 ++++++++++++++---- tests/test_export.py | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/graphify/export.py b/graphify/export.py index be44a9d03..217c1013e 100644 --- a/graphify/export.py +++ b/graphify/export.py @@ -912,14 +912,24 @@ def to_graphml( for k in [k for k in attrs if k.startswith("_")]: del attrs[k] # nx.write_graphml raises ValueError on None attribute values; replace with "". + # It also only accepts scalar attribute values, so dict/list-valued + # attributes (e.g. per-node "metadata", graph-level "hyperedges") must be + # serialized to a JSON string first, or the writer raises TypeError. + def _scrub(val): + if val is None: + return "" + if isinstance(val, (dict, list)): + return json.dumps(val) + return val + + for key, val in list(H.graph.items()): + H.graph[key] = _scrub(val) for node_id in H.nodes(): for key, val in list(H.nodes[node_id].items()): - if val is None: - H.nodes[node_id][key] = "" + H.nodes[node_id][key] = _scrub(val) for u, v in H.edges(): for key, val in list(H.edges[u, v].items()): - if val is None: - H.edges[u, v][key] = "" + H.edges[u, v][key] = _scrub(val) nx.write_graphml(H, output_path) diff --git a/tests/test_export.py b/tests/test_export.py index 7194d8b78..b6ab84860 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -99,6 +99,25 @@ def test_to_graphml_tolerates_none_attribute_values(): content = out.read_text() assert "