Skip to content

Commit

Permalink
feat(config): detect invalid pyproject.toml options
Browse files Browse the repository at this point in the history
  • Loading branch information
mkniewallner committed Mar 9, 2024
1 parent 503d884 commit 21d27dd
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 4 deletions.
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")

0 comments on commit 21d27dd

Please sign in to comment.