Skip to content

Build a Graphtage tree from plain import statements - #165

Merged
ESultanik merged 4 commits into
masterfrom
151-ast-import-builder
Sep 9, 2026
Merged

ESultanik merged 4 commits into
masterfrom
151-ast-import-builder

Conversation

@ESultanik

Copy link
Copy Markdown
Collaborator

Closes #151

ASTBuilder registered an expander and a builder for ast.ImportFrom but nothing for ast.Import, so
ast_to_tree(ast.parse("import os")) raised NotImplementedError. Since import x is far more common than
from y import x, most real Python sources could not be diffed at all.

Both ast.Import and ast.ImportFrom expose their aliases as .names, so ast.Import joins the existing
expander. The new builder produces the same graphtage.ast.Import node with an empty from_name; the node's
print and PyImportFormatter.print_Import already treat an empty from_name as the plain import x form, so
nothing on the printing side changes.

The expander was renamed from expand_import_from to expand_import because it now handles both statement forms.
Builder registration is decorator-based, so the name is not part of the dispatch.

Validation

The regression tests were run against the unfixed builder first. All seven subtests failed with
NotImplementedError: A builder for type Import is not defined for object Import(names=[alias(name='os', ...)]),
and all pass with the fix. test_plain_import_builds covers import os, import os.path, import os as o, and
import os, sys; test_plain_import_printing checks the rendered statement and keeps from os import path as a
control. Both tests work at the tree-construction and unedited-printing level, so they do not depend on the
separate diff-rendering problem in #150.

  • ruff check graphtage test docs bindist: passes
  • pytest: 142 passed, 8 subtests passed
  • make -C docs html SPHINXOPTS="-W --keep-going": build succeeded
  • uv lock --check: fails identically on an unmodified checkout because of a local exclude-newer setting; no
    dependency changed here

Out of scope

Most other statement and expression node types still have no builder, including FunctionDef, ClassDef, If,
For, While, With, Try, BinOp, Compare, AugAssign, AnnAssign, Lambda, the comprehensions,
Starred, Raise, Assert, Delete, Pass, JoinedStr, Slice, Match, and the Async* variants. Alias
asname values also render quoted (from os import path as "p"), which predates this change and affects
ImportFrom too.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GypKU5KdLfs2Cf8kS2TzJa

ESultanik and others added 4 commits September 9, 2026 10:17
ASTBuilder registered an expander and a builder for ast.ImportFrom but
nothing for ast.Import, so ast_to_tree raised NotImplementedError on any
module containing a plain `import x` statement. Because `import x` is far
more common than `from y import x`, that left most real Python sources
undiffable.

Register ast.Import on the existing alias expander, which both node types
expose as `.names`, and add a builder that constructs the same
graphtage.ast.Import node with an empty from_name. Both the node and
PyImportFormatter already treat an empty from_name as the plain form, so no
change to the printing side is needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GypKU5KdLfs2Cf8kS2TzJa
@ESultanik
ESultanik merged commit 9375975 into master Sep 9, 2026
12 checks passed
@ESultanik
ESultanik deleted the 151-ast-import-builder branch September 9, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ASTBuilder has no builder for ast.Import

1 participant