from __future__ import annotations import logging import sys from pathlib import Path from unittest import mock import pytest from deptry.core import Core from deptry.dependency import Dependency from deptry.dependency_getter.base import DependenciesExtract from deptry.imports.location import Location from deptry.module import Module from deptry.violations import ( DEP001MissingDependencyViolation, DEP002UnusedDependencyViolation, DEP003TransitiveDependencyViolation, DEP004MisplacedDevDependencyViolation, ) from tests.utils import create_files, run_within_dir @pytest.mark.parametrize( ("known_first_party", "root_suffix", "experimental_namespace_package", "expected"), [ ( (), "", False, { "local_file", "module_with_init", "module_without_init", }, ), ( ("module_with_init", "module_without_init"), "", False, { "local_file", "module_with_init", "module_without_init", }, ), ( ("module_without_init",), "module_with_init", False, { "foo", "module_without_init", "subdirectory", }, ), ( (), "", True, { "local_file", "module_using_namespace", "module_with_init", "module_without_init", }, ), ( ("module_with_init", "module_without_init"), "", True, { "local_file", "module_using_namespace", "module_with_init", "module_without_init", }, ), ( ("module_without_init",), "module_with_init", True, { "foo", "module_without_init", "subdirectory", }, ), ], ) def test__get_local_modules( tmp_path: Path, known_first_party: tuple[str, ...], root_suffix: str, experimental_namespace_package: bool, expected: set[str], ) -> None: with run_within_dir(tmp_path): create_files([ Path("directory_without_python_files/foo.txt"), Path("module_using_namespace/subdirectory_namespace/foo.py"), Path("module_with_init/__init__.py"), Path("module_with_init/foo.py"), Path("module_with_init/subdirectory/__init__.py"), Path("module_with_init/subdirectory/foo.py"), Path("module_without_init/bar.py"), Path("local_file.py"), ]) assert ( Core( root=(tmp_path / root_suffix,), config=Path("pyproject.toml"), no_ansi=False, per_rule_ignores={}, ignore=(), exclude=(), extend_exclude=(), using_default_exclude=True, ignore_notebooks=False, requirements_files=(), requirements_files_dev=(), known_first_party=known_first_party, json_output="", package_module_name_map={}, pep621_dev_dependency_groups=(), using_default_requirements_files=True, experimental_namespace_package=experimental_namespace_package, github_output=False, github_warning_errors=(), )._get_local_modules() == expected ) def test__get_stdlib_packages_with_stdlib_module_names() -> None: assert Core._get_standard_library_modules() == sys.stdlib_module_names @pytest.mark.parametrize( "version_info", [ (sys.version_info[0], sys.version_info[1] + 1, 0), (sys.version_info[0], sys.version_info[1] + 1, 13), (sys.version_info[0] + 1, sys.version_info[1], 0), (sys.version_info[0], sys.version_info[1] + 1, 0, "beta", 1), (sys.version_info[0], sys.version_info[1] + 1, 0, "candidate", 1), ], ) def test__get_stdlib_packages_with_stdlib_module_names_future_version(version_info: tuple[int | str, ...]) -> None: """Test that future versions of Python not yet tested on the CI will also work.""" with mock.patch("sys.version_info", (sys.version_info[0], sys.version_info[1] + 1, 0)): assert Core._get_standard_library_modules() == sys.stdlib_module_names def test__exit_with_violations() -> None: violations = [ DEP001MissingDependencyViolation(Module("foo"), Location(Path("foo.py"), 1, 2)), DEP002UnusedDependencyViolation(Dependency("foo", Path("pyproject.toml")), Location(Path("pyproject.toml"))), DEP003TransitiveDependencyViolation(Module("foo"), Location(Path("foo.py"), 1, 2)), DEP004MisplacedDevDependencyViolation(Module("foo"), Location(Path("foo.py"), 1, 2)), ] with pytest.raises(SystemExit) as e: Core._exit(violations) assert e.type is SystemExit assert e.value.code == 1 def test__exit_without_violations() -> None: with pytest.raises(SystemExit) as e: Core._exit([]) assert e.type is SystemExit assert e.value.code == 0 @pytest.mark.parametrize( ("dependencies", "dev_dependencies", "expected_logs"), [ ( [], [], [], ), ( [ Dependency("foo", Path("pyproject.toml")), Dependency("bar", Path("pyproject.toml")), ], [ Dependency("dev", Path("pyproject.toml")), Dependency("another-dev", Path("pyproject.toml")), ], [ "The project contains the following dependencies:", "Dependency 'foo' with top-levels: {'foo'}.", "Dependency 'bar' with top-levels: {'bar'}.", "", "The project contains the following dev dependencies:", "Dependency 'dev' with top-levels: {'dev'}.", "Dependency 'another-dev' with top-levels: {'another_dev'}.", "", ], ), ], ) def test__log_dependencies( dependencies: list[Dependency], dev_dependencies: list[Dependency], expected_logs: list[str], caplog: pytest.LogCaptureFixture, ) -> None: with caplog.at_level(logging.DEBUG): Core._log_dependencies(DependenciesExtract(dependencies, dev_dependencies)) assert caplog.messages == expected_logs