From f87d0649c5e88d9451b5e8d91b6bbcd61d506159 Mon Sep 17 00:00:00 2001 From: azizur100389 Date: Sun, 3 May 2026 00:46:46 +0100 Subject: [PATCH] fix: silence "invalid file_type 'None'" warning on legacy graphs (#660) Users running `graphify update .` against a graph that contains nodes preserved from older graphify versions see the warning: [graphify] Extraction warning (31 issues): Node 1661 (id='telepathy_app') has invalid file_type 'None' - must be one of [...] The cause: `_rebuild_code` in watch.py preserves nodes from the existing graph.json that aren't in the new AST output. Some of those nodes (stubs, semantic concepts created before file_type was always populated) have file_type=null in the on-disk JSON, which becomes Python None on load. The validator in validate.py:33 then flags every such node. Add a normalization step in `build_from_json` next to the existing `source` -> `source_file` legacy canonicalization: if a node's file_type is None or empty, default it to "concept" before validation. This silences the false positive while keeping the validator strict for genuinely invalid values like "weird_type". --- graphify/build.py | 10 ++++++++- tests/test_build.py | 53 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/graphify/build.py b/graphify/build.py index 2c2c773bb..af7349891 100644 --- a/graphify/build.py +++ b/graphify/build.py @@ -51,7 +51,9 @@ def build_from_json(extraction: dict, *, directed: bool = False) -> nx.Graph: # Canonicalize legacy node/edge schema before validation. for node in extraction.get("nodes", []): - if isinstance(node, dict) and "source" in node and "source_file" not in node: + if not isinstance(node, dict): + continue + if "source" in node and "source_file" not in node: # Count edges that reference this node so the warning is actionable (#479) node_id = node.get("id", "?") affected_edges = sum( @@ -65,6 +67,12 @@ def build_from_json(extraction: dict, *, directed: bool = False) -> nx.Graph: file=sys.stderr, ) node["source_file"] = node.pop("source") + # Default missing/None file_type to "concept" so legacy graph.json + # entries (and stub nodes preserved by `_rebuild_code` from older + # graphify versions that didn't always populate file_type) don't + # trigger spurious "invalid file_type 'None'" validator warnings (#660). + if node.get("file_type") in (None, ""): + node["file_type"] = "concept" errors = validate_extraction(extraction) # Dangling edges (stdlib/external imports) are expected - only warn about real schema errors. diff --git a/tests/test_build.py b/tests/test_build.py index 2b47bc86d..3750b57d7 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -60,3 +60,56 @@ def test_build_merges_multiple_extractions(): G = build([ext1, ext2]) assert G.number_of_nodes() == 2 assert G.number_of_edges() == 1 + + +def test_none_file_type_defaults_to_concept(capsys): + """Legacy nodes with file_type=None (e.g. preserved from older graph.json + by `_rebuild_code`) must not trigger 'invalid file_type None' warnings (#660).""" + ext = { + "nodes": [ + {"id": "n1", "label": "Stub", "file_type": None, "source_file": "a.py"}, + {"id": "n2", "label": "Real", "file_type": "code", "source_file": "b.py"}, + ], + "edges": [], + "input_tokens": 0, + "output_tokens": 0, + } + G = build_from_json(ext) + err = capsys.readouterr().err + assert "invalid file_type" not in err + # The legacy node still exists in the graph and has been canonicalized + assert G.nodes["n1"]["file_type"] == "concept" + assert G.nodes["n2"]["file_type"] == "code" + + +def test_missing_file_type_defaults_to_concept(capsys): + """Nodes missing file_type entirely should also be canonicalized to 'concept'.""" + ext = { + "nodes": [ + {"id": "n1", "label": "Bare", "source_file": "a.py"}, + ], + "edges": [], + "input_tokens": 0, + "output_tokens": 0, + } + G = build_from_json(ext) + err = capsys.readouterr().err + assert "invalid file_type" not in err + assert "missing required field 'file_type'" not in err + assert G.nodes["n1"]["file_type"] == "concept" + + +def test_real_invalid_file_type_still_warns(capsys): + """Truly invalid file_type values (not None, not empty) must still warn.""" + ext = { + "nodes": [ + {"id": "n1", "label": "Bad", "file_type": "weird_type", "source_file": "a.py"}, + ], + "edges": [], + "input_tokens": 0, + "output_tokens": 0, + } + build_from_json(ext) + err = capsys.readouterr().err + assert "invalid file_type" in err + assert "weird_type" in err