72 lines
2.7 KiB
Python
72 lines
2.7 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from dataclasses import dataclass
|
|
from typing import TYPE_CHECKING
|
|
|
|
from deptry.imports.location import Location
|
|
from deptry.violations.base import ViolationsFinder
|
|
from deptry.violations.dep002_unused.violation import DEP002UnusedDependencyViolation
|
|
|
|
if TYPE_CHECKING:
|
|
from deptry.dependency import Dependency
|
|
from deptry.violations import Violation
|
|
|
|
|
|
@dataclass
|
|
class DEP002UnusedDependenciesFinder(ViolationsFinder):
|
|
"""
|
|
Finds unused dependencies by comparing a list of imported modules to a list of project dependencies.
|
|
|
|
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 unused,
|
|
even if `matplotlib` itself is not imported anywhere.
|
|
"""
|
|
|
|
violation = DEP002UnusedDependencyViolation
|
|
|
|
def find(self) -> 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_unused(dependency):
|
|
unused_dependencies.append(self.violation(dependency, Location(dependency.definition_file)))
|
|
|
|
return unused_dependencies
|
|
|
|
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 unused, but ignoring.", dependency.name)
|
|
return False
|
|
|
|
logging.debug("Dependency '%s' does not seem to be used.", dependency.name)
|
|
return True
|
|
|
|
def _dependency_found_in_imported_modules(self, dependency: Dependency) -> bool:
|
|
return any(
|
|
module_with_locations.module.package == dependency.name
|
|
for module_with_locations in self.imported_modules_with_locations
|
|
)
|
|
|
|
def _any_of_the_top_levels_imported(self, dependency: Dependency) -> bool:
|
|
if not dependency.top_levels:
|
|
return False
|
|
|
|
return any(
|
|
any(
|
|
module_with_locations.module.name == top_level
|
|
for module_with_locations in self.imported_modules_with_locations
|
|
)
|
|
for top_level in dependency.top_levels
|
|
)
|