Skip to content

fix: honor pyproject-declared source roots in Python import resolution#623

Open
jkri-ch wants to merge 1 commit into
peteromallet:mainfrom
jkri-ch:fix/python-source-root-import-resolution
Open

fix: honor pyproject-declared source roots in Python import resolution#623
jkri-ch wants to merge 1 commit into
peteromallet:mainfrom
jkri-ch:fix/python-source-root-import-resolution

Conversation

@jkri-ch

@jkri-ch jkri-ch commented Jun 11, 2026

Copy link
Copy Markdown

Problem

Projects that keep importable code in a subdirectory of the repo root (e.g. scripts/ run with PYTHONPATH=scripts, declared via [tool.pytest.ini_options] pythonpath = ["scripts"]) get completely broken import resolution:

  • resolve_absolute_import only tries the scan root and the project root, so import riot_hydration never resolves to scripts/riot_hydration/__init__.py — the dependency graph reports 0 importers for every module.
  • The Python test-coverage import-spec mapper (languages/python/test_coverage.py::resolve_import_spec) only tries root-relative and src/-prefixed candidates, so every from mypkg.store import ... in a test file fails to map — every production module is flagged "Untested module (N LOC, 0 importers)" even when it has a dedicated multi-thousand-line test file.

Observed on a real ~115k-LOC project (desloppify scan --path .): 205 open test_coverage findings, ~80% of which were false positives, dragging Test health to 37% — independently adjudicated as detector noise by two blind review rounds.

Fix

New desloppify/languages/python/source_roots.py with a cached declared_source_roots(project_root) that reads source roots from pyproject.toml:

  • [tool.desloppify] python_source_roots (explicit override)
  • [tool.pytest.ini_options] pythonpath
  • [tool.mypy] mypy_path

(absolute paths, .. traversal, and . are dropped; duplicates removed)

  • resolve_absolute_import now tries scan root → project root → declared roots (existing order preserved).
  • resolve_import_spec extends its layout-prefix list (src/) with "<root>/" for each declared root.

Verification

  • 11 new tests in desloppify/languages/python/tests/test_py_source_roots.py (declaration parsing, resolution precedence, spec mapping, src/ regression).
  • Full suite: no new failures vs a clean checkout (the handful of failing tests on main — ruby phases, bash unused-imports, review-prepare dry-run — fail identically before and after this change).
  • Against the original project: declared roots: ('scripts',); riot_hydration.store resolves to scripts/riot_hydration/store.py; test-spec mapping for tests/unit/riot_hydration/test_riot_hydration_store.pyscripts/riot_hydration/store.py.

🤖 Generated with Claude Code

Projects that keep importable code in a subdirectory of the repo root
(e.g. scripts/ run with PYTHONPATH=scripts) previously had every absolute
import fail to resolve: the dependency graph reported 0 importers for
every module and the test-coverage mapper flagged fully-tested modules as
'Untested module (N LOC, 0 importers)' across the board.

resolve_absolute_import and the test-coverage import-spec mapper now also
try source roots declared in pyproject.toml:
- [tool.desloppify] python_source_roots (explicit override)
- [tool.pytest.ini_options] pythonpath
- [tool.mypy] mypy_path

Existing resolution order is preserved (scan root, then project root,
then declared roots); src/ layout support is unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant