From 73830a673a9f76afd7b1afa56262e8799a48aead Mon Sep 17 00:00:00 2001 From: Florian Maas Date: Sun, 18 Sep 2022 09:16:27 +0200 Subject: [PATCH 1/2] Added a method to extract top level module names from RECORD --- deptry/dependency.py | 42 ++++++++++++++++++++++++++++++++-- tests/test_dependency.py | 49 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/deptry/dependency.py b/deptry/dependency.py index fee80b3f..bd500232 100644 --- a/deptry/dependency.py +++ b/deptry/dependency.py @@ -1,4 +1,5 @@ import logging +import re from typing import List from deptry.utils import import_importlib_metadata @@ -30,7 +31,9 @@ def _get_top_levels(self, name: str) -> List[str]: top_levels = [] if self.found: - top_levels += self._get_top_level_module_names_from_metadata() + top_levels += self._get_top_level_module_names_from_top_level_txt() + if not top_levels: + top_levels += self._get_top_level_module_names_from_record_file() top_levels.append(name.replace("-", "_").lower()) return set(top_levels) @@ -66,9 +69,44 @@ def _string_for_printing(self): else: return " " - def _get_top_level_module_names_from_metadata(self): + def _get_top_level_module_names_from_top_level_txt(self): + """ + top-level.txt is a metadata file added by setuptools that looks as follows: + + 610faff656c4cfcbb4a3__mypyc + _black_version + black + blackd + blib2to3 + + This function extracts these names, if a top-level.txt file exists. + """ metadata_top_levels = metadata.distribution(self.name).read_text("top_level.txt") if metadata_top_levels: return [x for x in metadata_top_levels.split("\n") if len(x) > 0] else: return [] + + def _get_top_level_module_names_from_record_file(self): + """ + Get the top-level module names from the RECORD file, whose contents usually look as follows: + + ... + black/trans.cpython-39-darwin.so,sha256= + black/trans.py,sha256= + blackd/__init__.py,sha256= + blackd/__main__.py,sha256= + ... + + So if no file top-level.txt is provided, we can try and extract top-levels from this file, in this case black and blackd. + """ + top_levels = [] + try: + metadata_records = metadata.distribution(self.name).read_text("RECORD").split("\n") + except: + return [] + for line in metadata_records: + match = re.match("([a-zA-Z0-9-_]+)/", line) + if match: + top_levels.append(match.group(1)) + return list(set(top_levels)) diff --git a/tests/test_dependency.py b/tests/test_dependency.py index 2879aa62..fb38f25f 100644 --- a/tests/test_dependency.py +++ b/tests/test_dependency.py @@ -1,4 +1,7 @@ +from unittest.mock import patch + from deptry.dependency import Dependency +from deptry.utils import import_importlib_metadata def test_simple_dependency(): @@ -11,3 +14,49 @@ def test_create_default_top_level_if_metadata_not_found(): dependency = Dependency("Foo-bar") assert dependency.name == "Foo-bar" assert dependency.top_levels == set(["foo_bar"]) + + +def test_read_top_level_from_top_level_txt(): + """ + Read the top-levels.txt file + """ + + class MockDistribution: + def __init__(self): + pass + + def read_text(self, file_name): + return "foo\nbar" + + with patch("deptry.dependency.metadata.distribution") as mock: + mock.return_value = MockDistribution() + dependency = Dependency("Foo-bar") + assert dependency.name == "Foo-bar" + assert dependency.top_levels == set(["foo_bar", "foo", "bar"]) + + +def test_read_top_level_from_record(): + """ + Verify that if top-level.txt not found, an attempt is made to extract top-level module names from + the metadata RECORD file. + """ + + class MockDistribution: + def __init__(self): + pass + + def read_text(self, file_name): + if file_name == "top-level.txt": + return None + elif file_name == "RECORD": + return """black/trans.cpython-39-darwin.so,sha256= +black/trans.py,sha256= +blackd/__init__.py,sha256= +blackd/__main__.py,sha256= + """ + + with patch("deptry.dependency.metadata.distribution") as mock: + mock.return_value = MockDistribution() + dependency = Dependency("Foo-bar") + assert dependency.name == "Foo-bar" + assert dependency.top_levels == set(["foo_bar", "black", "blackd"]) From 5da924af7b96eade27d3f4fb6255ff569999047b Mon Sep 17 00:00:00 2001 From: Florian Maas Date: Sun, 18 Sep 2022 09:20:33 +0200 Subject: [PATCH 2/2] ignored a flake8 error --- deptry/dependency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deptry/dependency.py b/deptry/dependency.py index bd500232..63f6b563 100644 --- a/deptry/dependency.py +++ b/deptry/dependency.py @@ -103,7 +103,7 @@ def _get_top_level_module_names_from_record_file(self): top_levels = [] try: metadata_records = metadata.distribution(self.name).read_text("RECORD").split("\n") - except: + except: # noqa: E722 return [] for line in metadata_records: match = re.match("([a-zA-Z0-9-_]+)/", line)