Skip to content

Commit

Permalink
Load options from pyproject.toml files (#215).
Browse files Browse the repository at this point in the history
Co-authored-by: Jendrik Seipp <[email protected]>
  • Loading branch information
exhuma and jendrikseipp authored Aug 19, 2020
1 parent 54c952b commit d0062b6
Show file tree
Hide file tree
Showing 10 changed files with 421 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:

- name: Install Vulture from wheel (For Windows)
if: ${{ matrix.os == 'windows-2019' }}
run: "python -m pip install --only-binary=:all: --no-index --ignore-installed --find-links=dist/ vulture"
run: "python -m pip install --only-binary=:all: --ignore-installed --find-links=dist/ vulture"

- name: Run Vulture
run: |
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
`obj.constant_string` (jingw, #219).
* Fix false positives when assigning to `x.some_name` but reading via
`some_name`, at the cost of potential false negatives (jingw, #221).
* Allow reading options from `pyproject.toml` (Michel Albert, #164, #215).

# 2.0 (2020-08-11)

Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,25 @@ def foo(arg: Sequence):

if you're using Python 3.7+.


## Configuration

You can also store command line arguments in `pyproject.toml` under the
`tool.vulture` section. Simply remove leading dashes and replace all
remaining dashes with underscores. Example:

``` toml
[tool.vulture]
exclude = ["file*.py", "dir/"]
ignore_decorators = ["@app.route", "@require_*"]
ignore_names = ["visit_*", "do_*"]
make_whitelist = true
min_confidence = 80
sort_by_size = true
verbose = true
paths = ["myscript.py", "mydir"]
```

## How does it work?

Vulture uses the `ast` module to build abstract syntax trees for all
Expand Down
2 changes: 1 addition & 1 deletion dev/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ git pull
tox

# Bump version.
sed -i -e "s/__version__ = \".*\"/__version__ = \"$VERSION\"/" vulture/core.py
sed -i -e "s/__version__ = \".*\"/__version__ = \"$VERSION\"/" vulture/version.py
git commit -am "Update version number to ${VERSION} for release."
git tag -a "v$VERSION" -m "v$VERSION" HEAD

Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def find_version(*file_parts):

setuptools.setup(
name="vulture",
version=find_version("vulture", "core.py"),
version=find_version("vulture", "version.py"),
description="Find dead code",
long_description=long_description,
long_description_content_type="text/markdown",
Expand All @@ -51,6 +51,7 @@ def find_version(*file_parts):
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Quality Assurance",
],
install_requires=["toml"],
entry_points={"console_scripts": ["vulture = vulture.core:main"]},
python_requires=">=3.6",
packages=setuptools.find_packages(exclude=["tests"]),
Expand Down
178 changes: 178 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
"""
Unit tests for config file and CLI argument parsing.
"""

from io import StringIO
from textwrap import dedent

import pytest

from vulture.config import (
DEFAULTS,
_check_input_config,
_parse_args,
_parse_toml,
make_config,
)


def test_cli_args():
"""
Ensure that CLI arguments are converted to a config object.
"""
expected = dict(
paths=["path1", "path2"],
exclude=["file*.py", "dir/"],
ignore_decorators=["deco1", "deco2"],
ignore_names=["name1", "name2"],
make_whitelist=True,
min_confidence=10,
sort_by_size=True,
verbose=True,
)
result = _parse_args(
[
"--exclude=file*.py,dir/",
"--ignore-decorators=deco1,deco2",
"--ignore-names=name1,name2",
"--make-whitelist",
"--min-confidence=10",
"--sort-by-size",
"--verbose",
"path1",
"path2",
]
)
assert isinstance(result, dict)
assert result == expected


def test_toml_config():
"""
Ensure parsing of TOML files results in a valid config object.
"""
expected = dict(
paths=["path1", "path2"],
exclude=["file*.py", "dir/"],
ignore_decorators=["deco1", "deco2"],
ignore_names=["name1", "name2"],
make_whitelist=True,
min_confidence=10,
sort_by_size=True,
verbose=True,
)
data = StringIO(
dedent(
"""\
[tool.vulture]
exclude = ["file*.py", "dir/"]
ignore_decorators = ["deco1", "deco2"]
ignore_names = ["name1", "name2"]
make_whitelist = true
min_confidence = 10
sort_by_size = true
verbose = true
paths = ["path1", "path2"]
"""
)
)
result = _parse_toml(data)
assert isinstance(result, dict)
assert result == expected


def test_config_merging():
"""
If we have both CLI args and a ``pyproject.toml`` file, the CLI args should
have precedence.
"""
toml = StringIO(
dedent(
"""\
[tool.vulture]
exclude = ["toml_exclude"]
ignore_decorators = ["toml_deco"]
ignore_names = ["toml_name"]
make_whitelist = false
min_confidence = 10
sort_by_size = false
verbose = false
paths = ["toml_path"]
"""
)
)
cliargs = [
"--exclude=cli_exclude",
"--ignore-decorators=cli_deco",
"--ignore-names=cli_name",
"--make-whitelist",
"--min-confidence=20",
"--sort-by-size",
"--verbose",
"cli_path",
]
result = make_config(cliargs, toml)
expected = dict(
paths=["cli_path"],
exclude=["cli_exclude"],
ignore_decorators=["cli_deco"],
ignore_names=["cli_name"],
make_whitelist=True,
min_confidence=20,
sort_by_size=True,
verbose=True,
)
assert result == expected


def test_config_merging_missing():
"""
If we have set a boolean value in the TOML file, but not on the CLI, we
want the TOML value to be taken.
"""
toml = StringIO(
dedent(
"""\
[tool.vulture]
verbose = true
ignore_names = ["name1"]
"""
)
)
cliargs = [
"cli_path",
]
result = make_config(cliargs, toml)
assert result["verbose"] is True
assert result["ignore_names"] == ["name1"]


def test_invalid_config_options_output():
"""
If the config file contains unknown options we want to abort.
"""

with pytest.raises(SystemExit):
_check_input_config({"unknown_key_1": 1})


@pytest.mark.parametrize(
"key, value", list(DEFAULTS.items()),
)
def test_incompatible_option_type(key, value):
"""
If a config value has a different type from the default value we abort.
"""
wrong_types = {int, str, list, bool} - {type(value)}
for wrong_type in wrong_types:
test_value = wrong_type()
with pytest.raises(SystemExit):
_check_input_config({key: test_value})


def test_missing_paths():
"""
If the script is run without any paths, we want to abort.
"""
with pytest.raises(SystemExit):
make_config([])
3 changes: 2 additions & 1 deletion vulture/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from vulture.core import __version__, Vulture
from vulture.core import Vulture
from vulture.version import __version__

assert __version__
assert Vulture
Loading

0 comments on commit d0062b6

Please sign in to comment.