Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion graphify/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,23 @@ def _python_import_bindings(node, source: bytes) -> list[tuple[str, str]]:
return bindings


def _missing_relative_module_name(attempted: Path, root: Path, raw: str) -> str:
"""Dotted module name, relative to ``root``, of an unresolved relative import.

``attempted`` is the file the import would have named (``pkg/missing.py`` or
``pkg/__init__.py``). Falls back to ``ref:`` + the raw specifier when the
import climbs above the scan root, so the id never carries an absolute path.
"""
try:
rel = attempted.relative_to(root)
except ValueError:
return f"ref:{raw}"
parts = list(rel.with_suffix("").parts)
if parts and parts[-1] == "__init__":
parts.pop()
return ".".join(parts) if parts else f"ref:{raw}"


def _import_python(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression — _import_python()

fans out to 7 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

node, source: bytes, file_nid: str, stem: str, edges: list, str_path: str,
scope_stack: list[str] | None = None, scan_root: Path | None = None,
Expand Down Expand Up @@ -749,13 +766,24 @@ def _import_python(
target_path = _resolve_python_module_path(
module_name, current_path, root, level=dots
)
missing = False
if target_path is None:
base = current_path.parent
for _ in range(dots - 1):
base = base.parent
rel = (module_name.replace(".", "/") + ".py") if module_name else "__init__.py"
target_path = base / rel
tgt_nid = _make_id(str(target_path))
missing = not target_path.is_file()
if missing:
# No file behind the import: minting the id from the
# attempted absolute path bakes the checkout location (and
# OS username) into the graph, and the root-relative id
# remap never rewrites it. Use the dotted module name
# relative to the scan root, the id an unresolved absolute
# import of the same module already gets.
tgt_nid = _make_id(_missing_relative_module_name(target_path, root, raw))
else:
tgt_nid = _make_id(str(target_path))
else:
# Use the shared scan-root-aware resolver for absolute imports.
# It stops at the corpus boundary and handles package roots the
Expand Down
25 changes: 25 additions & 0 deletions tests/test_python_import_resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,31 @@ def test_overdeep_relative_import_is_unresolved_not_fatal(tmp_path: Path):
assert _node_id(result, "ok()", "pkg/mod.py")


def _missing_import_targets(checkout: Path) -> set[str]:
_write(checkout / "pkg" / "__init__.py", "")
source = _write(
checkout / "pkg" / "app.py",
"from .absent import helper\n"
"from pkg.absent import other\n\n"
"def run():\n"
" return helper(), other()\n",
)
result = extract([source, checkout / "pkg" / "__init__.py"], cache_root=checkout)
return {e["target"] for e in result["edges"] if e["relation"] == "imports_from"}


def test_missing_relative_import_target_id_does_not_depend_on_the_checkout(tmp_path: Path):
"""A relative import of a module with no file behind it minted its target id
from the attempted absolute path (``..._pkg_absent_py``), which the
root-relative remap never rewrites: the checkout location and OS username
ended up in the graph. It now gets the dotted module name, the id an
unresolved absolute import of the same module already gets."""
first = _missing_import_targets(tmp_path / "clone_one")
second = _missing_import_targets(tmp_path / "elsewhere" / "clone_two")

assert first == second == {"pkg_absent"}


def test_ordinary_relative_import_still_resolves(tmp_path: Path):
target = _write(tmp_path / "pkg" / "sibling.py", "def helper():\n return 1\n")
source = _write(tmp_path / "pkg" / "mod.py", "from .sibling import helper\n")
Expand Down
Loading