Skip to content

Commit

Permalink
feat(module) use sys.stdlib_module_names to get stdlibs in Python >…
Browse files Browse the repository at this point in the history
…= 3.10 (#275)

* refactor(scripts): only generate stdlibs for Python < 3.10

* feat(module): use `sys.stdlib_module_names` to get stdlibs in Python >= 3.10

* perf: retrieve stdlib modules only once

* test(core): rework tests following stdlib retrieval changes

* chore(mypy): set `explicit_package_bases`
  • Loading branch information
mkniewallner authored Jan 23, 2023
1 parent 1a9e1fa commit 4c72b74
Show file tree
Hide file tree
Showing 16 changed files with 822 additions and 1,221 deletions.
21 changes: 20 additions & 1 deletion deptry/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from deptry.module import Module, ModuleBuilder
from deptry.python_file_finder import PythonFileFinder
from deptry.result_logger import ResultLogger
from deptry.stdlibs import STDLIBS_PYTHON


@dataclass
Expand Down Expand Up @@ -58,10 +59,15 @@ def run(self) -> None:
).get_all_python_files_in(self.root)

local_modules = self._get_local_modules()
stdlib_modules = self._get_stdlib_modules()

imported_modules = [
ModuleBuilder(
mod, local_modules, dependencies_extract.dependencies, dependencies_extract.dev_dependencies
mod,
local_modules,
stdlib_modules,
dependencies_extract.dependencies,
dependencies_extract.dev_dependencies,
).build()
for mod in get_imported_modules_for_list_of_files(all_python_files)
]
Expand Down Expand Up @@ -110,6 +116,19 @@ def _get_local_modules(self) -> set[str]:

return guessed_local_modules | set(self.known_first_party)

@staticmethod
def _get_stdlib_modules() -> frozenset[str]:
if sys.version_info[:2] >= (3, 10):
return sys.stdlib_module_names

try: # type: ignore[unreachable]
return STDLIBS_PYTHON[f"{sys.version_info[0]}{sys.version_info[1]}"]
except KeyError as e:
raise ValueError(
f"Python version {sys.version_info[0]}.{sys.version_info[1]} is not supported. Only versions >= 3.7 are"
" supported."
) from e

def _log_config(self) -> None:
logging.debug("Running with the following configuration:")
for key, value in vars(self).items():
Expand Down
39 changes: 12 additions & 27 deletions deptry/module.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import logging
import sys
from dataclasses import dataclass

from deptry.compat import PackageNotFoundError, metadata
Expand Down Expand Up @@ -39,6 +38,7 @@ def __init__(
self,
name: str,
local_modules: set[str],
stdlib_modules: frozenset[str],
dependencies: list[Dependency] | None = None,
dev_dependencies: list[Dependency] | None = None,
) -> None:
Expand All @@ -47,11 +47,14 @@ def __init__(
Args:
name: The name of the imported module
local_modules: The list of local modules
stdlib_modules: The list of Python stdlib modules
dependencies: A list of the project's dependencies
dev-dependencies: A list of the project's development dependencies
"""
self.name = name
self.local_modules = local_modules
self.stdlib_modules = stdlib_modules
self.dependencies = dependencies or []
self.dev_dependencies = dev_dependencies or []

Expand Down Expand Up @@ -95,9 +98,11 @@ def _get_package_name_from_metadata(self) -> str | None:

def _get_corresponding_top_levels_from(self, dependencies: list[Dependency]) -> list[str]:
"""
Not all modules have associated metadata. e.g. `mpl_toolkits` from `matplotlib` has no metadata. However, it is in the
top-level module names of package matplotlib. This function extracts all dependencies which have this module in their top-level module names.
This can be multiple. e.g. `google-cloud-api` and `google-cloud-bigquery` both have `google` in their top-level module names.
Not all modules have associated metadata. e.g. `mpl_toolkits` from `matplotlib` has no metadata. However, it is
in the top-level module names of package matplotlib. This function extracts all dependencies which have this
module in their top-level module names.
This can be multiple, e.g. `google-cloud-api` and `google-cloud-bigquery` both have `google` in their top-level
module names.
"""
return [
dependency.name
Expand All @@ -106,27 +111,7 @@ def _get_corresponding_top_levels_from(self, dependencies: list[Dependency]) ->
]

def _in_standard_library(self) -> bool:
return self.name in self._get_stdlib_packages()

@staticmethod
def _get_stdlib_packages() -> set[str]:
if sys.version_info[:2] == (3, 7):
from deptry.stdlibs.py37 import stdlib
elif sys.version_info[:2] == (3, 8):
from deptry.stdlibs.py38 import stdlib
elif sys.version_info[:2] == (3, 9):
from deptry.stdlibs.py39 import stdlib
elif sys.version_info[:2] == (3, 10):
from deptry.stdlibs.py310 import stdlib
elif sys.version_info[:2] == (3, 11):
from deptry.stdlibs.py311 import stdlib
else:
raise ValueError(
f"Python version {'.'.join([str(x) for x in sys.version_info[0:3]])} is not supported. Only 3.7, 3.8,"
" 3.9, 3.10 and 3.11 are currently supported."
)

return stdlib
return self.name in self.stdlib_modules

def _is_local_module(self) -> bool:
"""
Expand All @@ -136,8 +121,8 @@ def _is_local_module(self) -> bool:

def _has_matching_dependency(self, package: str | None, top_levels: list[str]) -> bool:
"""
Check if this module is provided by a listed dependency. This is the case if either the package name that was found in the metadata is
listed as a dependency, or if the we found a top-level module name match earlier.
Check if this module is provided by a listed dependency. This is the case if either the package name that was
found in the metadata is listed as a dependency, or if we found a top-level module name match earlier.
"""
return package and (package in [dep.name for dep in self.dependencies]) or len(top_levels) > 0

Expand Down
Loading

0 comments on commit 4c72b74

Please sign in to comment.