fix(python): resolve call edges through imported modules (#578) (#715)

Give resolvePythonModuleMember the same absolute-dotted-path fallback that
resolveModuleImportToFile already uses, so a `module.func()` call after
`from pkg import module` / `import pkg.module as module` records its `calls`
edge. Adds a regression test and a CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-06-07 02:11:06 -04:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 471084dd6e
commit 8d35931c3b
3 changed files with 62 additions and 1 deletions
+1
View File
@@ -56,6 +56,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Python now follows whole-module imports — `from . import certs` then `certs.where()`, or `from pkg import sub` then `sub.run()`. Calls and attribute access through an imported submodule now resolve to that submodule, and importing a module is recorded as a dependency on it even when the member you use is itself re-exported from a third-party package. This also fixed Python relative-import path resolution generally (`from .sub.mod import x`), so `codegraph affected` and impact see the real module graph of a package.
- Python now also links a whole-module **absolute** import (`import conduit.apps.signals`) to that module's file, not just `from x import y`. A module imported by its dotted path — common in package setup and side-effect imports — is no longer reported as having no dependents. Standard-library imports (`import os`) correctly create no edge. (Python)
- Python `from package import submodule` now links to that submodule's file, resolved through the import's package so it lands on the right one when same-named modules exist in sibling packages (the FastAPI / Django router-aggregator pattern: `from app.api.routes import authentication` with an unrelated `authentication.py` elsewhere). So a route/handler module pulled in only by an aggregator is no longer reported as having no dependents. (Python)
- Python now records the actual call edge for a function invoked through an imported module — `from pkg import module` (or `import pkg.module`) followed by `module.func()`, a common testing and namespacing pattern. Previously only the module-level dependency was tracked, so `codegraph_callers`, `codegraph_callees`, and impact on the target function came back empty even though the import itself resolved. (#578) (Python)
- Django `include('app.urls')` now records a dependency from the project URLconf onto the included app's `urls.py`, so an app's routes module is no longer reported as having no dependents and editing it surfaces the project that mounts it. (Django)
- A chained method call (`builder.Services.AddCoreServices(...)`) now resolves to its definition. Previously only a single-segment receiver (`obj.method()`) resolved, so a call through a property/member chain — very common for C# extension methods like ASP.NET dependency-injection registration (`AddCoreServices`/`AddWebServices`) and Guard clauses — found no definition. (C#, and any language with chained calls)
- A renamed default import (`import articlesController from './article.controller'` where the module does `export default router`) now records a dependency on the imported module. Previously only named imports linked, so a module consumed only through a default import — the standard Express/NestJS route-controller pattern — looked like nothing depended on it. External packages (`import React from 'react'`) still create no edge. (TypeScript/JavaScript)