Skip to content

Python: from <external> import name binds to an unrelated project symbol of the same name (exact-match) — false import cycles #2566

Closed

Description

@kogriv

Summary

In Python, from <external module> import <name> gets an imports edge to an unrelated project symbol that happens to have the same name: a top-level variable or function in another file. The edge has resolvedBy: "exact-match" and confidence 0.9. If that other file imports the first one, which is common when it is a script using the package, findCircularDependencies() reports a cycle that does not exist. callers <name> lists the importing file as a caller.

This is the Python counterpart of #2101 (TypeScript, closed via #2120/#2128). The 1.6.2 fix does not cover Python.

Environment

  • @colbymchenry/codegraph 1.6.2 from npm, which is the current release
  • also reproduced on main at eec9288 (built from source, tsc only)
  • Linux, Node 24

Minimal reproduction (2 files)

# analysis.py
from scipy import stats


def summary(values):
    return stats.describe(values)
# report.py
from analysis import summary

stats = summary([1, 2, 3])

scipy does not need to be installed.

$ codegraph init -y .
$ codegraph callers stats
Callers of "stats" (1):

variable stats (python) — report.py:3
file        analysis.py [imports]
  analysis.py:1

The edges table:

file:analysis.py -> variable stats (report.py)   imports  {"confidence":0.9,"resolvedBy":"exact-match","refName":"stats"}
file:report.py   -> function summary (analysis.py) imports {"confidence":0.9,"resolvedBy":"import","refName":"summary"}

CodeGraph.open(dir).findCircularDependencies() → [["analysis.py","report.py"]]

Expected: no edge from analysis.py to report.py, because stats comes from scipy and no cycle exists.

Variants (1.6.2 and main eec9288 give the same results)

analysis.py imports report.py defines cycle reported
from scipy import stats stats = ... (variable) yes
from scipy import stats def stats(): ... yes
from os import path (stdlib) path = ... yes
import scipy.stats as stats stats = ... no
from scipy import stats result = ... (control) no

The problem is therefore the from X import name form with an external X, whether X is a third-party package or the stdlib.

Relation to #2409 / #1921

#2409 (import logging → app/core/logging.py) is a different path through the resolver. There the edge targets a file and has resolvedBy: "import" (filename-suffix match). Here it targets a symbol and has resolvedBy: "exact-match".

I built the head of open PR #1921 (b9840f6), and it removes this edge as well: all three positive rows above give [], and the two controls stay []. The PR description mentions only the file-suffix case. Its tests may not pin this form, so this repro could be useful as a regression case if the PR lands.

Where it was found

On a real 280-file Python repository (a package plus research scripts), 1.6.2 reports 25 import cycles. 5 of them are this defect: one script defines a top-level stats, and five package modules do from scipy import stats. On #1921's head these 5 are gone, and the number of true cycles found does not change (16 of 45 in both builds).

Not checked: other languages; Windows; from X import a as b with a local b; whether callees / impact / affected follow the edge (I expect they do, as in #2101, but did not verify).

Activity

  1. added a commit that references this issue on Oct 15, 2026
  2. danusha2345 commented on Oct 15, 2026

    @danusha2345
    Contributor

    Reproduced on current main (c71a3d0) with the two files as given: analysis.py -> variable stats (report.py) as an imports edge, exact-match 0.9, and the same for from os import path against a path = … in report.py.

    The open PR #1921 (@inth3shadows — Python imports resolved from the package roots the project has) already carries the rule this needs: a name imported from a module no package root of the project provides names nothing in the project, so the import stays unresolved instead of falling to name matching. With that branch applied on top of main, both edges are gone and findCircularDependencies() returns [] for this repository.

  3. added a commit that references this issue on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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