Skip to content

Commit 26e63fd

Browse files
committed
Introduce a new set-based function stdlib_modules()
1 parent 913bae7 commit 26e63fd

File tree

3 files changed

+50
-24
lines changed

3 files changed

+50
-24
lines changed

stdlib_list/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
long_versions,
88
short_versions,
99
stdlib_list,
10+
stdlib_modules,
1011
)
1112

1213
__all__ = [

stdlib_list/base.py

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
import os
44
import pkgutil
55
import sys
6-
from functools import lru_cache
76

8-
long_versions = [
7+
long_versions = {
98
"2.6.9",
109
"2.7.9",
1110
"3.2.6",
@@ -21,9 +20,11 @@
2120
"3.12",
2221
"3.13",
2322
"3.14",
24-
]
23+
}
2524

26-
short_versions = [".".join(x.split(".")[:2]) for x in long_versions]
25+
short_versions = {".".join(x.split(".")[:2]) for x in long_versions}
26+
27+
_cached_modules: dict[str, frozenset[str]] = {}
2728

2829

2930
def get_canonical_version(version: str) -> str:
@@ -35,19 +36,22 @@ def get_canonical_version(version: str) -> str:
3536
return version
3637

3738

38-
def stdlib_list(version: str | None = None) -> list[str]:
39+
def stdlib_modules(version: str | None = None) -> frozenset[str]:
3940
"""
40-
Given a ``version``, return a ``list`` of names of the Python Standard
41-
Libraries for that version.
41+
Given a ``version``, return a ``frozenset`` of names of the modules in
42+
the Python Standard Library for that version.
4243
43-
:param str|None version: The version (as a string) whose list of libraries you want
44+
:param str|None version:
45+
The version (as a string) whose list of libraries you want
4446
(formatted as ``X.Y``, e.g. ``"2.7"`` or ``"3.10"``).
4547
4648
If not specified, the current version of Python will be used.
4749
48-
:return: A list of standard libraries from the specified version of Python
49-
:rtype: list
50+
:return: The names of standard library modules from the given Python version
51+
:rtype: frozenset
5052
"""
53+
if version in _cached_modules:
54+
return _cached_modules[version]
5155

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

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

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

62-
result = [y for x in data.splitlines() if (y := x.strip())]
67+
result = frozenset({y for x in lines if (y := x.strip())})
68+
_cached_modules[version] = result
6369

6470
return result
6571

6672

67-
@lru_cache(maxsize=16)
68-
def _stdlib_list_with_cache(version: str | None = None) -> frozenset[str]:
69-
"""Internal cached version of `stdlib_list`"""
70-
return frozenset(stdlib_list(version=version))
73+
def stdlib_list(version: str | None = None) -> list[str]:
74+
"""
75+
Given a ``version``, return a ``list`` of names of the Python Standard
76+
Libraries for that version.
77+
78+
:param str|None version: The version (as a string) whose list of libraries you want
79+
(formatted as ``X.Y``, e.g. ``"2.7"`` or ``"3.10"``).
80+
81+
If not specified, the current version of Python will be used.
82+
83+
:return: A list of standard libraries from the specified version of Python
84+
:rtype: list
85+
"""
86+
return sorted(stdlib_modules(version))
7187

7288

7389
def in_stdlib(module_name: str, version: str | None = None) -> bool:
@@ -79,10 +95,6 @@ def in_stdlib(module_name: str, version: str | None = None) -> bool:
7995
Note that ``True`` will be returned for built-in modules too, since this project
8096
considers they are part of stdlib. See :issue:21.
8197
82-
It relies on ``@lru_cache`` to cache the stdlib list and query results for similar
83-
calls. Therefore it is much more efficient than ``module_name in stdlib_list()``
84-
especially if you wish to perform multiple checks.
85-
8698
:param str|None module_name: The module name (as a string) to query for.
8799
:param str|None version: The version (as a string) whose list of libraries you want
88100
(formatted as ``X.Y``, e.g. ``"2.7"`` or ``"3.10"``).
@@ -91,7 +103,7 @@ def in_stdlib(module_name: str, version: str | None = None) -> bool:
91103
92104
:return: A bool indicating if the given module name is part of standard libraries
93105
for the specified version of Python.
94-
:rtype: list
106+
:rtype: bool
95107
"""
96-
ref_list = _stdlib_list_with_cache(version=version)
108+
ref_list = stdlib_modules(version=version)
97109
return module_name in ref_list

tests/test_base.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,22 @@ def test_get_canonical_version_raises(version):
2020

2121

2222
@pytest.mark.parametrize("version", [*stdlib_list.short_versions, *stdlib_list.long_versions])
23-
def test_self_consistent(version):
23+
def test_self_consistent_unordered(version):
2424
list_path = f"lists/{stdlib_list.get_canonical_version(version)}.txt"
25-
modules = resources.files("stdlib_list").joinpath(list_path).read_text().splitlines()
25+
file = resources.files("stdlib_list") / list_path
26+
modules = frozenset(file.read_text(encoding='utf-8').splitlines())
27+
28+
for mod_name in modules:
29+
assert stdlib_list.in_stdlib(mod_name, version)
30+
31+
assert modules == stdlib_list.stdlib_modules(version)
32+
33+
34+
@pytest.mark.parametrize("version", [*stdlib_list.short_versions, *stdlib_list.long_versions])
35+
def test_self_consistent_ordered(version):
36+
list_path = f"lists/{stdlib_list.get_canonical_version(version)}.txt"
37+
file = resources.files("stdlib_list") / list_path
38+
modules = list(file.read_text(encoding='utf-8').splitlines())
2639

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

0 commit comments

Comments
 (0)