.. versionadded:: 3.11
+.. option:: --with-missing-stdlib-config=FILE
+
+ Path to a `JSON <https://www.json.org/json-en.html>`_ configuration file
+ containing custom error messages for missing :term:`standard library` modules.
+
+ This option is intended for Python distributors who wish to provide
+ distribution-specific guidance when users encounter standard library
+ modules that are missing or packaged separately.
+
+ The JSON file should map missing module names to custom error message strings.
+ For example, if your distribution packages :mod:`tkinter` and
+ :mod:`_tkinter` separately and excludes :mod:`!_gdbm` for legal reasons,
+ the configuration could contain:
+
+ .. code-block:: json
+
+ {
+ "_gdbm": "The '_gdbm' module is not available in this distribution"
+ "tkinter": "Install the python-tk package to use tkinter",
+ "_tkinter": "Install the python-tk package to use tkinter",
+ }
+
+ .. versionadded:: next
+
.. option:: --enable-pystats
Turn on internal Python performance statistics gathering.
set to ``no`` or with :option:`!--without-system-libmpdec`.
(Contributed by Sergey B Kirpichev in :gh:`115119`.)
+* The new configure option :option:`--with-missing-stdlib-config=FILE` allows
+ distributors to pass a `JSON <https://www.json.org/json-en.html>`_
+ configuration file containing custom error messages for :term:`standard library`
+ modules that are missing or packaged separately.
+ (Contributed by Stan Ulbrych and Petr Viktorin in :gh:`139707`.)
+
Porting to Python 3.15
======================
b"or to enable your virtual environment?"), stderr
)
- def test_missing_stdlib_package(self):
+ def test_missing_stdlib_module(self):
code = """
import sys
sys.stdlib_module_names |= {'spam'}
self.assertIn(b"Standard library module 'spam' was not found", stderr)
+ code = """
+ import sys
+ import traceback
+ traceback._MISSING_STDLIB_MODULE_MESSAGES = {'spam': "Install 'spam4life' for 'spam'"}
+ sys.stdlib_module_names |= {'spam'}
+ import spam
+ """
+ _, _, stderr = assert_python_failure('-S', '-c', code)
+
+ self.assertIn(b"Install 'spam4life' for 'spam'", stderr)
+
+ @unittest.skipIf(sys.platform == "win32", "Non-Windows test")
+ def test_windows_only_module_error(self):
+ try:
+ import msvcrt # noqa: F401
+ except ModuleNotFoundError:
+ formatted = traceback.format_exc()
+ self.assertIn("Unsupported platform for Windows-only standard library module 'msvcrt'", formatted)
+ else:
+ self.fail("ModuleNotFoundError was not raised")
+
class TestColorizedTraceback(unittest.TestCase):
maxDiff = None
from contextlib import suppress
+try:
+ from _missing_stdlib_info import _MISSING_STDLIB_MODULE_MESSAGES
+except ImportError:
+ _MISSING_STDLIB_MODULE_MESSAGES = {}
+
__all__ = ['extract_stack', 'extract_tb', 'format_exception',
'format_exception_only', 'format_list', 'format_stack',
'format_tb', 'print_exc', 'format_exc', 'print_exception',
elif exc_type and issubclass(exc_type, ModuleNotFoundError):
module_name = getattr(exc_value, "name", None)
if module_name in sys.stdlib_module_names:
- self._str = f"Standard library module '{module_name}' was not found"
+ message = _MISSING_STDLIB_MODULE_MESSAGES.get(
+ module_name,
+ f"Standard library module {module_name!r} was not found"
+ )
+ self._str = message
elif sys.flags.no_site:
self._str += (". Site initialization is disabled, did you forget to "
+ "add the site-packages directory to sys.path "
# dependency on BUILDPYTHON ensures that the target is run last
.PHONY: checksharedmods
checksharedmods: sharedmods $(PYTHON_FOR_BUILD_DEPS) $(BUILDPYTHON)
+ @if [ -n "@MISSING_STDLIB_CONFIG@" ]; then \
+ $(RUNSHARED) $(PYTHON_FOR_BUILD) $(srcdir)/Tools/build/check_extension_modules.py --generate-missing-stdlib-info --with-missing-stdlib-config="@MISSING_STDLIB_CONFIG@"; \
+ else \
+ $(RUNSHARED) $(PYTHON_FOR_BUILD) $(srcdir)/Tools/build/check_extension_modules.py --generate-missing-stdlib-info; \
+ fi
@$(RUNSHARED) $(PYTHON_FOR_BUILD) $(srcdir)/Tools/build/check_extension_modules.py
.PHONY: rundsymutil
$(INSTALL_DATA) `cat pybuilddir.txt`/_sysconfigdata_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).py $(DESTDIR)$(LIBDEST); \
$(INSTALL_DATA) `cat pybuilddir.txt`/_sysconfig_vars_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).json $(DESTDIR)$(LIBDEST); \
$(INSTALL_DATA) `cat pybuilddir.txt`/build-details.json $(DESTDIR)$(LIBDEST); \
+ $(INSTALL_DATA) `cat pybuilddir.txt`/_missing_stdlib_info.py $(DESTDIR)$(LIBDEST); \
$(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt
@ # If app store compliance has been configured, apply the patch to the
@ # installed library code. The patch has been previously validated against
--- /dev/null
+Add configure option :option:`--with-missing-stdlib-config=FILE` allows
+which distributors to pass a `JSON <https://www.json.org/json-en.html>`_
+configuration file containing custom error messages for missing
+:term:`standard library` modules.
import _imp
import argparse
import enum
+import json
import logging
import os
import pathlib
+import pprint
import re
import sys
import sysconfig
help="Print a list of module names to stdout and exit",
)
+parser.add_argument(
+ "--generate-missing-stdlib-info",
+ action="store_true",
+ help="Generate file with stdlib module info",
+)
+
+parser.add_argument(
+ "--with-missing-stdlib-config",
+ metavar="CONFIG_FILE",
+ help="Path to JSON config file with custom missing module messages",
+)
+
@enum.unique
class ModuleState(enum.Enum):
names.update(WINDOWS_MODULES)
return names
+ def generate_missing_stdlib_info(self, config_path: str | None = None) -> None:
+ config_messages = {}
+ if config_path:
+ try:
+ with open(config_path, encoding='utf-8') as f:
+ config_messages = json.load(f)
+ except (FileNotFoundError, json.JSONDecodeError) as e:
+ raise RuntimeError(f"Failed to load missing stdlib config {config_path!r}") from e
+
+ messages = {}
+ for name in WINDOWS_MODULES:
+ messages[name] = f"Unsupported platform for Windows-only standard library module {name!r}"
+
+ for modinfo in self.modules:
+ if modinfo.state in (ModuleState.DISABLED, ModuleState.DISABLED_SETUP):
+ messages[modinfo.name] = f"Standard library module disabled during build {modinfo.name!r} was not found"
+ elif modinfo.state == ModuleState.NA:
+ messages[modinfo.name] = f"Unsupported platform for standard library module {modinfo.name!r}"
+
+ messages.update(config_messages)
+
+ content = f'''\
+# Standard library information used by the traceback module for more informative
+# ModuleNotFound error messages.
+# Generated by check_extension_modules.py
+
+_MISSING_STDLIB_MODULE_MESSAGES = {pprint.pformat(messages)}
+'''
+
+ output_path = self.builddir / "_missing_stdlib_info.py"
+ with open(output_path, "w", encoding="utf-8") as f:
+ f.write(content)
+
def get_builddir(self) -> pathlib.Path:
try:
with open(self.pybuilddir_txt, encoding="utf-8") as f:
names = checker.list_module_names(all=True)
for name in sorted(names):
print(name)
+ elif args.generate_missing_stdlib_info:
+ checker.check()
+ checker.generate_missing_stdlib_info(args.with_missing_stdlib_config)
else:
checker.check()
checker.summary(verbose=args.verbose)
host_exec_prefix
host_prefix
MACHDEP
+MISSING_STDLIB_CONFIG
PKG_CONFIG_LIBDIR
PKG_CONFIG_PATH
PKG_CONFIG
enable_option_checking
with_build_python
with_pkg_config
+with_missing_stdlib_config
enable_universalsdk
with_universal_archs
with_framework_name
--with-pkg-config=[yes|no|check]
use pkg-config to detect build options (default is
check)
+ --with-missing-stdlib-config=FILE
+ File with custom module error messages for missing
+ stdlib modules
--with-universal-archs=ARCH
specify the kind of macOS universal binary that
should be created. This option is only valid when
as_fn_error $? "pkg-config is required" "$LINENO" 5]
fi
+
+# Check whether --with-missing-stdlib-config was given.
+if test ${with_missing_stdlib_config+y}
+then :
+ withval=$with_missing_stdlib_config; MISSING_STDLIB_CONFIG="$withval"
+else case e in #(
+ e) MISSING_STDLIB_CONFIG=""
+ ;;
+esac
+fi
+
+
+
# Set name for machine-dependent library files
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking MACHDEP" >&5
AC_MSG_ERROR([pkg-config is required])]
fi
+dnl Allow distributors to provide custom missing stdlib module error messages
+AC_ARG_WITH([missing-stdlib-config],
+ [AS_HELP_STRING([--with-missing-stdlib-config=FILE],
+ [File with custom module error messages for missing stdlib modules])],
+ [MISSING_STDLIB_CONFIG="$withval"],
+ [MISSING_STDLIB_CONFIG=""]
+)
+AC_SUBST([MISSING_STDLIB_CONFIG])
+
# Set name for machine-dependent library files
AC_ARG_VAR([MACHDEP], [name for machine-dependent library files])
AC_MSG_CHECKING([MACHDEP])