diff --git a/.gitignore b/.gitignore index df191195..0ec272f6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ vulture.egg-info/ .pytest_cache/ .tox/ .venv/ +.vscode/ diff --git a/CHANGELOG.md b/CHANGELOG.md index c7938149..660b0d07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# next (unreleased) +* Switch to tomllib/tomli to support heterogeneous arrays (Sebastian Csar, #340). + # 2.10 (2023-10-06) * Drop support for Python 3.7 (Jendrik Seipp, #323). diff --git a/setup.py b/setup.py index 3914be97..48549415 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ def find_version(*parts): "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Quality Assurance", ], - install_requires=["toml"], + install_requires=["tomli >= 1.1.0; python_version < '3.11'"], entry_points={"console_scripts": ["vulture = vulture.core:main"]}, python_requires=">=3.8", packages=setuptools.find_packages(exclude=["tests"]), diff --git a/tests/test_config.py b/tests/test_config.py index 494433de..04ce7259 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,7 +2,7 @@ Unit tests for config file and CLI argument parsing. """ -from io import StringIO +from io import BytesIO from textwrap import dedent import pytest @@ -17,6 +17,13 @@ ) +def get_toml_bytes(toml_str: str) -> BytesIO: + """ + Wrap a string in BytesIO to play the role of the incoming config stream. + """ + return BytesIO(bytes(toml_str, "utf-8")) + + def test_cli_args(): """ Ensure that CLI arguments are converted to a config object. @@ -62,9 +69,48 @@ def test_toml_config(): sort_by_size=True, verbose=True, ) - data = StringIO( + data = get_toml_bytes( + 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_toml_config_with_heterogenous_array(): + """ + Ensure parsing of TOML files results in a valid config object, even if some + other part of the file contains an array of mixed types. + """ + 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 = get_toml_bytes( dedent( """\ + [tool.foo] + # comment for good measure + problem_array = [{a = 1}, [2,3,4], "foo"] + [tool.vulture] exclude = ["file*.py", "dir/"] ignore_decorators = ["deco1", "deco2"] @@ -87,7 +133,7 @@ def test_config_merging(): If we have both CLI args and a ``pyproject.toml`` file, the CLI args should have precedence. """ - toml = StringIO( + toml = get_toml_bytes( dedent( """\ [tool.vulture] @@ -131,7 +177,7 @@ 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( + toml = get_toml_bytes( dedent( """\ [tool.vulture] @@ -153,7 +199,7 @@ def test_config_merging_toml_paths_only(): If we have paths in the TOML but not on the CLI, the TOML paths should be used. """ - toml = StringIO( + toml = get_toml_bytes( dedent( """\ [tool.vulture] diff --git a/vulture/config.py b/vulture/config.py index 4aa0d2d5..4e193fe2 100644 --- a/vulture/config.py +++ b/vulture/config.py @@ -5,7 +5,10 @@ import argparse import pathlib -import toml +try: + import tomllib +except ModuleNotFoundError: + import tomli as tomllib from .version import __version__ @@ -76,7 +79,7 @@ def _parse_toml(infile): verbose = true paths = ["path1", "path2"] """ - data = toml.load(infile) + data = tomllib.load(infile) settings = data.get("tool", {}).get("vulture", {}) _check_input_config(settings) return settings @@ -194,7 +197,7 @@ def make_config(argv=None, tomlfile=None): else: toml_path = pathlib.Path("pyproject.toml").resolve() if toml_path.is_file(): - with open(toml_path) as fconfig: + with open(toml_path, "rb") as fconfig: config = _parse_toml(fconfig) detected_toml_path = str(toml_path) else: