diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 0e15df91..450f077e 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,6 +1,6 @@ - id: deptry name: deptry - description: deptry is a command line tool to check for issues with dependencies in a Python project, such as obsolete or missing dependencies. + description: deptry is a command line tool to check for issues with dependencies in a Python project, such as unused or missing dependencies. entry: deptry . language: system always_run: true diff --git a/Makefile b/Makefile index 94b7bc69..8308955f 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ check: ## Run code quality tools. @poetry run pre-commit run -a @echo "🚀 Static type checking: Running mypy" @poetry run mypy - @echo "🚀 Checking for obsolete dependencies: Running deptry" + @echo "🚀 Checking for dependency issues: Running deptry" @poetry run deptry . .PHONY: test diff --git a/README.md b/README.md index d34b1afc..62fad84d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![PyPI - Downloads](https://img.shields.io/pypi/dm/deptry)](https://pypistats.org/packages/deptry) [![License](https://img.shields.io/github/license/fpgmaas/deptry)](https://img.shields.io/github/license/fpgmaas/deptry) -_deptry_ is a command line tool to check for issues with dependencies in a Python project, such as obsolete or missing dependencies. It supports the following types of projects: +_deptry_ is a command line tool to check for issues with dependencies in a Python project, such as unused or missing dependencies. It supports the following types of projects: - Projects that use [Poetry](https://python-poetry.org/) and a corresponding _pyproject.toml_ file - Projects that use [PDM](https://pdm.fming.dev/latest/) and a corresponding _pyproject.toml_ file diff --git a/deptry/cli.py b/deptry/cli.py index f8dd35eb..91b8bcab 100644 --- a/deptry/cli.py +++ b/deptry/cli.py @@ -11,6 +11,7 @@ from deptry.config import read_configuration_from_pyproject_toml from deptry.core import Core +from deptry.deprecate_obsolete import get_value_for_ignore_unused, get_value_for_skip_unused if TYPE_CHECKING: from collections.abc import Mapping, Sequence @@ -130,10 +131,11 @@ def display_deptry_version(ctx: click.Context, _param: click.Parameter, value: b is_flag=True, help="Disable ANSI characters in terminal output.", ) +@click.option("--skip-obsolete", is_flag=True, hidden=True) @click.option( - "--skip-obsolete", + "--skip-unused", is_flag=True, - help="Boolean flag to specify if deptry should skip scanning the project for obsolete dependencies.", + help="Boolean flag to specify if deptry should skip scanning the project for unused dependencies.", ) @click.option( "--skip-missing", @@ -153,13 +155,14 @@ def display_deptry_version(ctx: click.Context, _param: click.Parameter, value: b " regular dependencies." ), ) +@click.option("--ignore-obsolete", "-io", type=COMMA_SEPARATED_TUPLE, default=(), hidden=True) @click.option( - "--ignore-obsolete", - "-io", + "--ignore-unused", + "-iu", type=COMMA_SEPARATED_TUPLE, help=""" - Comma-separated list of dependencies that should never be marked as obsolete, even if they are not imported in any of the files scanned. - For example; `deptry . --ignore-obsolete foo,bar`. + Comma-separated list of dependencies that should never be marked as unused, even if they are not imported in any of the files scanned. + For example; `deptry . --ignore-unused foo,bar`. """, default=(), ) @@ -271,10 +274,12 @@ def deptry( root: tuple[Path, ...], config: Path, no_ansi: bool, + ignore_unused: tuple[str, ...], ignore_obsolete: tuple[str, ...], ignore_missing: tuple[str, ...], ignore_transitive: tuple[str, ...], ignore_misplaced_dev: tuple[str, ...], + skip_unused: bool, skip_obsolete: bool, skip_missing: bool, skip_transitive: bool, @@ -306,7 +311,7 @@ def deptry( root=root, config=config, no_ansi=no_ansi, - ignore_obsolete=ignore_obsolete, + ignore_unused=get_value_for_ignore_unused(ignore_obsolete=ignore_obsolete, ignore_unused=ignore_unused), ignore_missing=ignore_missing, ignore_transitive=ignore_transitive, ignore_misplaced_dev=ignore_misplaced_dev, @@ -314,7 +319,7 @@ def deptry( extend_exclude=extend_exclude, using_default_exclude=not exclude, ignore_notebooks=ignore_notebooks, - skip_obsolete=skip_obsolete, + skip_unused=get_value_for_skip_unused(skip_obsolete=skip_obsolete, skip_unused=skip_unused), skip_missing=skip_missing, skip_transitive=skip_transitive, skip_misplaced_dev=skip_misplaced_dev, diff --git a/deptry/core.py b/deptry/core.py index 52da1979..a03b381f 100644 --- a/deptry/core.py +++ b/deptry/core.py @@ -15,8 +15,8 @@ from deptry.imports.extract import get_imported_modules_for_list_of_files from deptry.issues_finder.misplaced_dev import MisplacedDevDependenciesFinder from deptry.issues_finder.missing import MissingDependenciesFinder -from deptry.issues_finder.obsolete import ObsoleteDependenciesFinder from deptry.issues_finder.transitive import TransitiveDependenciesFinder +from deptry.issues_finder.unused import UnusedDependenciesFinder from deptry.module import ModuleBuilder, ModuleLocations from deptry.python_file_finder import PythonFileFinder from deptry.reporters import JSONReporter, TextReporter @@ -36,11 +36,11 @@ class Core: root: tuple[Path, ...] config: Path no_ansi: bool - ignore_obsolete: tuple[str, ...] + ignore_unused: tuple[str, ...] ignore_missing: tuple[str, ...] ignore_transitive: tuple[str, ...] ignore_misplaced_dev: tuple[str, ...] - skip_obsolete: bool + skip_unused: bool skip_missing: bool skip_transitive: bool skip_misplaced_dev: bool @@ -101,9 +101,9 @@ def _find_violations( ) -> list[Violation]: violations = [] - if not self.skip_obsolete: + if not self.skip_unused: violations.extend( - ObsoleteDependenciesFinder(imported_modules_with_locations, dependencies, self.ignore_obsolete).find() + UnusedDependenciesFinder(imported_modules_with_locations, dependencies, self.ignore_unused).find() ) if not self.skip_missing: diff --git a/deptry/deprecate_obsolete.py b/deptry/deprecate_obsolete.py new file mode 100644 index 00000000..3f4dc607 --- /dev/null +++ b/deptry/deprecate_obsolete.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +import logging + +SKIP_OBSOLETE_WARNING = ( + "Warning: In an upcoming release, support for the `--skip-obsolete` command-line option and the `skip_obsolete`" + " configuration parameter will be discontinued. Instead, use the `--skip-unused` option or the `skip_unused`" + " configuration parameter to achieve the desired behavior." +) +IGNORE_OBSOLETE_WARNING = ( + "Warning: In an upcoming release, support for the `--ignore-obsolete` command-line option and the `ignore_obsolete`" + " configuration parameter will be discontinued. Instead, use the `--ignore-unused` option or the `ignore_unused`" + " configuration parameter to achieve the desired behavior." +) +SKIP_OBSOLETE_AND_SKIP_UNUSED_ERROR_MESSAGE = ( + "Both `skip_obsolete` and `skip_unused` options are set, either in pyproject.toml or through their corresponding" + " CLI arguments. Please use only the `skip_unused` option, as `skip_obsolete` will be deprecated in the future." +) +IGNORE_OBSOLETE_AND_IGNORE_UNUSED_ERROR_MESSAGE = ( + "Both `ignore_obsolete` and `ignore_unused` options are set, either in pyproject.toml or through their" + " corresponding CLI arguments. Please use only the `ignore_unused` option, as `ignore_obsolete` will be deprecated" + " in the future." +) + + +def get_value_for_skip_unused(skip_obsolete: bool, skip_unused: bool) -> bool: + """ + The skip_obsolete argument will be deprecated in the future, users should use skip_unused instead. + If only skip_obsolete is defined, display a warning message. If both skip_obsolete and skip_unused are defined, + raise an Error. + """ + if skip_obsolete: + logging.warning(SKIP_OBSOLETE_WARNING) + if skip_unused: + raise ValueError(SKIP_OBSOLETE_AND_SKIP_UNUSED_ERROR_MESSAGE) + else: + return skip_obsolete + return skip_unused + + +def get_value_for_ignore_unused(ignore_obsolete: tuple[str, ...], ignore_unused: tuple[str, ...]) -> tuple[str, ...]: + """ + The ignore_obsolete argument will be deprecated in the future, users should use ignore_unused instead. + If only ignore_obsolete is defined, display a warning message. If both ignore_obsolete and ignore_unused are defined, + raise an Error. + """ + if ignore_obsolete: + logging.warning(IGNORE_OBSOLETE_WARNING) + if ignore_unused: + raise ValueError(IGNORE_OBSOLETE_AND_IGNORE_UNUSED_ERROR_MESSAGE) + else: + return ignore_obsolete + return ignore_unused diff --git a/deptry/issues_finder/obsolete.py b/deptry/issues_finder/unused.py similarity index 68% rename from deptry/issues_finder/obsolete.py rename to deptry/issues_finder/unused.py index 24ced8ed..9a647c36 100644 --- a/deptry/issues_finder/obsolete.py +++ b/deptry/issues_finder/unused.py @@ -6,7 +6,7 @@ from deptry.imports.location import Location from deptry.issues_finder.base import IssuesFinder -from deptry.violations import ObsoleteDependencyViolation +from deptry.violations import UnusedDependencyViolation if TYPE_CHECKING: from deptry.dependency import Dependency @@ -14,39 +14,37 @@ @dataclass -class ObsoleteDependenciesFinder(IssuesFinder): +class UnusedDependenciesFinder(IssuesFinder): """ - Finds obsolete dependencies by comparing a list of imported modules to a list of project dependencies. + Finds unused dependencies by comparing a list of imported modules to a list of project dependencies. - A dependency is considered obsolete if none of the following conditions hold: + A dependency is considered unused if none of the following conditions hold: - A module with the exact name of the dependency is imported. - Any of the top-level modules of the dependency are imported. For example, 'matplotlib' has top-levels ['matplotlib', 'mpl_toolkits', 'pylab']. `mpl_toolkits` does not have - any associated metadata, but if this is imported the associated dependency `matplotlib` is not obsolete, + any associated metadata, but if this is imported the associated dependency `matplotlib` is not unused, even if `matplotlib` itself is not imported anywhere. """ def find(self) -> list[Violation]: - logging.debug("\nScanning for obsolete dependencies...") - obsolete_dependencies: list[Violation] = [] + logging.debug("\nScanning for unused dependencies...") + unused_dependencies: list[Violation] = [] for dependency in self.dependencies: logging.debug("Scanning module %s...", dependency.name) - if self._is_obsolete(dependency): - obsolete_dependencies.append( - ObsoleteDependencyViolation(dependency, Location(dependency.definition_file)) - ) + if self._is_unused(dependency): + unused_dependencies.append(UnusedDependencyViolation(dependency, Location(dependency.definition_file))) - return obsolete_dependencies + return unused_dependencies - def _is_obsolete(self, dependency: Dependency) -> bool: + def _is_unused(self, dependency: Dependency) -> bool: if self._dependency_found_in_imported_modules(dependency) or self._any_of_the_top_levels_imported(dependency): return False if dependency.name in self.ignored_modules: - logging.debug("Dependency '%s' found to be obsolete, but ignoring.", dependency.name) + logging.debug("Dependency '%s' found to be unused, but ignoring.", dependency.name) return False logging.debug("Dependency '%s' does not seem to be used.", dependency.name) diff --git a/deptry/violations/__init__.py b/deptry/violations/__init__.py index 100b7faa..82dc345d 100644 --- a/deptry/violations/__init__.py +++ b/deptry/violations/__init__.py @@ -3,13 +3,13 @@ from deptry.violations.base import Violation from deptry.violations.misplaced_dev import MisplacedDevDependencyViolation from deptry.violations.missing import MissingDependencyViolation -from deptry.violations.obsolete import ObsoleteDependencyViolation from deptry.violations.transitive import TransitiveDependencyViolation +from deptry.violations.unused import UnusedDependencyViolation __all__ = ( "MisplacedDevDependencyViolation", "MissingDependencyViolation", - "ObsoleteDependencyViolation", + "UnusedDependencyViolation", "TransitiveDependencyViolation", "Violation", ) diff --git a/deptry/violations/obsolete.py b/deptry/violations/unused.py similarity index 91% rename from deptry/violations/obsolete.py rename to deptry/violations/unused.py index 412c53f9..196d4d1f 100644 --- a/deptry/violations/obsolete.py +++ b/deptry/violations/unused.py @@ -10,7 +10,7 @@ @dataclass -class ObsoleteDependencyViolation(Violation): +class UnusedDependencyViolation(Violation): error_code: ClassVar[str] = "DEP002" error_template: ClassVar[str] = "'{name}' defined as a dependency but not used in the codebase" issue: Dependency diff --git a/docs/index.md b/docs/index.md index a7803a8f..89ad0fb8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,7 +17,7 @@ [![PyPI - Downloads](https://img.shields.io/pypi/dm/deptry)](https://pypistats.org/packages/deptry) [![License](https://img.shields.io/github/license/fpgmaas/deptry)](https://img.shields.io/github/license/fpgmaas/deptry) -_deptry_ is a command line tool to check for issues with dependencies in a Python project, such as obsolete or missing dependencies. It supports the following types of projects: +_deptry_ is a command line tool to check for issues with dependencies in a Python project, such as unused or missing dependencies. It supports the following types of projects: - Projects that use [Poetry](https://python-poetry.org/) and a corresponding `pyproject.toml` file - Projects that use [PDM](https://pdm.fming.dev/latest/) and a corresponding `pyproject.toml` file diff --git a/docs/issues-detection.md b/docs/issues-detection.md index 8e1a440d..2f5e5ea9 100644 --- a/docs/issues-detection.md +++ b/docs/issues-detection.md @@ -3,7 +3,7 @@ _deptry_ looks for the following issues in dependencies: - [Missing dependencies (DEP001)](#missing-dependencies-dep001) -- [Obsolete dependencies (DEP002)](#obsolete-dependencies-dep002) +- [Unused dependencies (DEP002)](#unused-dependencies-dep002) - [Transitive dependencies (DEP003)](#transitive-dependencies-dep003) - [Misplaced development dependencies (DEP004)](#misplaced-development-dependencies-dep004) @@ -44,15 +44,15 @@ To fix the issue, `httpx` should be added to `[project.dependencies]`: dependencies = ["httpx==0.23.1"] ``` -## Obsolete dependencies (DEP002) +## Unused dependencies (DEP002) Dependencies that are required in a project, but are not used within the codebase. ### Configuration -This check can be disabled with [Skip obsolete](usage.md#skip-obsolete) option. +This check can be disabled with [Skip unused](usage.md#skip-unused) option. -Specific dependencies can be ignored with [Ignore obsolete](usage.md#ignore-obsolete) option. +Specific dependencies can be ignored with [Ignore unused](usage.md#ignore-unused) option. ### Example @@ -76,7 +76,7 @@ def make_http_request(): return httpx.get("https://example.com") ``` -_deptry_ will report `requests` as an obsolete dependency because it is not used in the project. +_deptry_ will report `requests` as an unused dependency because it is not used in the project. To fix the issue, `requests` should be removed from `[project.dependencies]`: diff --git a/docs/usage.md b/docs/usage.md index edabfb12..3f5e2108 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -190,22 +190,22 @@ ignore_missing = ["pip", "tomllib"] deptry . --ignore-missing "pip,tomllib" ``` -#### Ignore obsolete +#### Ignore unused -List of packages to ignore when running the check for [Obsolete dependencies (DEP002)](issues-detection.md#obsolete-dependencies-dep002). +List of packages to ignore when running the check for [Unused dependencies (DEP002)](issues-detection.md#unused-dependencies-dep002). - Type: `List[str]` - Default: `[]` -- `pyproject.toml` option name: `ignore_obsolete` -- CLI option name: `--ignore-obsolete` (short: `-io`) +- `pyproject.toml` option name: `ignore_unused` +- CLI option name: `--ignore-unused` (short: `-io`) - `pyproject.toml` example: ```toml [tool.deptry] -ignore_obsolete = ["uvicorn", "uvloop"] +ignore_unused = ["uvicorn", "uvloop"] ``` - CLI example: ```shell -deptry . --ignore-obsolete "uvicorn,uvloop" +deptry . --ignore-unused "uvicorn,uvloop" ``` #### Ignore transitive @@ -280,22 +280,22 @@ skip_missing = true deptry . --skip-missing ``` -#### Skip obsolete +#### Skip unused -Disable the check for [Obsolete dependencies (DEP002)](issues-detection.md#obsolete-dependencies-dep002). +Disable the check for [Unused dependencies (DEP002)](issues-detection.md#unused-dependencies-dep002). - Type: `bool` - Default: `False` -- `pyproject.toml` option name: `skip_obsolete` -- CLI option name: `--skip-obsolete` +- `pyproject.toml` option name: `skip_unused` +- CLI option name: `--skip-unused` - `pyproject.toml` example: ```toml [tool.deptry] -skip_obsolete = true +skip_unused = true ``` - CLI example: ```shell -deptry . --skip-obsolete +deptry . --skip-unused ``` #### Skip transitive @@ -471,7 +471,7 @@ This however is not always sufficient. A situation may occur where a package is not installed because it is optional and unused in the current installation. Then when the package name doesn't directly translate to the top level module name, or there are more top level modules names, Deptry may report both -obsolete packages, and missing packages. A concrete example is deptry reporting obsolete (optional) dependency +unused packages, and missing packages. A concrete example is deptry reporting unused (optional) dependency `foo-python`, and missing package `foo`, while package `foo-python` would install top level module `foo`, if it were installed. diff --git a/pyproject.toml b/pyproject.toml index 1f9edf9e..085c22ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "deptry" version = "0.0.1" -description = "A command line utility to check for obsolete, missing and transitive dependencies in a Python project." +description = "A command line utility to check for unused, missing and transitive dependencies in a Python project." authors = ["Florian Maas "] maintainers = ["Mathieu Kniewallner "] repository = "https://github.com/fpgmaas/deptry" diff --git a/tests/data/example_project/README.md b/tests/data/example_project/README.md index b7aaba0e..acb449a6 100644 --- a/tests/data/example_project/README.md +++ b/tests/data/example_project/README.md @@ -47,7 +47,7 @@ pkginfo So expected output without any additional configuration: ``` -obsolete: requests (because pkginfo is ignored) +unused: isort, requests (because pkginfo is ignored) missing: white transitive: None dev: black diff --git a/tests/data/example_project/pyproject.toml b/tests/data/example_project/pyproject.toml index dffe065c..4e12701c 100644 --- a/tests/data/example_project/pyproject.toml +++ b/tests/data/example_project/pyproject.toml @@ -17,6 +17,6 @@ pkginfo = "^1.8.3" black = "^22.6.0" [tool.deptry] -ignore_obsolete = [ +ignore_unused = [ 'pkginfo' ] diff --git a/tests/data/pep_621_project/README.md b/tests/data/pep_621_project/README.md index 92f370f6..463eff59 100644 --- a/tests/data/pep_621_project/README.md +++ b/tests/data/pep_621_project/README.md @@ -41,7 +41,7 @@ pkginfo So expected output without any additional configuration: ``` -obsolete: isort, requests (pkginfo is ignored) +unused: isort, requests (pkginfo is ignored) missing: white transitive: black dev: None diff --git a/tests/data/pep_621_project/pyproject.toml b/tests/data/pep_621_project/pyproject.toml index 0685665b..943e700c 100644 --- a/tests/data/pep_621_project/pyproject.toml +++ b/tests/data/pep_621_project/pyproject.toml @@ -27,4 +27,4 @@ requires = ["setuptools>=61.0.0"] build-backend = "setuptools.build_meta" [tool.deptry] -ignore_obsolete = ["pkginfo"] +ignore_unused = ["pkginfo"] diff --git a/tests/data/project_with_future_deprecated_obsolete_argument/README.md b/tests/data/project_with_future_deprecated_obsolete_argument/README.md new file mode 100644 index 00000000..a07d7860 --- /dev/null +++ b/tests/data/project_with_future_deprecated_obsolete_argument/README.md @@ -0,0 +1,54 @@ +## Dependencies + +Dependencies are: + +``` +toml +urllib3 +isort +click +requests +pkginfo +``` + +dev-dependencies: + +``` +black +``` + +## Imports + +Imported in .py files are + +``` +click +urllib3 +black +white +``` + +Additional imports in .ipynb file: + +``` +toml +``` + +## Config + +pyproject.toml specifies to ignore the dependencies through the to-be deprecated `ignore_obsolete`: + +``` +pkginfo +``` + +## Output + +So expected output without any additional configuration: + +``` +unused: isort, requests +missing: white +transitive: None +dev: black +``` diff --git a/tests/data/project_with_future_deprecated_obsolete_argument/poetry.toml b/tests/data/project_with_future_deprecated_obsolete_argument/poetry.toml new file mode 100644 index 00000000..ab1033bd --- /dev/null +++ b/tests/data/project_with_future_deprecated_obsolete_argument/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true diff --git a/tests/data/project_with_future_deprecated_obsolete_argument/pyproject.toml b/tests/data/project_with_future_deprecated_obsolete_argument/pyproject.toml new file mode 100644 index 00000000..dffe065c --- /dev/null +++ b/tests/data/project_with_future_deprecated_obsolete_argument/pyproject.toml @@ -0,0 +1,22 @@ +[tool.poetry] +name = "test" +version = "0.0.1" +description = "A test project" +authors = ["test "] + +[tool.poetry.dependencies] +python = ">=3.7,<4.0" +toml = "^0.10.2" +urllib3 = "^1.26.12" +isort = "^5.10.1" +click = "^8.1.3" +requests = "^2.28.1" +pkginfo = "^1.8.3" + +[tool.poetry.dev-dependencies] +black = "^22.6.0" + +[tool.deptry] +ignore_obsolete = [ +'pkginfo' +] diff --git a/tests/data/project_with_future_deprecated_obsolete_argument/src/main.py b/tests/data/project_with_future_deprecated_obsolete_argument/src/main.py new file mode 100644 index 00000000..2ee666ac --- /dev/null +++ b/tests/data/project_with_future_deprecated_obsolete_argument/src/main.py @@ -0,0 +1,7 @@ +from os import chdir, walk +from pathlib import Path + +import black +import click +import white as w +from urllib3 import contrib diff --git a/tests/data/project_with_future_deprecated_obsolete_argument/src/notebook.ipynb b/tests/data/project_with_future_deprecated_obsolete_argument/src/notebook.ipynb new file mode 100644 index 00000000..a51bdb9d --- /dev/null +++ b/tests/data/project_with_future_deprecated_obsolete_argument/src/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/data/project_with_gitignore/pyproject.toml b/tests/data/project_with_gitignore/pyproject.toml index 0685665b..943e700c 100644 --- a/tests/data/project_with_gitignore/pyproject.toml +++ b/tests/data/project_with_gitignore/pyproject.toml @@ -27,4 +27,4 @@ requires = ["setuptools>=61.0.0"] build-backend = "setuptools.build_meta" [tool.deptry] -ignore_obsolete = ["pkginfo"] +ignore_unused = ["pkginfo"] diff --git a/tests/data/project_with_pdm/README.md b/tests/data/project_with_pdm/README.md index 9142231f..2aa86235 100644 --- a/tests/data/project_with_pdm/README.md +++ b/tests/data/project_with_pdm/README.md @@ -47,7 +47,7 @@ pkginfo So expected output without any additional configuration: ``` -obsolete: isort, requests (pkginfo is ignored) +unused: isort, requests (pkginfo is ignored) missing: white transitive: None dev: black diff --git a/tests/data/project_with_pdm/pyproject.toml b/tests/data/project_with_pdm/pyproject.toml index dbe36ade..46efd1e5 100644 --- a/tests/data/project_with_pdm/pyproject.toml +++ b/tests/data/project_with_pdm/pyproject.toml @@ -26,4 +26,4 @@ lint = [ ] [tool.deptry] -ignore_obsolete = ["pkginfo"] +ignore_unused = ["pkginfo"] diff --git a/tests/data/project_with_pyproject_different_directory/a_sub_directory/pyproject.toml b/tests/data/project_with_pyproject_different_directory/a_sub_directory/pyproject.toml index 0685665b..943e700c 100644 --- a/tests/data/project_with_pyproject_different_directory/a_sub_directory/pyproject.toml +++ b/tests/data/project_with_pyproject_different_directory/a_sub_directory/pyproject.toml @@ -27,4 +27,4 @@ requires = ["setuptools>=61.0.0"] build-backend = "setuptools.build_meta" [tool.deptry] -ignore_obsolete = ["pkginfo"] +ignore_unused = ["pkginfo"] diff --git a/tests/data/project_with_requirements_txt/pyproject.toml b/tests/data/project_with_requirements_txt/pyproject.toml index d7c4205f..4d7d2469 100644 --- a/tests/data/project_with_requirements_txt/pyproject.toml +++ b/tests/data/project_with_requirements_txt/pyproject.toml @@ -2,4 +2,4 @@ line-length = 120 [tool.deptry] -ignore_obsolete = ["pkginfo"] +ignore_unused = ["pkginfo"] diff --git a/tests/data/project_with_src_directory/pyproject.toml b/tests/data/project_with_src_directory/pyproject.toml index 5afc2036..2ffb7d48 100644 --- a/tests/data/project_with_src_directory/pyproject.toml +++ b/tests/data/project_with_src_directory/pyproject.toml @@ -27,4 +27,4 @@ requires = ["setuptools>=61.0.0"] build-backend = "setuptools.build_meta" [tool.deptry] -ignore_obsolete = ["pkginfo"] +ignore_unused = ["pkginfo"] diff --git a/tests/functional/cli/test_cli.py b/tests/functional/cli/test_cli.py index e967b79d..c578455d 100644 --- a/tests/functional/cli/test_cli.py +++ b/tests/functional/cli/test_cli.py @@ -142,14 +142,14 @@ def test_cli_ignore_notebooks(project_builder: ToolSpecificProjectBuilder) -> No def test_cli_ignore_flags(project_builder: ToolSpecificProjectBuilder) -> None: with run_within_dir(project_builder("example_project", "poetry install --no-interaction --no-root")): - result = CliRunner().invoke(deptry, ". --ignore-obsolete isort,pkginfo,requests -im white -id black") + result = CliRunner().invoke(deptry, ". --ignore-unused isort,pkginfo,requests -im white -id black") assert result.exit_code == 0 def test_cli_skip_flags(project_builder: ToolSpecificProjectBuilder) -> None: with run_within_dir(project_builder("example_project", "poetry install --no-interaction --no-root")): - result = CliRunner().invoke(deptry, ". --skip-obsolete --skip-missing --skip-misplaced-dev --skip-transitive") + result = CliRunner().invoke(deptry, ". --skip-unused --skip-missing --skip-misplaced-dev --skip-transitive") assert result.exit_code == 0 diff --git a/tests/functional/cli/test_cli_with_future_deprecated_obsolete_argument.py b/tests/functional/cli/test_cli_with_future_deprecated_obsolete_argument.py new file mode 100644 index 00000000..1467fd57 --- /dev/null +++ b/tests/functional/cli/test_cli_with_future_deprecated_obsolete_argument.py @@ -0,0 +1,138 @@ +from __future__ import annotations + +import shlex +import subprocess +from pathlib import Path +from typing import TYPE_CHECKING + +from deptry.deprecate_obsolete import ( + IGNORE_OBSOLETE_AND_IGNORE_UNUSED_ERROR_MESSAGE, + IGNORE_OBSOLETE_WARNING, + SKIP_OBSOLETE_AND_SKIP_UNUSED_ERROR_MESSAGE, + SKIP_OBSOLETE_WARNING, +) +from tests.utils import get_issues_report, run_within_dir + +if TYPE_CHECKING: + from tests.functional.types import ToolSpecificProjectBuilder + + +def test_cli_uses_both_obsolete_and_unused_flag_from_pyproject_toml( + project_builder: ToolSpecificProjectBuilder, +) -> None: + with run_within_dir( + project_builder("project_with_future_deprecated_obsolete_argument", "poetry install --no-interaction --no-root") + ): + result = subprocess.run(shlex.split("poetry run deptry . -o report.json"), capture_output=True, text=True) + + assert IGNORE_OBSOLETE_WARNING in result.stderr + assert result.returncode == 1 + assert get_issues_report() == [ + { + "error": { + "code": "DEP002", + "message": "'isort' defined as a dependency but not used in the codebase", + }, + "module": "isort", + "location": { + "file": str(Path("pyproject.toml")), + "line": None, + "column": None, + }, + }, + { + "error": { + "code": "DEP002", + "message": "'requests' defined as a dependency but not used in the codebase", + }, + "module": "requests", + "location": { + "file": str(Path("pyproject.toml")), + "line": None, + "column": None, + }, + }, + { + "error": { + "code": "DEP004", + "message": "'black' imported but declared as a dev dependency", + }, + "module": "black", + "location": { + "file": str(Path("src/main.py")), + "line": 4, + "column": 0, + }, + }, + { + "error": { + "code": "DEP001", + "message": "'white' imported but missing from the dependency definitions", + }, + "module": "white", + "location": { + "file": str(Path("src/main.py")), + "line": 6, + "column": 0, + }, + }, + ] + + +def test_cli_skip_obsolete_argument_still_works(project_builder: ToolSpecificProjectBuilder) -> None: + with run_within_dir( + project_builder("project_with_future_deprecated_obsolete_argument", "poetry install --no-interaction --no-root") + ): + result = subprocess.run( + shlex.split("poetry run deptry . --skip-obsolete -o report.json"), capture_output=True, text=True + ) + + assert SKIP_OBSOLETE_WARNING in result.stderr + assert IGNORE_OBSOLETE_WARNING in result.stderr + assert result.returncode == 1 + assert get_issues_report() == [ + { + "error": { + "code": "DEP004", + "message": "'black' imported but declared as a dev dependency", + }, + "module": "black", + "location": { + "file": str(Path("src/main.py")), + "line": 4, + "column": 0, + }, + }, + { + "error": { + "code": "DEP001", + "message": "'white' imported but missing from the dependency definitions", + }, + "module": "white", + "location": { + "file": str(Path("src/main.py")), + "line": 6, + "column": 0, + }, + }, + ] + + +def test_cli_raise_error_if_both_skip_options_used(project_builder: ToolSpecificProjectBuilder) -> None: + with run_within_dir( + project_builder("project_with_future_deprecated_obsolete_argument", "poetry install --no-interaction --no-root") + ): + result = subprocess.run( + shlex.split("poetry run deptry . --skip-obsolete --skip-unused"), capture_output=True, text=True + ) + assert SKIP_OBSOLETE_AND_SKIP_UNUSED_ERROR_MESSAGE in result.stderr + + +def test_cli_raise_error_if_both_ignore_options_used(project_builder: ToolSpecificProjectBuilder) -> None: + with run_within_dir( + project_builder("project_with_future_deprecated_obsolete_argument", "poetry install --no-interaction --no-root") + ): + result = subprocess.run( + shlex.split("poetry run deptry . --ignore-unused 'a,b'"), capture_output=True, text=True + ) + assert IGNORE_OBSOLETE_AND_IGNORE_UNUSED_ERROR_MESSAGE in result.stderr diff --git a/tests/unit/issues_finder/test_obsolete.py b/tests/unit/issues_finder/test_obsolete.py index 1c74dec9..ef91a5b2 100644 --- a/tests/unit/issues_finder/test_obsolete.py +++ b/tests/unit/issues_finder/test_obsolete.py @@ -4,9 +4,9 @@ from deptry.dependency import Dependency from deptry.imports.location import Location -from deptry.issues_finder.obsolete import ObsoleteDependenciesFinder +from deptry.issues_finder.unused import UnusedDependenciesFinder from deptry.module import ModuleBuilder, ModuleLocations -from deptry.violations import ObsoleteDependencyViolation +from deptry.violations import UnusedDependencyViolation def test_simple() -> None: @@ -18,8 +18,8 @@ def test_simple() -> None: ) ] - assert ObsoleteDependenciesFinder(modules_locations, dependencies).find() == [ - ObsoleteDependencyViolation(dependency_toml, Location(Path("pyproject.toml"))) + assert UnusedDependenciesFinder(modules_locations, dependencies).find() == [ + UnusedDependencyViolation(dependency_toml, Location(Path("pyproject.toml"))) ] @@ -31,13 +31,13 @@ def test_simple_with_ignore() -> None: ) ] - assert ObsoleteDependenciesFinder(modules_locations, dependencies, ignored_modules=("click",)).find() == [] + assert UnusedDependenciesFinder(modules_locations, dependencies, ignored_modules=("click",)).find() == [] def test_top_level() -> None: """ - Test if top-level information is read, and correctly used to not mark a dependency as obsolete. - blackd is in the top-level of black, so black should not be marked as an obsolete dependency. + Test if top-level information is read, and correctly used to not mark a dependency as unused. + blackd is in the top-level of black, so black should not be marked as an unused dependency. """ dependencies = [Dependency("black", Path("pyproject.toml"))] modules_locations = [ @@ -46,14 +46,14 @@ def test_top_level() -> None: ) ] - deps = ObsoleteDependenciesFinder(modules_locations, dependencies).find() + deps = UnusedDependenciesFinder(modules_locations, dependencies).find() assert deps == [] def test_without_top_level() -> None: """ - Test if packages without top-level information are correctly maked as obsolete + Test if packages without top-level information are correctly maked as unused """ dependencies = [Dependency("isort", Path("pyproject.toml"))] modules_locations = [ @@ -62,4 +62,4 @@ def test_without_top_level() -> None: ) ] - assert ObsoleteDependenciesFinder(modules_locations, dependencies).find() == [] + assert UnusedDependenciesFinder(modules_locations, dependencies).find() == [] diff --git a/tests/unit/reporters/test_json_reporter.py b/tests/unit/reporters/test_json_reporter.py index 745abaf0..89aa3e8f 100644 --- a/tests/unit/reporters/test_json_reporter.py +++ b/tests/unit/reporters/test_json_reporter.py @@ -10,8 +10,8 @@ from deptry.violations import ( MisplacedDevDependencyViolation, MissingDependencyViolation, - ObsoleteDependencyViolation, TransitiveDependencyViolation, + UnusedDependencyViolation, ) from tests.utils import run_within_dir @@ -21,9 +21,7 @@ def test_simple(tmp_path: Path) -> None: JSONReporter( [ MissingDependencyViolation(Module("foo", package="foo_package"), Location(Path("foo.py"), 1, 2)), - ObsoleteDependencyViolation( - Dependency("foo", Path("pyproject.toml")), Location(Path("pyproject.toml")) - ), + UnusedDependencyViolation(Dependency("foo", Path("pyproject.toml")), Location(Path("pyproject.toml"))), TransitiveDependencyViolation(Module("foo", package="foo_package"), Location(Path("foo/bar.py"), 1, 2)), MisplacedDevDependencyViolation(Module("foo", package="foo_package"), Location(Path("foo.py"), 1, 2)), ], diff --git a/tests/unit/reporters/test_text_reporter.py b/tests/unit/reporters/test_text_reporter.py index 4bab4b2f..e7555696 100644 --- a/tests/unit/reporters/test_text_reporter.py +++ b/tests/unit/reporters/test_text_reporter.py @@ -14,8 +14,8 @@ from deptry.violations import ( MisplacedDevDependencyViolation, MissingDependencyViolation, - ObsoleteDependencyViolation, TransitiveDependencyViolation, + UnusedDependencyViolation, ) from tests.utils import stylize @@ -27,7 +27,7 @@ def test_logging_number_multiple(caplog: LogCaptureFixture) -> None: with caplog.at_level(logging.INFO): violations = [ MissingDependencyViolation(Module("foo", package="foo_package"), Location(Path("foo.py"), 1, 2)), - ObsoleteDependencyViolation(Dependency("foo", Path("pyproject.toml")), Location(Path("pyproject.toml"))), + UnusedDependencyViolation(Dependency("foo", Path("pyproject.toml")), Location(Path("pyproject.toml"))), TransitiveDependencyViolation(Module("foo", package="foo_package"), Location(Path("foo/bar.py"), 1, 2)), MisplacedDevDependencyViolation(Module("foo", package="foo_package"), Location(Path("foo.py"), 1, 2)), ] @@ -102,7 +102,7 @@ def test_logging_no_ansi(caplog: LogCaptureFixture) -> None: with caplog.at_level(logging.INFO): violations = [ MissingDependencyViolation(Module("foo", package="foo_package"), Location(Path("foo.py"), 1, 2)), - ObsoleteDependencyViolation(Dependency("foo", Path("pyproject.toml")), Location(Path("pyproject.toml"))), + UnusedDependencyViolation(Dependency("foo", Path("pyproject.toml")), Location(Path("pyproject.toml"))), TransitiveDependencyViolation(Module("foo", package="foo_package"), Location(Path("foo/bar.py"), 1, 2)), MisplacedDevDependencyViolation(Module("foo", package="foo_package"), Location(Path("foo.py"), 1, 2)), ] diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 431569fb..7ad73243 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -20,11 +20,11 @@ def test_read_configuration_from_pyproject_toml_exists(tmp_path: Path) -> None: "exclude": ["bar"], "extend_exclude": ["foo"], "ignore_notebooks": False, - "ignore_obsolete": ["baz", "bar"], + "ignore_unused": ["baz", "bar"], "ignore_missing": [], "ignore_misplaced_dev": [], "ignore_transitive": [], - "skip_obsolete": False, + "skip_unused": False, "skip_missing": False, "skip_transitive": False, "skip_misplaced_dev": False, @@ -38,11 +38,11 @@ def test_read_configuration_from_pyproject_toml_exists(tmp_path: Path) -> None: exclude = ["foo", "bar"] extend_exclude = ["bar", "foo"] ignore_notebooks = true - ignore_obsolete = ["foo"] + ignore_unused = ["foo"] ignore_missing = ["baz", "foobar"] ignore_misplaced_dev = ["barfoo"] ignore_transitive = ["foobaz"] - skip_obsolete = true + skip_unused = true skip_missing = true skip_transitive = true skip_misplaced_dev = true @@ -65,11 +65,11 @@ def test_read_configuration_from_pyproject_toml_exists(tmp_path: Path) -> None: "exclude": ["foo", "bar"], "extend_exclude": ["bar", "foo"], "ignore_notebooks": True, - "ignore_obsolete": ["foo"], + "ignore_unused": ["foo"], "ignore_missing": ["baz", "foobar"], "ignore_misplaced_dev": ["barfoo"], "ignore_transitive": ["foobaz"], - "skip_obsolete": True, + "skip_unused": True, "skip_missing": True, "skip_transitive": True, "skip_misplaced_dev": True, diff --git a/tests/unit/test_core.py b/tests/unit/test_core.py index 49b4ae07..b95da38c 100644 --- a/tests/unit/test_core.py +++ b/tests/unit/test_core.py @@ -16,8 +16,8 @@ from deptry.violations import ( MisplacedDevDependencyViolation, MissingDependencyViolation, - ObsoleteDependencyViolation, TransitiveDependencyViolation, + UnusedDependencyViolation, ) from tests.utils import create_files, run_within_dir @@ -82,11 +82,11 @@ def test__get_local_modules( root=(tmp_path / root_suffix,), config=Path("pyproject.toml"), no_ansi=False, - ignore_obsolete=(), + ignore_unused=(), ignore_missing=(), ignore_transitive=(), ignore_misplaced_dev=(), - skip_obsolete=False, + skip_unused=False, skip_missing=False, skip_transitive=False, skip_misplaced_dev=False, @@ -157,7 +157,7 @@ def test__get_stdlib_packages_unsupported(version_info: tuple[int | str, ...]) - def test__exit_with_violations() -> None: violations = [ MissingDependencyViolation(Module("foo"), Location(Path("foo.py"), 1, 2)), - ObsoleteDependencyViolation(Dependency("foo", Path("pyproject.toml")), Location(Path("pyproject.toml"))), + UnusedDependencyViolation(Dependency("foo", Path("pyproject.toml")), Location(Path("pyproject.toml"))), TransitiveDependencyViolation(Module("foo"), Location(Path("foo.py"), 1, 2)), MisplacedDevDependencyViolation(Module("foo"), Location(Path("foo.py"), 1, 2)), ] diff --git a/tests/unit/test_deprecate_obsolete.py b/tests/unit/test_deprecate_obsolete.py new file mode 100644 index 00000000..0a8e5a80 --- /dev/null +++ b/tests/unit/test_deprecate_obsolete.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +import pytest + +from deptry.deprecate_obsolete import ( + IGNORE_OBSOLETE_AND_IGNORE_UNUSED_ERROR_MESSAGE, + IGNORE_OBSOLETE_WARNING, + SKIP_OBSOLETE_AND_SKIP_UNUSED_ERROR_MESSAGE, + SKIP_OBSOLETE_WARNING, + get_value_for_ignore_unused, + get_value_for_skip_unused, +) + + +def test_skip_obsolete(caplog: pytest.LogCaptureFixture) -> None: + result = get_value_for_skip_unused(skip_obsolete=True, skip_unused=False) + assert result + assert SKIP_OBSOLETE_WARNING in caplog.text + + +def test_skip_unused() -> None: + result = get_value_for_skip_unused(skip_obsolete=False, skip_unused=True) + assert result + + +def test_skip_unused_and_skip_obsolete() -> None: + with pytest.raises(ValueError, match=SKIP_OBSOLETE_AND_SKIP_UNUSED_ERROR_MESSAGE): + get_value_for_skip_unused(skip_obsolete=True, skip_unused=True) + + +def test_ignore_obsolete(caplog: pytest.LogCaptureFixture) -> None: + result = get_value_for_ignore_unused(ignore_obsolete=("a",), ignore_unused=()) + assert result == ("a",) + assert IGNORE_OBSOLETE_WARNING in caplog.text + + +def test_ignore_unused() -> None: + result = get_value_for_ignore_unused(ignore_obsolete=(), ignore_unused=("a",)) + assert result == ("a",) + + +def test_ignore_unused_and_ignore_obsolete() -> None: + with pytest.raises(ValueError, match=IGNORE_OBSOLETE_AND_IGNORE_UNUSED_ERROR_MESSAGE): + get_value_for_ignore_unused(ignore_obsolete=("b",), ignore_unused=("a",)) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 8aac16b1..4bc90cc6 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -8,7 +8,7 @@ def test_load_pyproject_toml() -> None: assert load_pyproject_toml(Path("tests/data/example_project/pyproject.toml")) == { "tool": { - "deptry": {"ignore_obsolete": ["pkginfo"]}, + "deptry": {"ignore_unused": ["pkginfo"]}, "poetry": { "authors": ["test "], "dependencies": {