Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

check: Add function for parsing import names for local packages #139

Merged
merged 1 commit into from Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 36 additions & 1 deletion fawltydeps/check.py
@@ -1,7 +1,9 @@
"Compare imports and dependencies"

import logging
import sys
from itertools import groupby
from typing import List, Tuple
from typing import List, Optional, Tuple

from fawltydeps.types import (
DeclaredDependency,
Expand All @@ -10,6 +12,39 @@
UnusedDependency,
)

# importlib.metadata.packages_distributions() was introduced in v3.10, but it
# is not able to infer import names for modules lacking a top_level.txt until
# v3.11. Hence we prefer importlib_metadata in v3.10 as well as pre-v3.10.
jherland marked this conversation as resolved.
Show resolved Hide resolved
if sys.version_info >= (3, 11):
from importlib.metadata import packages_distributions
else:
from importlib_metadata import packages_distributions

logger = logging.getLogger(__name__)


def find_import_names_from_package_name(package: str) -> Optional[List[str]]:
mknorps marked this conversation as resolved.
Show resolved Hide resolved
"""Convert a package name to provided import names.

(Although this function generally works with _all_ packages, we will apply
it only to the subset that is the dependencies of the current project.)

Use importlib.metadata to look up the mapping between packages and their
provided import names, and return the import names associated with the given
package/distribution name in the current Python environment. This obviously
depends on which Python environment (e.g. virtualenv) we're calling from.

Return None if we're unable to find any import names for the given package.
This is typically because the package is missing from the current
environment, or because it fails to declare its importable modules.
"""
ret = [
import_name
for import_name, packages in packages_distributions().items()
Nour-Mws marked this conversation as resolved.
Show resolved Hide resolved
if package in packages
]
return ret or None


def compare_imports_to_dependencies(
imports: List[ParsedImport], dependencies: List[DeclaredDependency]
Expand Down
2 changes: 1 addition & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Expand Up @@ -32,7 +32,7 @@ fawltydeps = "fawltydeps.main:main"
# These are the main dependencies for fawltydeps at runtime.
# Do not add anything here that is only needed by CI/tests/linters/developers
python = "^3.7.2"
importlib_metadata = {version = "^5.0.0", python = "<3.8"}
importlib_metadata = {version = "^5.0.0", python = "<3.11"}
isort = "^5.10"
pydantic = "^1.10.4"
tomli = {version = "^2.0.1", python = "<3.11"}
Expand Down
41 changes: 41 additions & 0 deletions tests/test_map_dep_name_to_import_names.py
@@ -0,0 +1,41 @@
"""Test the mapping of dependency names to import names."""

import pytest

from fawltydeps.check import find_import_names_from_package_name

# TODO: These tests are not fully isolated, i.e. they do not control the
# virtualenv in which they run. For now, we assume that we are running in an
# environment where at least these packages are available:
# - setuptools (exposes multiple import names, including pkg_resources)
# - pip (exposes a single import name: pip)
# - isort (exposes no top_level.txt, but 'isort' import name can be inferred)


@pytest.mark.parametrize(
"dep_name,expect_import_names",
[
pytest.param(
"NOT_A_PACKAGE",
None,
id="missing_package__returns_None",
),
pytest.param(
"isort",
["isort"],
id="package_exposes_nothing__can_still_infer_import_name",
),
pytest.param(
"pip",
["pip"],
id="package_exposes_one_entry__returns_entry",
),
pytest.param(
"setuptools",
["_distutils_hack", "pkg_resources", "setuptools"],
id="package_exposes_many_entries__returns_all_entries",
),
],
)
def test_find_import_names_from_package_name(dep_name, expect_import_names):
assert find_import_names_from_package_name(dep_name) == expect_import_names