From f7cc6ca953b31c7f1dc13652da9e3bb5c30c585a Mon Sep 17 00:00:00 2001 From: Mathieu Kniewallner Date: Sun, 29 Jan 2023 15:42:30 +0100 Subject: [PATCH] feat(core): handle local modules without `__init__.py` --- deptry/core.py | 21 ++++++++++++++----- tests/cli/test_cli_src_directory.py | 2 +- .../project_with_src_directory/pyproject.toml | 2 +- .../project_with_src_directory/src/foobar.py | 5 +++++ .../src/project_with_src_directory/bar.py | 1 + tests/test_core.py | 14 +++++-------- 6 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 tests/data/project_with_src_directory/src/foobar.py diff --git a/deptry/core.py b/deptry/core.py index 8b4ec6af..346581f7 100644 --- a/deptry/core.py +++ b/deptry/core.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -import os import sys from dataclasses import dataclass from typing import TYPE_CHECKING @@ -115,13 +114,25 @@ def _get_dependencies(self, dependency_management_format: DependencyManagementFo raise IncorrectDependencyFormatError def _get_local_modules(self) -> set[str]: - directories = [f for f in os.scandir(self.root) if f.is_dir()] - guessed_local_modules = { - subdirectory.name for subdirectory in directories if "__init__.py" in os.listdir(subdirectory) - } + """ + Get all local Python modules from the root directory and `known_first_party` list. + A module is considered a local Python module if it matches at least one of those conditions: + - it is a directory that contains at least one Python file + - it is a Python file that is not named `__init__.py` (since it is a special case) + - it is set in the `known_first_party` list + """ + guessed_local_modules = {path.stem for path in self.root.iterdir() if self._is_local_module(path)} return guessed_local_modules | set(self.known_first_party) + @staticmethod + def _is_local_module(path: Path) -> bool: + """Guess if a module is a local Python module.""" + return bool( + (path.is_file() and path.name != "__init__.py" and path.suffix == ".py") + or (path.is_dir() and list(path.glob("*.py"))) + ) + @staticmethod def _get_stdlib_modules() -> frozenset[str]: if sys.version_info[:2] >= (3, 10): diff --git a/tests/cli/test_cli_src_directory.py b/tests/cli/test_cli_src_directory.py index 36b26bf7..25765dc6 100644 --- a/tests/cli/test_cli_src_directory.py +++ b/tests/cli/test_cli_src_directory.py @@ -31,7 +31,7 @@ def test_cli_with_src_directory(pep_621_dir_with_src_directory: Path) -> None: assert result.returncode == 1 assert get_issues_report() == { "misplaced_dev": [], - "missing": ["white"], + "missing": ["httpx", "white"], "obsolete": ["isort", "requests", "mypy", "pytest"], "transitive": [], } diff --git a/tests/data/project_with_src_directory/pyproject.toml b/tests/data/project_with_src_directory/pyproject.toml index 0685665b..5afc2036 100644 --- a/tests/data/project_with_src_directory/pyproject.toml +++ b/tests/data/project_with_src_directory/pyproject.toml @@ -19,7 +19,7 @@ dev = [ "mypy==0.982", ] test = [ - "pytest==7.2.0", + "pytest==7.2.0", ] [build-system] diff --git a/tests/data/project_with_src_directory/src/foobar.py b/tests/data/project_with_src_directory/src/foobar.py new file mode 100644 index 00000000..db8d6883 --- /dev/null +++ b/tests/data/project_with_src_directory/src/foobar.py @@ -0,0 +1,5 @@ +import httpx + + +def another_local_method(): + ... 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 index dbc1a7b7..ac3bc531 100644 --- 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 @@ -1 +1,2 @@ from project_with_src_directory.foo import a_local_method +from foobar import another_local_method diff --git a/tests/test_core.py b/tests/test_core.py index e4cfe947..91b2a730 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -19,22 +19,17 @@ ( (), "", - {"module_with_init"}, - ), - ( - ("module_without_init",), - "", - {"module_with_init", "module_without_init"}, + {"module_with_init", "module_without_init", "local_file"}, ), ( ("module_with_init", "module_without_init"), "", - {"module_with_init", "module_without_init"}, + {"module_with_init", "module_without_init", "local_file"}, ), ( ("module_without_init",), "module_with_init", - {"module_without_init", "subdirectory"}, + {"foo", "module_without_init", "subdirectory"}, ), ], ) @@ -47,7 +42,8 @@ def test__get_local_modules( {"dir": "module_with_init", "file": "foo.py"}, {"dir": "module_with_init/subdirectory", "file": "__init__.py"}, {"dir": "module_with_init/subdirectory", "file": "foo.py"}, - {"dir": "module_without_init", "file": "foo.py"}, + {"dir": "module_without_init", "file": "bar.py"}, + {"dir": ".", "file": "local_file.py"}, ] create_files_from_list_of_dicts(paths)