11from __future__ import annotations
22
33import logging
4- import re
5- from contextlib import suppress
64from typing import TYPE_CHECKING
75
8- from deptry .compat import importlib_metadata
6+ from deptry .distribution import get_packages_from_distribution
97
108if TYPE_CHECKING :
119 from collections .abc import Sequence
12- from importlib .metadata import Distribution
1310 from pathlib import Path
1411
1512
@@ -22,7 +19,6 @@ class Dependency:
2219 name (str): The name of the dependency.
2320 definition_file (Path): The path to the file defining the dependency, e.g. 'pyproject.toml'.
2421 and that can be used to create a variant of the package with a set of extra functionalities.
25- found (bool): Indicates if the dependency has been found in the environment.
2622 top_levels (set[str]): The top-level module names associated with the dependency.
2723 """
2824
@@ -32,16 +28,11 @@ def __init__(
3228 definition_file : Path ,
3329 module_names : Sequence [str ] | None = None ,
3430 ) -> None :
35- distribution = self .find_distribution (name )
36-
3731 self .name = name
3832 self .definition_file = definition_file
39- self .found = distribution is not None
40- self .top_levels = self ._get_top_levels (name , distribution , module_names )
33+ self .top_levels = self ._get_top_levels (name , module_names )
4134
42- def _get_top_levels (
43- self , name : str , distribution : Distribution | None , module_names : Sequence [str ] | None
44- ) -> set [str ]:
35+ def _get_top_levels (self , name : str , module_names : Sequence [str ] | None ) -> set [str ]:
4536 """
4637 Get the top-level module names for a dependency. They are searched for in the following order:
4738 1. If `module_names` is defined, simply use those as the top-level modules.
@@ -50,22 +41,16 @@ def _get_top_levels(
5041
5142 Args:
5243 name: The name of the dependency.
53- distribution: The metadata distribution of the package.
5444 module_names: If this is given, use these as the top-level modules instead of
5545 searching for them in the metadata.
5646 """
5747 if module_names is not None :
5848 return set (module_names )
5949
60- if distribution is not None :
61- with suppress (FileNotFoundError ):
62- return self ._get_top_level_module_names_from_top_level_txt (distribution )
63-
64- with suppress (FileNotFoundError ):
65- return self ._get_top_level_module_names_from_record_file (distribution )
50+ if distributions := get_packages_from_distribution (self .name ):
51+ return distributions
6652
67- # No metadata or other configuration has been found. As a fallback
68- # we'll guess the name.
53+ # No metadata or other configuration has been found. As a fallback we'll guess the name.
6954 module_name = name .replace ("-" , "_" ).lower ()
7055 logging .warning (
7156 "Assuming the corresponding module name of package %r is %r. Install the package or configure a"
@@ -80,56 +65,3 @@ def __repr__(self) -> str:
8065
8166 def __str__ (self ) -> str :
8267 return f"Dependency '{ self .name } ' with top-levels: { self .top_levels } ."
83-
84- @staticmethod
85- def find_distribution (name : str ) -> Distribution | None :
86- try :
87- return importlib_metadata .distribution (name )
88- except importlib_metadata .PackageNotFoundError :
89- return None
90-
91- @staticmethod
92- def _get_top_level_module_names_from_top_level_txt (distribution : Distribution ) -> set [str ]:
93- """
94- top-level.txt is a metadata file added by setuptools that looks as follows:
95-
96- 610faff656c4cfcbb4a3__mypyc
97- _black_version
98- black
99- blackd
100- blib2to3
101-
102- This function extracts these names, if a top-level.txt file exists.
103- """
104- metadata_top_levels = distribution .read_text ("top_level.txt" )
105- if metadata_top_levels is None :
106- raise FileNotFoundError ("top_level.txt" )
107-
108- return {x for x in metadata_top_levels .splitlines () if x }
109-
110- @staticmethod
111- def _get_top_level_module_names_from_record_file (distribution : Distribution ) -> set [str ]:
112- """
113- Get the top-level module names from the RECORD file, whose contents usually look as follows:
114-
115- ...
116- ../../../bin/black,sha256=<HASH>,247
117- __pycache__/_black_version.cpython-311.pyc,,
118- _black_version.py,sha256=<HASH>,19
119- black/trans.cpython-39-darwin.so,sha256=<HASH>
120- black/trans.py,sha256=<HASH>
121- blackd/__init__.py,sha256=<HASH>
122- blackd/__main__.py,sha256=<HASH>
123- ...
124-
125- So if no file top-level.txt is provided, we can try and extract top-levels from this file, in
126- this case _black_version, black, and blackd.
127- """
128- metadata_records = distribution .read_text ("RECORD" )
129-
130- if metadata_records is None :
131- raise FileNotFoundError ("RECORD" )
132-
133- matches = re .finditer (r"^(?!__)([a-zA-Z0-9-_]+)(?:/|\.py,)" , metadata_records , re .MULTILINE )
134-
135- return {x .group (1 ) for x in matches }
0 commit comments