fix(python): a module member follows the package's re-exports too (#2248)

resolvePythonModuleMember (`mod.member` after `import mod` / `from pkg import
mod`) looks the member up with pythonModuleSymbol, so a package's
__init__.py re-exports lead to the definition.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-01 04:34:57 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 6d8802cef8
commit 34ede4de88
3 changed files with 27 additions and 10 deletions
+1
View File
@@ -14,6 +14,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Fixes
- A Python module member used through the imported module, like `generic.ObjectEditView` after `from netbox.views import generic`, `serializers.ValidationError` or `utils.assert_deserialize(…)`, now also follows the package's re-exports to its definition. Before, netbox's view classes lost their `generic.*` base classes, and mealie's test helpers called through `utils.` went unresolved.
- A Python name imported from a package now follows the package's re-exports to where it's defined. That covers both `from .users import *` and `from .tokens import Token` in its `__init__.py`. Before, httpx's own tests' `httpx.Client(…)` and `httpx.URL(…)`, pytest's `pytest.raises(…)` and `pytest.param(…)`, and netbox's `from users.models import User` went unresolved or to a same-named class elsewhere, such as a test's `User` or an Alembic migration's copy of a model.
- In NestJS projects, the provider naming convention (`…Service`, `…Controller`, …) now picks a class only from the reference's own file. Another file's provider is reached through its import, or by ordinary name matching that prefers the nearest declaration. Before, the convention's preference for `*.service.ts` files sent Nest's own specs' `TransientService` and `TestService` to same-named classes in unrelated sample apps.
- In Astro projects, the component-name convention now applies only inside `.astro` files, where components are used. Before, a TypeScript type with a component's name was linked to that component: astrowind's `image?: Image` and `callToAction?: CallToAction` in its type declarations, and starlight's Playwright `page: Page` in its tests.
@@ -32,6 +32,22 @@ from .users import *
`,
'netbox/alembic/versions/0001_tokens.py': `class Token:
pass
`,
'netbox/netbox/__init__.py': '',
'netbox/netbox/views/__init__.py': '',
'netbox/netbox/views/generic/__init__.py': `from .object_views import *
`,
'netbox/netbox/views/generic/object_views.py': `class ObjectEditView:
pass
`,
'netbox/dcim/views.py': `from netbox.views import generic
class DeviceEditView(generic.ObjectEditView):
pass
`,
'netbox/extras/views.py': `class ObjectEditView:
pass
`,
'netbox/core/views.py': `from users.models import Token, User
@@ -53,6 +69,12 @@ afterAll(() => {
});
describe('Python package re-exports', () => {
it('lead a module member to its definition', () => {
const ids = cg.getNodesInFile('netbox/dcim/views.py').map((n) => n.id);
const bases = cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind === 'extends').map((e) => cg.getNode(e.target)!.filePath);
expect(bases).toEqual(['netbox/netbox/views/generic/object_views.py']);
});
it('lead an imported name to its definition, through star and named re-exports', () => {
const ids = cg.getNodesInFile('netbox/core/views.py').map((n) => n.id);
const targets = cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind === 'instantiates').map((e) => cg.getNode(e.target)!.filePath).sort();
+4 -10
View File
@@ -1897,16 +1897,10 @@ function resolvePythonModuleMember(
}
if (!resolvedPath || resolvedPath === ref.filePath) continue;
// Find the member as a top-level definition in the module file. Exclude
// `method` so `mod.foo` never lands on a same-named class method.
const target = context.getNodesInFile(resolvedPath).find(
(n) =>
n.name === member &&
(n.kind === 'function' ||
n.kind === 'class' ||
n.kind === 'variable' ||
n.kind === 'constant')
);
// Find the member as a top-level definition in the module file, or one it
// re-exports (a package's `__init__.py`). Exclude `method` so `mod.foo`
// never lands on a same-named class method.
const target = pythonModuleSymbol(resolvedPath, member, context, 0);
if (target) {
return { original: ref, targetNodeId: target.id, confidence: 0.85, resolvedBy: 'import' };
}