fix(python): resolve absolute package import targets - #3729
Ha1baraA11 wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.
Graphify review — findings
Resolves absolute Python imports at extraction time by probing the filesystem the same way relative imports already do, so from pkg.sub import thing produces a file-level edge to the real pkg_sub_init node instead of a dangling pkg_sub dotted-name slug (#3723). Searches upward from the importer through non-package ancestors and guards against turning a same-basename external import into a self-loop, falling back to the dotted-name id only when no module file resolves.
Worth a look
- target_path may reference an unbound/carried-over value from prior loop iterations —
graphify/extract.py:515· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Absolute import probing may bind to unrelated files sharing the module basename —
graphify/extract.py:494· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2068 functions depend on the 259 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 656 callers, 45 callees - new:
_rebuild_code()— 142 callers, 54 callees - new:
extract_js()— 87 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
dispatch_command()— 2 callers, 125 callees - new:
_get_extractor()— 26 callers, 6 callees - new:
run_pipeline()— 8 callers, 13 callees - new:
collect_files()— 17 callers, 6 callees - …and 30 more — each is listed as a finding
Verification — 2068 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: 1893 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
121 of 290 test file(s) selected (42%) via static blast radius.
tests/test_astro_extraction.py— impacttests/test_astro_import_ids.py— impacttests/test_build.py— impacttests/test_builtin_global_type_refs.py— impacttests/test_case_sensitive_resolution.py— impacttests/test_cjs_module_extension.py— impacttests/test_cpp_nested_and_cli.py— impacttests/test_cpp_objc_cross_file_calls.py— impacttests/test_cross_extension_reexport_self_cycle.py— impacttests/test_cross_language_call_resolution.py— impacttests/test_cross_repo_external_call_guards.py— impacttests/test_cross_repo_member_calls.py— impacttests/test_csharp_call_site_generic_args.py— impacttests/test_csharp_enum_members.py— impacttests/test_csharp_field_generic_args.py— impacttests/test_csharp_generic_callsites.py— impacttests/test_csharp_interface_dispatch.py— impacttests/test_csharp_member_calls.py— impacttests/test_csharp_member_nodes.py— impacttests/test_csharp_object_creation.py— impacttests/test_csharp_partial_classes.py— impacttests/test_csharp_type_resolution.py— impacttests/test_definition_file_portability.py— impacttests/test_detect.py— impacttests/test_dotnet.py— impacttests/test_duplicate_annotation_edges.py— impacttests/test_elixir_import_resolution.py— impacttests/test_extract.py— impacttests/test_extract_cache_location.py— impacttests/test_file_label_disambiguation.py— impacttests/test_file_node_id_spec.py— impacttests/test_forwarding_review_findings.py— impacttests/test_go_builtin_call_targets.py— impacttests/test_go_interface_methods.py— impacttests/test_go_qualified_resolution.py— impacttests/test_import_extension_resolution.py— impacttests/test_import_self_loops.py— impacttests/test_imported_export_forwarding.py— impacttests/test_incremental.py— impacttests/test_indirect_call_arrow_single_param_shadow.py— impacttests/test_indirect_call_block_scoped_shadow.py— impacttests/test_indirect_call_catch_binding_shadow.py— impacttests/test_indirect_call_external_import_shadow.py— impacttests/test_indirect_call_for_of_binding_shadow.py— impacttests/test_indirect_call_function_expression_shadow.py— impacttests/test_indirect_call_nested_closure_shadow.py— impacttests/test_indirect_dispatch.py— impacttests/test_indirect_dispatch_assign_return.py— impacttests/test_indirect_dispatch_getattr.py— impacttests/test_inferred_confidence_rubric.py— impact- … and 71 more
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
· 38 more finding(s) on lines outside this diff (see the check run).
|
Thanks @Ha1baraA11 — the direction is right (resolve absolute package imports at the scan root; the
Not far off — mostly consolidating onto the existing resolver. Happy to re-review once the walk is bounded. |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 4 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Resolves Python absolute imports through the shared module resolver at extraction time so import edges stamp the real target file, including files importable from a nested src/-style root; _import_python now takes a scan_root and _safe_extract threads it into extract_python. Adds a corpus-wide ambiguity guard (_python_absolute_import_alias_files + _suppress_ambiguous_python_imports) that, when one absolute module name maps to multiple scanned files, repoints the import edge to a stable node-less id so the graph builder drops it rather than binding to an arbitrary same-named package, and flags dependent calls (including *-import call sites) so symbol resolution leaves them dangling. A unique hit under the scan root stays authoritative even when a nested independently-importable tree shares the name, keeping legitimate imports resolved while only genuinely ambiguous ones fall closed.
Worth a look
- Early
returnin continuation-processing aborts remaining imports on the node —graphify/extractors/resolution.py:2563· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- _python_import_bindings dotted_name local binding uses last segment instead of first —
graphify/extract.py· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Parenthesized Python from-import bindings are ignored —
graphify/extract.py:599· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Global XAML extract root races across concurrent extractions —
graphify/extract.py:6810· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2602 functions depend on the 679 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 665 callers, 47 callees - new:
_rebuild_code()— 142 callers, 54 callees - new:
_extract_generic()— 18 callers, 29 callees - new:
extract_js()— 87 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
_resolve_js_module_path()— 34 callers, 9 callees - new:
dispatch_command()— 2 callers, 125 callees - new:
extract_objc()— 27 callers, 9 callees - …and 51 more — each is listed as a finding
Verification — 2602 functions in the blast radius were not formally verified this run (proofs are advisory here).
Health delta baseline: last indexed commit a5957aa (diverged from this PR's base — delta is approximate).
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: 2427 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
125 of 290 test file(s) selected (43%) via static blast radius.
tests/test_astro_extraction.py— impacttests/test_astro_import_ids.py— impacttests/test_build.py— impacttests/test_builtin_global_type_refs.py— impacttests/test_case_sensitive_resolution.py— impacttests/test_cjs_module_extension.py— impacttests/test_cpp_nested_and_cli.py— impacttests/test_cpp_objc_cross_file_calls.py— impacttests/test_cross_extension_reexport_self_cycle.py— impacttests/test_cross_language_call_resolution.py— impacttests/test_cross_repo_external_call_guards.py— impacttests/test_cross_repo_member_calls.py— impacttests/test_csharp_call_site_generic_args.py— impacttests/test_csharp_enum_members.py— impacttests/test_csharp_field_generic_args.py— impacttests/test_csharp_generic_callsites.py— impacttests/test_csharp_interface_dispatch.py— impacttests/test_csharp_member_calls.py— impacttests/test_csharp_member_nodes.py— impacttests/test_csharp_object_creation.py— impacttests/test_csharp_partial_classes.py— impacttests/test_csharp_type_resolution.py— impacttests/test_definition_file_portability.py— impacttests/test_detect.py— impacttests/test_dotnet.py— impacttests/test_duplicate_annotation_edges.py— impacttests/test_elixir_import_resolution.py— impacttests/test_extract.py— impacttests/test_extract_cache_location.py— impacttests/test_file_label_disambiguation.py— impacttests/test_file_node_id_spec.py— impacttests/test_forwarding_review_findings.py— impacttests/test_go_builtin_call_targets.py— impacttests/test_go_interface_methods.py— impacttests/test_go_qualified_resolution.py— impacttests/test_import_extension_resolution.py— impacttests/test_import_self_loops.py— impacttests/test_imported_export_forwarding.py— impacttests/test_incremental.py— impacttests/test_indirect_call_arrow_single_param_shadow.py— impacttests/test_indirect_call_block_scoped_shadow.py— impacttests/test_indirect_call_catch_binding_shadow.py— impacttests/test_indirect_call_external_import_shadow.py— impacttests/test_indirect_call_for_of_binding_shadow.py— impacttests/test_indirect_call_function_expression_shadow.py— impacttests/test_indirect_call_nested_closure_shadow.py— impacttests/test_indirect_dispatch.py— impacttests/test_indirect_dispatch_assign_return.py— impacttests/test_indirect_dispatch_getattr.py— impacttests/test_inferred_confidence_rubric.py— impact- … and 75 more
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify extract.
The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify extract\_python.
The verifier did not have enough to check extract\_python, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_import\_python.
The verifier did not have enough to check \_import\_python, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `scan_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_resolve\_python\_member\_calls.
The verifier did not have enough to check \_resolve\_python\_member\_calls, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous
Could not verify: Could not verify \_safe\_extract.
The verifier did not have enough to check \_safe\_extract, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `extractor` is annotated `Callable` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_safe\_extract\_with\_xaml\_root.
The verifier did not have enough to check \_safe\_extract\_with\_xaml\_root, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_augment\_symbol\_resolution\_edges.
The verifier did not have enough to check \_augment\_symbol\_resolution\_edges, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_collect\_python\_symbol\_resolution\_facts.
The verifier did not have enough to check \_collect\_python\_symbol\_resolution\_facts, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_resolve\_cross\_file\_imports.
The verifier did not have enough to check \_resolve\_cross\_file\_imports, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: non-vacuity: domain too small (only 2 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous
· 2 grounded finding(s) anchored inline below; 57 more finding(s) on lines outside this diff (see the check run).
| return bindings | ||
|
|
||
|
|
||
| def _import_python( |
There was a problem hiding this comment.
_import_python()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -3459,7 +3459,8 @@ def _lua_is_require_call(node, source: bytes) -> bool: | |||
|
|
|||
|
|
|||
| def _extract_generic( | |||
There was a problem hiding this comment.
_extract_generic()
fans out to 29 callees (efferent coupling); 18 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 4 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds fail-closed handling for ambiguous Python absolute imports: when the same module name maps to more than one scanned file, _suppress_ambiguous_python_imports leaves the import edge dangling on a stable node-less id and flags dependent call edges rather than binding to an arbitrary same-named package. Carves out an exception for a bare import helper resolved to a concrete same-directory sibling in the importer's own non-package directory, treating that directory-local target as unambiguous via _is_resolved_loose_sibling. Wires extract_python to receive the scan_root so imports are probed against every in-root, non-package ancestor the shared resolver would use (_python_absolute_import_alias_files, _python_import_bindings).
Worth a look
- Python import_handler hook now receives an extra positional argument —
graphify/extractors/engine.py:3711· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Context source_file can escape scan root —
graphify/extract.py:7194· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Dotted Python imports record the wrong local binding —
graphify/extract.py:650· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- XAML extraction root stored in process-global state races across concurrent extractions —
graphify/extract.py:6852· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2613 functions depend on the 696 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 665 callers, 47 callees - new:
_rebuild_code()— 142 callers, 54 callees - new:
_extract_generic()— 18 callers, 29 callees - new:
extract_js()— 87 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
_resolve_js_module_path()— 34 callers, 9 callees - new:
dispatch_command()— 2 callers, 125 callees - new:
extract_objc()— 27 callers, 9 callees - …and 51 more — each is listed as a finding
Verification — 2613 functions in the blast radius were not formally verified this run (proofs are advisory here).
Health delta baseline: last indexed commit a5957aa (diverged from this PR's base — delta is approximate).
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: 2438 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
125 of 290 test file(s) selected (43%) via static blast radius.
tests/test_astro_extraction.py— impacttests/test_astro_import_ids.py— impacttests/test_build.py— impacttests/test_builtin_global_type_refs.py— impacttests/test_case_sensitive_resolution.py— impacttests/test_cjs_module_extension.py— impacttests/test_cpp_nested_and_cli.py— impacttests/test_cpp_objc_cross_file_calls.py— impacttests/test_cross_extension_reexport_self_cycle.py— impacttests/test_cross_language_call_resolution.py— impacttests/test_cross_repo_external_call_guards.py— impacttests/test_cross_repo_member_calls.py— impacttests/test_csharp_call_site_generic_args.py— impacttests/test_csharp_enum_members.py— impacttests/test_csharp_field_generic_args.py— impacttests/test_csharp_generic_callsites.py— impacttests/test_csharp_interface_dispatch.py— impacttests/test_csharp_member_calls.py— impacttests/test_csharp_member_nodes.py— impacttests/test_csharp_object_creation.py— impacttests/test_csharp_partial_classes.py— impacttests/test_csharp_type_resolution.py— impacttests/test_definition_file_portability.py— impacttests/test_detect.py— impacttests/test_dotnet.py— impacttests/test_duplicate_annotation_edges.py— impacttests/test_elixir_import_resolution.py— impacttests/test_extract.py— impacttests/test_extract_cache_location.py— impacttests/test_file_label_disambiguation.py— impacttests/test_file_node_id_spec.py— impacttests/test_forwarding_review_findings.py— impacttests/test_go_builtin_call_targets.py— impacttests/test_go_interface_methods.py— impacttests/test_go_qualified_resolution.py— impacttests/test_import_extension_resolution.py— impacttests/test_import_self_loops.py— impacttests/test_imported_export_forwarding.py— impacttests/test_incremental.py— impacttests/test_indirect_call_arrow_single_param_shadow.py— impacttests/test_indirect_call_block_scoped_shadow.py— impacttests/test_indirect_call_catch_binding_shadow.py— impacttests/test_indirect_call_external_import_shadow.py— impacttests/test_indirect_call_for_of_binding_shadow.py— impacttests/test_indirect_call_function_expression_shadow.py— impacttests/test_indirect_call_nested_closure_shadow.py— impacttests/test_indirect_dispatch.py— impacttests/test_indirect_dispatch_assign_return.py— impacttests/test_indirect_dispatch_getattr.py— impacttests/test_inferred_confidence_rubric.py— impact- … and 75 more
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify extract.
The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify extract\_python.
The verifier did not have enough to check extract\_python, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_import\_python.
The verifier did not have enough to check \_import\_python, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `scan_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_resolve\_python\_member\_calls.
The verifier did not have enough to check \_resolve\_python\_member\_calls, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous
Could not verify: Could not verify \_safe\_extract.
The verifier did not have enough to check \_safe\_extract, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `extractor` is annotated `Callable` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_safe\_extract\_with\_xaml\_root.
The verifier did not have enough to check \_safe\_extract\_with\_xaml\_root, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_augment\_symbol\_resolution\_edges.
The verifier did not have enough to check \_augment\_symbol\_resolution\_edges, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_collect\_python\_symbol\_resolution\_facts.
The verifier did not have enough to check \_collect\_python\_symbol\_resolution\_facts, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_resolve\_cross\_file\_imports.
The verifier did not have enough to check \_resolve\_cross\_file\_imports, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: non-vacuity: domain too small (only 2 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous
· 2 grounded finding(s) anchored inline below; 57 more finding(s) on lines outside this diff (see the check run).
| return bindings | ||
|
|
||
|
|
||
| def _import_python( |
There was a problem hiding this comment.
_import_python()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -3459,7 +3459,8 @@ def _lua_is_require_call(node, source: bytes) -> bool: | |||
|
|
|||
|
|
|||
| def _extract_generic( | |||
There was a problem hiding this comment.
_extract_generic()
fans out to 29 callees (efferent coupling); 18 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
…ge imports #3729 (absolute package import resolution) and #3784 (retract phantom package/module-collision import edges) landed in the same cut and interacted: a legitimate `from pkg import sub` (pure package, no colliding pkg.py) now resolves its provisional edge to the real `pkg/__init__.py` node, but #3784 retracted it — via the __init__.py candidate id and the len==1 blind fallback — even though there is no name collision (no phantom cycle to fix). This dropped the correct package-dependency edge (caught by test_absolute_package_import_targets_package_init). Tighten #3784 to retract ONLY genuine module-collision phantoms: drop the __init__.py candidate id forms (keep the module-file `pkg`/`pkg.py` collision ids that the #3777 fix actually targets) and remove the len==1 blind fallback (a real phantom always matches by its module-file id, so no fallback is needed). Both the #3729 package-import tests and all 26 #3784 phantom-cycle tests now pass together. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Shipped in v0.9.67 (on PyPI). Cherry-picked with authorship preserved. Thanks @Ha1baraA11! Great rework in response to the review — scan-root confinement via the canonical resolver, both import forms, and the fail-closed ambiguity guard all landed. On merge I reconciled a small interaction with #3784 (it was over-retracting the legitimate package-init edge this PR now produces). |
What does this PR do?
Fixes #3723 by resolving absolute Python package imports to the package or module file that exists on disk. The extractor now probes non-package ancestors of the importing file, preserves the existing dotted-name fallback for external imports, and avoids turning same-basename external imports into self-loops.
Type of change
How was this tested?
This was tested with the following commands:
The regression test reproduces both package targets and confirms that pkg and pkg_sub dangling ids are replaced by pkg_init and pkg_sub_init.
Graphify-specific checklist