Skip to content

fix(python): resolve relative subpackage import to package __init__ (#2455) - #2688

Closed
ousamabenyounes wants to merge 1 commit into
Graphify-Labs:v8from
ousamabenyounes:fix/issue-2455
Closed

fix(python): resolve relative subpackage import to package __init__ (#2455)#2688
ousamabenyounes wants to merge 1 commit into
Graphify-Labs:v8from
ousamabenyounes:fix/issue-2455

Conversation

@ousamabenyounes

Copy link
Copy Markdown
Contributor

Summary

Fix #2455

A Python relative import of a sibling subpackage (from ...graphs import build_graph, where graphs/ is a package — a directory with __init__.py, not a graphs.py module) emitted an imports_from edge whose target was an absolute-scan-path-derived slug for a nonexistent graphs.py, instead of resolving to the package's __init__.py file node.

Root cause: _import_python built the relative target as base/<module>.py unconditionally. For a subpackage that file does not exist, so the existence-gated target_file stamp never fired, and the #2169 remap had nothing to canonicalize — the edge kept a per-checkout absolute-path slug (<scan>_src_mypkg_graphs_py) that matches no node and dangles. This is the same reproducibility class as #2262/#2273/#2457.

Fix: probe the candidate on disk with _probe_python_module_candidate — the exact resolver the companion imports edge already uses via _resolve_python_module_path — so graphs resolves to graphs/__init__.py. The target_file stamp then fires and the edge canonicalizes to the real package file node. A genuinely nonexistent module still falls back to the prior dangling behaviour, unchanged.

Reproduces with

src/mypkg/__init__.py
src/mypkg/api/__init__.py
src/mypkg/api/routes/__init__.py
src/mypkg/graphs/__init__.py            -> def build_graph(): return {}
src/mypkg/api/routes/health.py          -> from ...graphs import build_graph

Before (on v8):

src_mypkg_api_routes_health --imports_from--> <scan>_src_mypkg_graphs_py   # BUG: graphs.py does not exist
src_mypkg_api_routes_health --imports-->      src_mypkg_graphs_init_build_graph

After:

src_mypkg_api_routes_health --imports_from--> src_mypkg_graphs_init
src_mypkg_api_routes_health --imports-->      src_mypkg_graphs_init_build_graph

Test verification (RED → GREEN)

New test test_relative_subpackage_import_from_targets_package_init in tests/test_python_import_resolution.py.

RED — unmodified v8 base, new test applied, no prod fix:

F
E   AssertionError: assert False
     +  where False = _has_edge(..., 'src_mypkg_api_routes_health', 'src_mypkg_graphs_init', 'imports_from')
tests/test_python_import_resolution.py: AssertionError
1 failed

GREEN — with the fix:

tests/test_python_import_resolution.py::test_relative_subpackage_import_from_targets_package_init
1 passed

Full local suite

uv run --frozen pytest tests/ -q4328 passed, 3 skipped (+1 new). The 4 failing tests (test_ollama.py::*, one test_labeling ordering artifact) are pre-existing and reproduce identically on the unmodified v8 base — they are backend-detection env leakage (GEMINI_API_KEY), unrelated to this change (which touches only _import_python). skillgen validators (--check, --monolith-roundtrip, --always-on-roundtrip) and graphify --help smoke all green.

Files changed

File Change
graphify/extract.py _import_python: probe the relative-import candidate on disk so a subpackage resolves to its __init__.py
tests/test_python_import_resolution.py regression test for subpackage imports_from resolution
CHANGELOG.md entry under 0.9.41

…raphify-Labs#2455)

A relative import of a sibling subpackage (from ...graphs import x, where
graphs/ is a package) built its imports_from target from a naive <module>.py
path that does not exist, so the target_file stamp never fired and the edge
kept an absolute-scan-path-derived slug that the Graphify-Labs#2169 remap could not heal —
dangling and machine-specific per checkout.

Probe the candidate on disk via _probe_python_module_candidate (the same
resolver the companion imports edge already uses), resolving graphs ->
graphs/__init__.py so the target canonicalizes to the package file node. A
genuinely nonexistent module still falls back to the prior dangling behaviour.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).


Graphify review — findings

This PR modifies Python relative import resolution in graphify/extract.py. In _import_python, when handling a relative import target, it now probes the candidate path on disk via the newly imported _probe_python_module_candidate helper so that a relative import naming a subpackage (a directory with __init__.py) resolves to that package's __init__.py rather than defaulting to a .py module path. The change adds a corresponding test verifying that from ...graphs import build_graph (where graphs/ is a package) produces an imports_from edge to the package __init__.py node, plus a CHANGELOG entry. The diff also lists many changelog/symbol names, but the substantive edits are confined to the extract logic and this one test.

No blocking issues surfaced. 1 lower-confidence candidate did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1615 functions depend on the 396 functions this change touches.

Health — this change adds coupling hotspots:

  • worse: extract() — 439 callers, 41 callees

Verification — 1615 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 1475 function(s) in the blast radius were not formally verified this run

· 1 more finding(s) on lines outside this diff (see the check run).

safishamsi added a commit that referenced this pull request Aug 13, 2026
Correctness (#2685 loop-binding shadow, #2699 provenance, #2707 affected
seed, #2688 py subpackage imports, #2602 sql grammar, #2683 cache
integrity, #2682 report basename) + Windows portability/docs batch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@safishamsi

Copy link
Copy Markdown
Collaborator

Shipped in v0.9.42 (graphifyy==0.9.42 on PyPI). Relative subpackage imports resolve to the package __init__. Credited in the release notes. Thanks @ousamabenyounes!

@safishamsi safishamsi closed this Aug 13, 2026
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.

Relative import of a subpackage emits an abs-path slug imports_from target to a non-existent <pkg>.py

2 participants