Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions stdlib_list/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
long_versions,
short_versions,
stdlib_list,
stdlib_modules,
)

__all__ = [
"stdlib_modules",
"stdlib_list",
"in_stdlib",
"get_canonical_version",
Expand Down
56 changes: 34 additions & 22 deletions stdlib_list/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
import os
import pkgutil
import sys
from functools import lru_cache

long_versions = [
long_versions = {
"2.6.9",
"2.7.9",
"3.2.6",
Expand All @@ -21,9 +20,11 @@
"3.12",
"3.13",
"3.14",
]
}

short_versions = [".".join(x.split(".")[:2]) for x in long_versions]
short_versions = {".".join(x.split(".")[:2]) for x in long_versions}

_cached_modules: dict[str, frozenset[str]] = {}


def get_canonical_version(version: str) -> str:
Expand All @@ -35,19 +36,22 @@ def get_canonical_version(version: str) -> str:
return version


def stdlib_list(version: str | None = None) -> list[str]:
def stdlib_modules(version: str | None = None) -> frozenset[str]:
"""
Given a ``version``, return a ``list`` of names of the Python Standard
Libraries for that version.
Given a ``version``, return a ``frozenset`` of names of the modules in
the Python Standard Library for that version.

:param str|None version: The version (as a string) whose list of libraries you want
:param str|None version:
The version (as a string) whose list of libraries you want
(formatted as ``X.Y``, e.g. ``"2.7"`` or ``"3.10"``).

If not specified, the current version of Python will be used.

:return: A list of standard libraries from the specified version of Python
:rtype: list
:return: The names of standard library modules from the given Python version
:rtype: frozenset
"""
if version in _cached_modules:
return _cached_modules[version]

version = (
get_canonical_version(version)
Expand All @@ -57,17 +61,29 @@ def stdlib_list(version: str | None = None) -> list[str]:

module_list_file = os.path.join("lists", f"{version}.txt")

data = pkgutil.get_data("stdlib_list", module_list_file).decode() # type: ignore[union-attr]
data = pkgutil.get_data("stdlib_list", module_list_file)
lines = data.decode().splitlines() if data else []

result = [y for x in data.splitlines() if (y := x.strip())]
result = frozenset({y for x in lines if (y := x.strip())})
_cached_modules[version] = result

return result


@lru_cache(maxsize=16)
def _stdlib_list_with_cache(version: str | None = None) -> frozenset[str]:
"""Internal cached version of `stdlib_list`"""
return frozenset(stdlib_list(version=version))
def stdlib_list(version: str | None = None) -> list[str]:
"""
Given a ``version``, return a ``list`` of names of the Python Standard
Libraries for that version.

:param str|None version: The version (as a string) whose list of libraries you want
(formatted as ``X.Y``, e.g. ``"2.7"`` or ``"3.10"``).

If not specified, the current version of Python will be used.

:return: A list of standard libraries from the specified version of Python
:rtype: list
"""
return sorted(stdlib_modules(version))


def in_stdlib(module_name: str, version: str | None = None) -> bool:
Expand All @@ -79,10 +95,6 @@ def in_stdlib(module_name: str, version: str | None = None) -> bool:
Note that ``True`` will be returned for built-in modules too, since this project
considers they are part of stdlib. See :issue:21.

It relies on ``@lru_cache`` to cache the stdlib list and query results for similar
calls. Therefore it is much more efficient than ``module_name in stdlib_list()``
especially if you wish to perform multiple checks.

:param str|None module_name: The module name (as a string) to query for.
:param str|None version: The version (as a string) whose list of libraries you want
(formatted as ``X.Y``, e.g. ``"2.7"`` or ``"3.10"``).
Expand All @@ -91,7 +103,7 @@ def in_stdlib(module_name: str, version: str | None = None) -> bool:

:return: A bool indicating if the given module name is part of standard libraries
for the specified version of Python.
:rtype: list
:rtype: bool
"""
ref_list = _stdlib_list_with_cache(version=version)
ref_list = stdlib_modules(version=version)
return module_name in ref_list
17 changes: 15 additions & 2 deletions tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,22 @@ def test_get_canonical_version_raises(version):


@pytest.mark.parametrize("version", [*stdlib_list.short_versions, *stdlib_list.long_versions])
def test_self_consistent(version):
def test_self_consistent_unordered(version):
list_path = f"lists/{stdlib_list.get_canonical_version(version)}.txt"
modules = resources.files("stdlib_list").joinpath(list_path).read_text().splitlines()
file = resources.files("stdlib_list") / list_path
modules = frozenset(file.read_text(encoding="utf-8").splitlines())

for mod_name in modules:
assert stdlib_list.in_stdlib(mod_name, version)

assert modules == stdlib_list.stdlib_modules(version)


@pytest.mark.parametrize("version", [*stdlib_list.short_versions, *stdlib_list.long_versions])
def test_self_consistent_ordered(version):
list_path = f"lists/{stdlib_list.get_canonical_version(version)}.txt"
file = resources.files("stdlib_list") / list_path
modules = list(file.read_text(encoding="utf-8").splitlines())

for mod_name in modules:
assert stdlib_list.in_stdlib(mod_name, version)
Expand Down