From 25c9338640677cde949ea5feb3f3c51348befe57 Mon Sep 17 00:00:00 2001 From: SAI KISHAN A <222060771+kishansaaai@users.noreply.github.com> Date: Sat, 4 Jul 2026 02:05:46 +0530 Subject: [PATCH] Fix #666: Probe submodules for python package imports --- .../repowise/core/ingestion/graph/builder.py | 4 ++ .../core/ingestion/resolvers/python.py | 21 +++++++- tests/unit/ingestion/test_python_resolver.py | 52 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 tests/unit/ingestion/test_python_resolver.py diff --git a/packages/core/src/repowise/core/ingestion/graph/builder.py b/packages/core/src/repowise/core/ingestion/graph/builder.py index d0242976f..fceef6eb1 100644 --- a/packages/core/src/repowise/core/ingestion/graph/builder.py +++ b/packages/core/src/repowise/core/ingestion/graph/builder.py @@ -342,6 +342,10 @@ def build(self, progress: Any | None = None) -> nx.DiGraph: from ..resolvers.cpp import resolve_cpp_import_all targets = resolve_cpp_import_all(imp.module_path, path, ctx) + elif _lang == "python": + from ..resolvers.python import resolve_python_import_all + + targets = resolve_python_import_all(imp, path, ctx) else: single = resolve_import(imp.module_path, path, _lang, ctx) targets = (single,) if single else () diff --git a/packages/core/src/repowise/core/ingestion/resolvers/python.py b/packages/core/src/repowise/core/ingestion/resolvers/python.py index 9b76e8fdf..45f064bf9 100644 --- a/packages/core/src/repowise/core/ingestion/resolvers/python.py +++ b/packages/core/src/repowise/core/ingestion/resolvers/python.py @@ -3,6 +3,7 @@ from __future__ import annotations from pathlib import Path +from typing import Any from ..languages.python_modules import build_python_module_index from .context import ResolverContext @@ -67,6 +68,24 @@ def resolve_python_import(module_path: str, importer_path: str, ctx: ResolverCon if c in ctx.path_set: return c - # Stem-only fallback +# Stem-only fallback stem = module_path.split(".")[-1].lower() return ctx.stem_lookup(stem) + + +def resolve_python_import_all(imp: Any, importer_path: str, ctx: ResolverContext) -> tuple[str, ...]: + """Resolve a Python import, additionally probing submodules for package imports.""" + targets = [] + base = resolve_python_import(imp.module_path, importer_path, ctx) + if base: + targets.append(base) + if base.endswith("__init__.py"): + base_dir = Path(base).parent.as_posix() + for name in (imp.imported_names or []): + c1 = f"{base_dir}/{name}.py" + c2 = f"{base_dir}/{name}/__init__.py" + if c1 in ctx.path_set: + targets.append(c1) + elif c2 in ctx.path_set: + targets.append(c2) + return tuple(targets) diff --git a/tests/unit/ingestion/test_python_resolver.py b/tests/unit/ingestion/test_python_resolver.py new file mode 100644 index 000000000..23f486baf --- /dev/null +++ b/tests/unit/ingestion/test_python_resolver.py @@ -0,0 +1,52 @@ +import pytest +from repowise.core.ingestion.models import Import +from repowise.core.ingestion.resolvers.context import ResolverContext +from repowise.core.ingestion.resolvers.python import resolve_python_import, resolve_python_import_all + + +def test_resolve_python_import_all_submodules(): + path_set = { + "src/app.py", + "src/routers/__init__.py", + "src/routers/workspace.py", + "src/routers/git/__init__.py", + } + + ctx = ResolverContext(path_set=path_set, stem_map={}, graph=None) + + # 1. Single submodule without __init__.py package + imp = Import( + module_path="routers", + module_type="python", + imported_names=("workspace", "git", "overview"), + range_start=0, + range_end=0, + is_type_only=False, + ) + + targets = resolve_python_import_all(imp, "src/app.py", ctx) + assert "src/routers/__init__.py" in targets + assert "src/routers/workspace.py" in targets + assert "src/routers/git/__init__.py" in targets + assert len(targets) == 3 + + +def test_resolve_python_import_all_no_submodules(): + path_set = { + "src/app.py", + "src/routers/__init__.py", + } + + ctx = ResolverContext(path_set=path_set, stem_map={}, graph=None) + + imp = Import( + module_path="routers", + module_type="python", + imported_names=("missing",), + range_start=0, + range_end=0, + is_type_only=False, + ) + + targets = resolve_python_import_all(imp, "src/app.py", ctx) + assert targets == ("src/routers/__init__.py",)