Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(config): detect invalid pyproject.toml options #571

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion deptry/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging
from typing import TYPE_CHECKING, Any

from deptry.exceptions import InvalidPyprojectTOMLOptionsError
from deptry.utils import load_pyproject_toml

if TYPE_CHECKING:
Expand All @@ -11,6 +12,13 @@
import click


def _get_invalid_pyproject_toml_keys(ctx: click.Context, deptry_toml_config_keys: set[str]) -> list[str]:
"""Returns the list of options set in `pyproject.toml` that do not exist as CLI parameters."""
existing_cli_params = {param.name for param in ctx.command.params}

return sorted(deptry_toml_config_keys.difference(existing_cli_params))


def read_configuration_from_pyproject_toml(ctx: click.Context, _param: click.Parameter, value: Path) -> Path | None:
"""
Callback that, given a click context, overrides the default values with configuration options set in a
Expand All @@ -28,11 +36,15 @@ def read_configuration_from_pyproject_toml(ctx: click.Context, _param: click.Par
return value

try:
deptry_toml_config = pyproject_data["tool"]["deptry"]
deptry_toml_config: dict[str, Any] = pyproject_data["tool"]["deptry"]
except KeyError:
logging.debug("No configuration for deptry was found in pyproject.toml.")
return value

invalid_pyproject_toml_keys = _get_invalid_pyproject_toml_keys(ctx, set(deptry_toml_config))
if invalid_pyproject_toml_keys:
raise InvalidPyprojectTOMLOptionsError(invalid_pyproject_toml_keys)

click_default_map: dict[str, Any] = {}

if ctx.default_map:
Expand Down
9 changes: 9 additions & 0 deletions deptry/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from typing import TYPE_CHECKING

from click import UsageError

if TYPE_CHECKING:
from pathlib import Path

Expand Down Expand Up @@ -29,3 +31,10 @@ def __init__(self, version: tuple[int, int]) -> None:
super().__init__(
f"Python version {version[0]}.{version[1]} is not supported. Only versions >= 3.8 are supported."
)


class InvalidPyprojectTOMLOptionsError(UsageError):
def __init__(self, invalid_options: list[str]) -> None:
super().__init__(
f"'[tool.deptry]' section in 'pyproject.toml' contains invalid configuration options: {invalid_options}."
)
46 changes: 43 additions & 3 deletions tests/unit/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,34 @@
from typing import TYPE_CHECKING

import click
import pytest
from click import Argument

from deptry.config import read_configuration_from_pyproject_toml
from deptry.exceptions import InvalidPyprojectTOMLOptionsError
from tests.utils import run_within_dir

if TYPE_CHECKING:
from _pytest.logging import LogCaptureFixture


click_command = click.Command(
"",
params=[
Argument(param_decls=["exclude"]),
Argument(param_decls=["extend_exclude"]),
Argument(param_decls=["per_rule_ignores"]),
Argument(param_decls=["ignore"]),
Argument(param_decls=["ignore_notebooks"]),
Argument(param_decls=["requirements_txt"]),
Argument(param_decls=["requirements_txt_dev"]),
],
)


def test_read_configuration_from_pyproject_toml_exists(tmp_path: Path) -> None:
click_context = click.Context(
click.Command(""),
click_command,
default_map={
"exclude": ["bar"],
"extend_exclude": ["foo"],
Expand Down Expand Up @@ -77,7 +94,7 @@ def test_read_configuration_from_pyproject_toml_file_not_found(caplog: LogCaptur
with caplog.at_level(logging.DEBUG):
assert (
read_configuration_from_pyproject_toml(
click.Context(click.Command("")), click.UNPROCESSED(None), pyproject_toml_path
click.Context(click_command), click.UNPROCESSED(None), pyproject_toml_path
)
== pyproject_toml_path
)
Expand All @@ -101,7 +118,30 @@ def test_read_configuration_from_pyproject_toml_file_without_deptry_section(

with caplog.at_level(logging.DEBUG):
assert read_configuration_from_pyproject_toml(
click.Context(click.Command("")), click.UNPROCESSED(None), pyproject_toml_path
click.Context(click_command), click.UNPROCESSED(None), pyproject_toml_path
) == Path("pyproject.toml")

assert "No configuration for deptry was found in pyproject.toml." in caplog.text


def test_read_configuration_from_pyproject_toml_file_with_invalid_options(
caplog: LogCaptureFixture, tmp_path: Path
) -> None:
pyproject_toml_content = """
[tool.deptry]
exclude = ["foo", "bar"]
invalid_option = "nope"
another_invalid_option = "still nope"
extend_exclude = ["bar", "foo"]
"""

with run_within_dir(tmp_path):
pyproject_toml_path = Path("pyproject.toml")

with pyproject_toml_path.open("w") as f:
f.write(pyproject_toml_content)

with pytest.raises(InvalidPyprojectTOMLOptionsError):
assert read_configuration_from_pyproject_toml(
click.Context(click_command), click.UNPROCESSED(None), pyproject_toml_path
) == Path("pyproject.toml")
Loading