Skip to content

Read the vulture configuration from pyproject.toml - #836

Open
Eljees wants to merge 5 commits into
prospector-dev:masterfrom
Eljees:fix/vulture-pyproject-config
Open

Read the vulture configuration from pyproject.toml#836
Eljees wants to merge 5 commits into
prospector-dev:masterfrom
Eljees:fix/vulture-pyproject-config

Conversation

@Eljees

@Eljees Eljees commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #505 (and covers what #398 asks for).

VultureTool.configure() only read the disabled message codes; the [tool.vulture] section
of pyproject.toml was never looked at, so ignore_names / ignore_decorators were silently
dropped and prospector reported dead code that plain vulture does not:

# pyproject.toml
[tool.vulture]
ignore_decorators = ["@app.route"]
import app


@app.route
def handler():
    return 1
$ vulture code.py            # no output
$ prospector                 # vulture: Unused function 'handler'

The configuration is now read with vulture's own vulture.config.make_config(), so the exact
same parsing rules apply, and the file is reported in the External Config: header like the
pylint/pycodestyle tools already do. It is only read when external config is enabled, so
--no-external-config still ignores it. A malformed [tool.vulture] section is reported as a
config problem instead of being swallowed.

Added tests/tools/vulture/test_vulture_config.py with the case above plus a control test that
the same code is still reported when the configuration does not ignore it.

@Pierre-Sassoulas Pierre-Sassoulas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on prospector. Shouldn't we rather use / pass as argument the whole vulture config and not those two values only ?

@Eljees

Eljees commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Happy to - but passing the whole config through isn't quite a drop-in, so let me lay out what's actually in it and you can tell me which shape you want.

make_config() returns the 8 keys in vulture.config.DEFAULTS, and Vulture.__init__ only takes three of them (verbose, ignore_names, ignore_decorators, core.py L193). The rest are consumed by vulture's own main(): scavenge(paths, exclude=...) and report(min_confidence=..., sort_by_size=..., make_whitelist=...).

Of those remaining five, four collide with prospector rather than extend it: paths and exclude are handled by ProspectorVulture.scavenge(), which is overridden to walk FileFinder instead - honouring them would mean two path selectors disagreeing. verbose writes to sys.stdout, which prospector has replaced for the duration of the run. sort_by_size and make_whitelist only affect vulture's own report formatting; prospector builds Message objects and sorts them itself.

min_confidence is the one you're right about: it's a real user-facing setting that prospector currently drops on the floor, because get_messages() reads unused_funcs/unused_props/unused_vars/unused_attrs directly instead of going through get_unused_code(). I'll add it - a confidence >= min_confidence filter in get_messages(), with a test.

On the plumbing itself, either works for me:

  1. keep the config dict on the tool (self._config = config) and read keys from it at each use site, or
  2. keep explicit attributes, adding min_confidence to the two that are there.

(1) is less to change when vulture grows an option; (2) makes it obvious at a glance which options prospector actually honours, which matters here because four of them are deliberately ignored. I slightly prefer (2) plus a comment naming the ignored keys and why, but I'll do (1) if you'd rather. Which do you want?

The previous test asserted on the configuration the tool had parsed. This
one runs the tool against a fixture project that carries a real
[tool.vulture] section, so the assertion is on prospector's own output --
the thing issue prospector-dev#505 is actually about.

It also widens the case: ignore_names as well as ignore_decorators, with a
genuinely dead function left in the fixture as a control, so the test fails
if the section is dropped and equally if it is applied too broadly.

Signed-off-by: Eljees <3.14hell@gmail.com>
@Eljees

Eljees commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

I've replaced the test with a fixture project that runs the tool end to end (tests/tools/vulture/pyproject_config/), so the assertion is on prospector's own output rather than on the configuration it parsed. It covers ignore_names as well as ignore_decorators, and leaves one genuinely dead function in the fixture as a control, so it fails both if the section is dropped and if it is applied too broadly. On master it fails with read_items still reported; on this branch it passes.

Apologies for the noise of #838 — I opened it as a second attempt at the same issue instead of pushing here, and have closed it.

Still on your side: whether prospector should pass vulture's whole config rather than these two keys. My reading of what that involves is in the comment above — short version is that make_config() returns eight keys, Vulture.__init__ takes three, and the rest are consumed by vulture's own main(), so honouring exclude and min_confidence means deciding how they compose with prospector's file finding and message filtering. Happy to implement either shape.

The fixture project added in the previous commit is deliberately written the
way a small app is written -- unannotated defs, decorator arguments the
decorator does not use -- because that is what vulture has to see. Running
prospector's own veryhigh profile over it therefore reports 8 messages and
fails both the pre-commit hook and the "Run Prospector checks" step, which
runs before pytest, so the test suite never executes.

Every other tool fixture in the tree is exempted the same way, including the
sibling tests/tools/*/testpath/*, which contains the identical unannotated
defs. This adds the one missing pattern; nothing else changes.

The test is unaffected: it builds ProspectorConfig(workdir=<fixture dir>) and
prospector only looks for a profile directly in workdir
(prospector/config/__init__.py), so the repository profile is never loaded
there.

Signed-off-by: Eljees <3.14hell@gmail.com>
@Eljees

Eljees commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

The five failing jobs were prospector linting its own new test fixture: the fixture is deliberately unannotated, with a decorator argument the decorator does not use, because that is what vulture has to see. The "Run Prospector checks" step runs before pytest, so the suite never got to run either.

83cbf4b adds that fixture directory to ignore-patterns, the same way tests/tools/.*/testpath/.* and the pylint fixtures are already exempted. The test itself is unaffected - it builds ProspectorConfig(workdir=<fixture dir>), and a profile is only looked for directly in workdir, so the repository profile is never loaded there.

The workflows for that commit are currently action_required, so the checks shown above are still from the previous head.

Eljees added 2 commits August 13, 2026 11:44
Signed-off-by: Eljees <3.14hell@gmail.com>
Signed-off-by: Eljees <3.14hell@gmail.com>
@Eljees

Eljees commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Implemented the whole-config plumbing in ad20945: ProspectorVulture receives the vulture config dictionary, passes the constructor settings through, and applies min_confidence while producing Prospector messages. Paths remain governed by Prospector FileFinder, while vulture-only output settings remain irrelevant to Prospector messages. c16e640 adds a regression test proving that min_confidence is honored. The fixture is now typed, fixing the original pre-commit failure; the three targeted tests, Ruff, and mypy pass.

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.

Vulture configuration in pyproject.toml ignored

2 participants