Skip to content

Python: codegraph_callers misses module-attribute call sites (module.func(...) after from pkg import module) — zero recall on a common test/namespacing pattern #578

@JosefAschauer

Description

@JosefAschauer

Python: codegraph_callers misses module-attribute call sites (module.func(...) after from pkg import module) — zero recall on a common test/namespacing pattern

Summary

On Python, a call made through a module object attributemodule.func(...) where module was bound via import pkg.module as module or from pkg import module — produces no calls edge, so codegraph_callers / callees / impact / trace return empty for the target. Both the caller node and the callee node are correctly indexed; only the edge between them is dropped.

This is the same root cause class confirmed and fixed for Go in #388 / #469 (isExternalImportresolveViaImport returning null → fall-through), and analogous to the per-language import-resolution fixes already shipped for Java/Kotlin (#314), TypeScript (#359), and C# (#381). Python was never covered. This was flagged in a comment on #388, but since that issue was scoped and closed as Go-specific, it has no dedicated tracking — hence this issue.

Environment

Minimal reproduction

repro/
  pkg/
    __init__.py
    mod.py          # def helper(x): return x
  tests/
    test_mod.py

pkg/mod.py:

def helper(x):
    return x

tests/test_mod.py — both common module-attribute forms:

from pkg import mod              # form 1: from-import of the module
import pkg.mod as mod2           # form 2: aliased module import

def test_helper():
    assert mod.helper(1) == 1    # attribute access on module object
    assert mod2.helper(2) == 2

Expected: codegraph_callers helper → 2 callers (the two *.helper(...) calls in test_mod.py).
Observed: No callers found.

Compare with the form that does work today:

from pkg.mod import helper       # bare-name import
def test_helper():
    assert helper(1) == 1        # bare call → edge IS created

So the gap is specifically the qualified/attribute call shape, exactly mirroring Go's pkg.Func(...).

Production evidence (this is not a toy-only failure)

In the RAGFlow codebase, dialog_service._repair_grounded_citation_alignment is called by 9 regression tests via module-attribute access:

# test/unit_test/api/db/services/test_dialog_service_prompt_sanitization.py
from api.db.services import dialog_service      # line 21
...
answer, cited = dialog_service._repair_grounded_citation_alignment(...)   # lines 1766, 1786, 1805, 1822
answer = dialog_service._preserve_grounded_reference_labels(...)          # lines 1730, 1747, 1770, 1840, 1857

Graph state on the fresh 0.9.7 index:

Node Indexed? Evidence
_repair_grounded_citation_alignment (callee) codegraph_searchdialog_service.py:754
test_repair_grounded_citation_alignment_* (callers) codegraph_searchtest_dialog_service_prompt_sanitization.py:1757/1777/1796/1815
calls edge between them codegraph_callers _repair_grounded_citation_alignment"No callers found"

Same result for _preserve_grounded_reference_labels. Both ends exist; the edge does not.

Real-world impact: codegraph_callers reporting zero callers led to a (correctly caught, but only via textual grep) near-miss where these functions looked like dead code. For any user relying on callers/impact to make "is this safe to delete?" decisions, this is a silent high-recall miss. The pattern (from pkg import module then module.helper(...)) is extremely common in Python test suites that use module-attribute access for namespacing and ergonomic monkeypatching.

Hypothesis

Parallel to the #469 Go root cause: the Python resolver likely resolves a call's qualifier (module) to the imported module, but the qualified-member lookup (module.helper → the helper def node) either isn't attempted for the module.attr(...) call shape or fails to match because the target's stored name is the bare helper and the resolver doesn't strip/relate the module qualifier. Edge is then silently dropped (no fall-through to a bad match, just nothing — consistent with the clean ~0 recall).

Ask

  1. Can the Python import/call resolver get the same treatment Go received in fix(resolution): Go cross-package qualified calls resolve via go.mod (#388) #469 — resolve module.func(...) attribute calls (where module is a from pkg import module / import pkg.module as module binding) to the exported member node and emit the calls edge?
  2. Both from pkg import module and import pkg.module as module qualifier forms should be covered, plus re-exported aggregator modules.
  3. Happy to run targeted queries against the local .codegraph/codegraph.db (e.g. dump edges filtered to the test file) to validate any fix or hypothesis.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions