From 26e63fd020757c1c52bc71c81805864bfac2771a Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 18 Aug 2025 15:06:51 +0100 Subject: [PATCH 1/3] Introduce a new set-based function ``stdlib_modules()`` --- stdlib_list/__init__.py | 1 + stdlib_list/base.py | 56 +++++++++++++++++++++++++---------------- tests/test_base.py | 17 +++++++++++-- 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/stdlib_list/__init__.py b/stdlib_list/__init__.py index e0baaa5..d1f667a 100644 --- a/stdlib_list/__init__.py +++ b/stdlib_list/__init__.py @@ -7,6 +7,7 @@ long_versions, short_versions, stdlib_list, + stdlib_modules, ) __all__ = [ diff --git a/stdlib_list/base.py b/stdlib_list/base.py index 31855f6..ab117c2 100644 --- a/stdlib_list/base.py +++ b/stdlib_list/base.py @@ -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", @@ -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: @@ -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) @@ -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: @@ -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"``). @@ -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 diff --git a/tests/test_base.py b/tests/test_base.py index fb62922..80bd598 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -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) From 1a80e3324e15ec4e989bf988a93208125bc5a740 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 18 Aug 2025 15:17:32 +0100 Subject: [PATCH 2/3] lint --- tests/test_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_base.py b/tests/test_base.py index 80bd598..bbba153 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -23,7 +23,7 @@ def test_get_canonical_version_raises(version): def test_self_consistent_unordered(version): list_path = f"lists/{stdlib_list.get_canonical_version(version)}.txt" file = resources.files("stdlib_list") / list_path - modules = frozenset(file.read_text(encoding='utf-8').splitlines()) + modules = frozenset(file.read_text(encoding="utf-8").splitlines()) for mod_name in modules: assert stdlib_list.in_stdlib(mod_name, version) @@ -35,7 +35,7 @@ def test_self_consistent_unordered(version): 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()) + modules = list(file.read_text(encoding="utf-8").splitlines()) for mod_name in modules: assert stdlib_list.in_stdlib(mod_name, version) From b58fd605d3d4eea43038bc548a58293294aab579 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 18 Aug 2025 15:21:15 +0100 Subject: [PATCH 3/3] lint --- stdlib_list/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib_list/__init__.py b/stdlib_list/__init__.py index d1f667a..690472f 100644 --- a/stdlib_list/__init__.py +++ b/stdlib_list/__init__.py @@ -11,6 +11,7 @@ ) __all__ = [ + "stdlib_modules", "stdlib_list", "in_stdlib", "get_canonical_version",