diff --git a/deptry/cli.py b/deptry/cli.py index 0b88c9b3..0a8a5fec 100644 --- a/deptry/cli.py +++ b/deptry/cli.py @@ -8,7 +8,7 @@ from deptry.compat import metadata from deptry.config import read_configuration_from_pyproject_toml from deptry.core import Core -from deptry.utils import PYPROJECT_TOML_PATH, run_within_dir +from deptry.utils import PYPROJECT_TOML_PATH class CommaSeparatedTupleParamType(click.ParamType): @@ -186,7 +186,6 @@ def display_deptry_version(ctx: click.Context, _param: click.Parameter, value: b help="Path to the pyproject.toml file to read configuration from.", default=PYPROJECT_TOML_PATH, expose_value=False, - hidden=True, ) def deptry( root: Path, @@ -212,20 +211,20 @@ def deptry( """ - with run_within_dir(root): - Core( - ignore_obsolete=ignore_obsolete, - ignore_missing=ignore_missing, - ignore_transitive=ignore_transitive, - ignore_misplaced_dev=ignore_misplaced_dev, - exclude=exclude, - extend_exclude=extend_exclude, - ignore_notebooks=ignore_notebooks, - skip_obsolete=skip_obsolete, - skip_missing=skip_missing, - skip_transitive=skip_transitive, - skip_misplaced_dev=skip_misplaced_dev, - requirements_txt=requirements_txt, - requirements_txt_dev=requirements_txt_dev, - json_output=json_output, - ).run() + Core( + root=root, + ignore_obsolete=ignore_obsolete, + ignore_missing=ignore_missing, + ignore_transitive=ignore_transitive, + ignore_misplaced_dev=ignore_misplaced_dev, + exclude=exclude, + extend_exclude=extend_exclude, + ignore_notebooks=ignore_notebooks, + skip_obsolete=skip_obsolete, + skip_missing=skip_missing, + skip_transitive=skip_transitive, + skip_misplaced_dev=skip_misplaced_dev, + requirements_txt=requirements_txt, + requirements_txt_dev=requirements_txt_dev, + json_output=json_output, + ).run() diff --git a/deptry/core.py b/deptry/core.py index 29e5c47d..f46f15a3 100644 --- a/deptry/core.py +++ b/deptry/core.py @@ -26,6 +26,7 @@ @dataclass class Core: + root: Path ignore_obsolete: tuple[str, ...] ignore_missing: tuple[str, ...] ignore_transitive: tuple[str, ...] @@ -49,7 +50,7 @@ def run(self) -> None: all_python_files = PythonFileFinder( exclude=self.exclude + self.extend_exclude, ignore_notebooks=self.ignore_notebooks - ).get_all_python_files_in(Path(".")) + ).get_all_python_files_in(self.root) local_modules = self._get_local_modules() @@ -96,10 +97,9 @@ def _get_dependencies(self, dependency_management_format: DependencyManagementFo return RequirementsTxtDependencyGetter(self.requirements_txt, self.requirements_txt_dev).get() raise ValueError("Incorrect dependency manage format. Only poetry, pdm and requirements.txt are supported.") - @staticmethod - def _get_local_modules() -> set[str]: - directories = [f for f in os.listdir() if Path(f).is_dir()] - return {subdir for subdir in directories if "__init__.py" in os.listdir(subdir)} + def _get_local_modules(self) -> set[str]: + directories = [f for f in os.scandir(self.root) if f.is_dir()] + return {subdirectory.name for subdirectory in directories if "__init__.py" in os.listdir(subdirectory)} def _log_config(self) -> None: logging.debug("Running with the following configuration:") diff --git a/deptry/utils.py b/deptry/utils.py index 4f6a699d..7f2227d5 100644 --- a/deptry/utils.py +++ b/deptry/utils.py @@ -2,9 +2,8 @@ import os import sys -from contextlib import contextmanager from pathlib import Path -from typing import Any, Generator +from typing import Any if sys.version_info >= (3, 11): import tomllib @@ -14,27 +13,6 @@ PYPROJECT_TOML_PATH = "./pyproject.toml" -@contextmanager -def run_within_dir(path: Path) -> Generator[None, None, None]: - """ - Utility function to run some code within a directory, and change back to the current directory afterwards. - - Example usage: - - ``` - with run_within_dir(directory): - some_code() - ``` - - """ - oldpwd = os.getcwd() - os.chdir(path) - try: - yield - finally: - os.chdir(oldpwd) - - def load_pyproject_toml(pyproject_toml_path: str = PYPROJECT_TOML_PATH) -> dict[str, Any]: try: with Path(pyproject_toml_path).open("rb") as pyproject_file: diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/cli/__init__.py b/tests/cli/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index 1d85a457..15e09634 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -7,7 +7,7 @@ import pytest from _pytest.tmpdir import TempPathFactory -from deptry.utils import run_within_dir +from tests.utils import run_within_dir @pytest.fixture(scope="session") diff --git a/tests/cli/test_cli_pdm.py b/tests/cli/test_cli_pdm.py index cc281913..8bc27d63 100644 --- a/tests/cli/test_cli_pdm.py +++ b/tests/cli/test_cli_pdm.py @@ -6,7 +6,7 @@ import pytest from _pytest.tmpdir import TempPathFactory -from deptry.utils import run_within_dir +from tests.utils import run_within_dir @pytest.fixture(scope="session") diff --git a/tests/cli/test_cli_pep_621.py b/tests/cli/test_cli_pep_621.py index 7798c874..957e4894 100644 --- a/tests/cli/test_cli_pep_621.py +++ b/tests/cli/test_cli_pep_621.py @@ -6,7 +6,7 @@ import pytest from _pytest.tmpdir import TempPathFactory -from deptry.utils import run_within_dir +from tests.utils import run_within_dir @pytest.fixture(scope="session") diff --git a/tests/cli/test_cli_requirements_txt.py b/tests/cli/test_cli_requirements_txt.py index 5de0dc73..7ed5c321 100644 --- a/tests/cli/test_cli_requirements_txt.py +++ b/tests/cli/test_cli_requirements_txt.py @@ -6,7 +6,7 @@ import pytest from _pytest.tmpdir import TempPathFactory -from deptry.utils import run_within_dir +from tests.utils import run_within_dir @pytest.fixture(scope="session") diff --git a/tests/cli/test_cli_src_directory.py b/tests/cli/test_cli_src_directory.py new file mode 100644 index 00000000..8570ea18 --- /dev/null +++ b/tests/cli/test_cli_src_directory.py @@ -0,0 +1,28 @@ +import shlex +import shutil +import subprocess +from pathlib import Path + +import pytest +from _pytest.tmpdir import TempPathFactory + +from tests.utils import run_within_dir + + +@pytest.fixture(scope="session") +def pep_621_dir_with_src_directory(tmp_path_factory: TempPathFactory) -> Path: + tmp_path_proj = tmp_path_factory.getbasetemp() / "project_with_src_directory" + shutil.copytree("tests/data/project_with_src_directory", str(tmp_path_proj)) + with run_within_dir(tmp_path_proj): + assert subprocess.check_call(shlex.split("pip install .")) == 0 + return tmp_path_proj + + +def test_cli_with_src_directory(pep_621_dir_with_src_directory: Path) -> None: + with run_within_dir(pep_621_dir_with_src_directory): + result = subprocess.run(shlex.split("deptry src"), capture_output=True, text=True) + assert result.returncode == 1 + assert ( + "The project contains obsolete dependencies:\n\n\tisort\n\tmypy\n\tpytest\n\trequests\n\n" in result.stderr + ) + assert "There are dependencies missing from the project's list of dependencies:\n\n\twhite\n\n" in result.stderr diff --git a/tests/data/project_with_src_directory/pyproject.toml b/tests/data/project_with_src_directory/pyproject.toml new file mode 100644 index 00000000..0685665b --- /dev/null +++ b/tests/data/project_with_src_directory/pyproject.toml @@ -0,0 +1,30 @@ +[project] +# PEP 621 project metadata +# See https://www.python.org/dev/peps/pep-0621/ +name = "foo" +version = "1.2.3" +requires-python = ">=3.7" +dependencies = [ + "toml", + "urllib3>=1.26.12", + "isort>=5.10.1", + "click>=8.1.3", + "requests>=2.28.1", + "pkginfo>=1.8.3", +] + +[project.optional-dependencies] +dev = [ + "black==22.10.0", + "mypy==0.982", +] +test = [ + "pytest==7.2.0", +] + +[build-system] +requires = ["setuptools>=61.0.0"] +build-backend = "setuptools.build_meta" + +[tool.deptry] +ignore_obsolete = ["pkginfo"] diff --git a/tests/data/project_with_src_directory/src/project_with_src_directory/__init__.py b/tests/data/project_with_src_directory/src/project_with_src_directory/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/project_with_src_directory/src/project_with_src_directory/bar.py b/tests/data/project_with_src_directory/src/project_with_src_directory/bar.py new file mode 100644 index 00000000..dbc1a7b7 --- /dev/null +++ b/tests/data/project_with_src_directory/src/project_with_src_directory/bar.py @@ -0,0 +1 @@ +from project_with_src_directory.foo import a_local_method diff --git a/tests/data/project_with_src_directory/src/project_with_src_directory/foo.py b/tests/data/project_with_src_directory/src/project_with_src_directory/foo.py new file mode 100644 index 00000000..92f88c21 --- /dev/null +++ b/tests/data/project_with_src_directory/src/project_with_src_directory/foo.py @@ -0,0 +1,11 @@ +from os import chdir, walk +from pathlib import Path + +import black +import click +import white as w +from urllib3 import contrib + + +def a_local_method(): + ... diff --git a/tests/data/project_with_src_directory/src/project_with_src_directory/notebook.ipynb b/tests/data/project_with_src_directory/src/project_with_src_directory/notebook.ipynb new file mode 100644 index 00000000..a51bdb9d --- /dev/null +++ b/tests/data/project_with_src_directory/src/project_with_src_directory/notebook.ipynb @@ -0,0 +1,37 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "id": "9f4924ec-2200-4801-9d49-d4833651cbc4", + "metadata": {}, + "outputs": [], + "source": [ + "import click\n", + "from urllib3 import contrib\n", + "import toml" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/dependency_getter/test_pdm.py b/tests/dependency_getter/test_pdm.py index 6e643c60..56b2d330 100644 --- a/tests/dependency_getter/test_pdm.py +++ b/tests/dependency_getter/test_pdm.py @@ -1,7 +1,7 @@ from pathlib import Path from deptry.dependency_getter.pdm import PDMDependencyGetter -from deptry.utils import run_within_dir +from tests.utils import run_within_dir def test_dependency_getter(tmp_path: Path) -> None: diff --git a/tests/dependency_getter/test_pep_621.py b/tests/dependency_getter/test_pep_621.py index b5a05a0e..93a6b5a4 100644 --- a/tests/dependency_getter/test_pep_621.py +++ b/tests/dependency_getter/test_pep_621.py @@ -1,7 +1,7 @@ from pathlib import Path from deptry.dependency_getter.pep_621 import PEP621DependencyGetter -from deptry.utils import run_within_dir +from tests.utils import run_within_dir def test_dependency_getter(tmp_path: Path) -> None: diff --git a/tests/dependency_getter/test_poetry.py b/tests/dependency_getter/test_poetry.py index 545de808..ce91446c 100644 --- a/tests/dependency_getter/test_poetry.py +++ b/tests/dependency_getter/test_poetry.py @@ -1,7 +1,7 @@ from pathlib import Path from deptry.dependency_getter.poetry import PoetryDependencyGetter -from deptry.utils import run_within_dir +from tests.utils import run_within_dir def test_dependency_getter(tmp_path: Path) -> None: diff --git a/tests/dependency_getter/test_requirements_txt.py b/tests/dependency_getter/test_requirements_txt.py index 9a0c78ac..7019fd73 100644 --- a/tests/dependency_getter/test_requirements_txt.py +++ b/tests/dependency_getter/test_requirements_txt.py @@ -1,7 +1,7 @@ from pathlib import Path from deptry.dependency_getter.requirements_txt import RequirementsTxtDependencyGetter -from deptry.utils import run_within_dir +from tests.utils import run_within_dir def test_parse_requirements_txt(tmp_path: Path) -> None: diff --git a/tests/imports/test_extract.py b/tests/imports/test_extract.py index f4c0e638..164f5652 100644 --- a/tests/imports/test_extract.py +++ b/tests/imports/test_extract.py @@ -9,7 +9,7 @@ from _pytest.logging import LogCaptureFixture from deptry.imports.extract import get_imported_modules_for_list_of_files, get_imported_modules_from_file -from deptry.utils import run_within_dir +from tests.utils import run_within_dir def test_import_parser_py() -> None: diff --git a/tests/test_config.py b/tests/test_config.py index fd791660..b7a6e528 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -5,7 +5,7 @@ from _pytest.logging import LogCaptureFixture from deptry.config import read_configuration_from_pyproject_toml -from deptry.utils import run_within_dir +from tests.utils import run_within_dir def test_read_configuration_from_pyproject_toml_exists(tmp_path: Path) -> None: diff --git a/tests/test_dependency_specification_detector.py b/tests/test_dependency_specification_detector.py index d07a2331..3c262fea 100644 --- a/tests/test_dependency_specification_detector.py +++ b/tests/test_dependency_specification_detector.py @@ -4,7 +4,7 @@ import pytest from deptry.dependency_specification_detector import DependencyManagementFormat, DependencySpecificationDetector -from deptry.utils import run_within_dir +from tests.utils import run_within_dir def test_poetry(tmp_path: Path) -> None: diff --git a/tests/test_json_writer.py b/tests/test_json_writer.py index 044f78b4..05c1c6f5 100644 --- a/tests/test_json_writer.py +++ b/tests/test_json_writer.py @@ -2,7 +2,7 @@ from pathlib import Path from deptry.json_writer import JsonWriter -from deptry.utils import run_within_dir +from tests.utils import run_within_dir def test_simple(tmp_path: Path) -> None: diff --git a/tests/test_python_file_finder.py b/tests/test_python_file_finder.py index 3aba0488..83ade200 100644 --- a/tests/test_python_file_finder.py +++ b/tests/test_python_file_finder.py @@ -3,7 +3,7 @@ from pathlib import Path from deptry.python_file_finder import PythonFileFinder -from deptry.utils import run_within_dir +from tests.utils import run_within_dir def create_files_from_list_of_dicts(paths: list[dict[str, str]]) -> None: diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 00000000..f27f6cf2 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,25 @@ +import os +from contextlib import contextmanager +from pathlib import Path +from typing import Generator + + +@contextmanager +def run_within_dir(path: Path) -> Generator[None, None, None]: + """ + Utility function to run some code within a directory, and change back to the current directory afterwards. + + Example usage: + + ``` + with run_within_dir(directory): + some_code() + ``` + + """ + oldpwd = os.getcwd() + os.chdir(path) + try: + yield + finally: + os.chdir(oldpwd)