From e7e1ea49d8a629e5a2b9fff9eeea503620fac807 Mon Sep 17 00:00:00 2001 From: David C Ellis Date: Mon, 5 Oct 2026 10:24:56 +0100 Subject: [PATCH 01/10] Make the argument name in documentation for `forget` match the name in the code. --- Doc/library/test.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/test.rst b/Doc/library/test.rst index 765811eba32fd40..d823e55918485b2 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1694,9 +1694,9 @@ The :mod:`!test.support.import_helper` module provides support for import tests. .. versionadded:: 3.10 -.. function:: forget(module_name) +.. function:: forget(modname) - Remove the module named *module_name* from ``sys.modules`` and delete any + Remove the module named *modname* from ``sys.modules`` and delete any byte-compiled files of the module. From a9dcbc56cc6c0eef26fad96f0d9b3d630f77592e Mon Sep 17 00:00:00 2001 From: David C Ellis Date: Mon, 5 Oct 2026 11:16:21 +0100 Subject: [PATCH 02/10] Document the `usefrozen` argument --- Doc/library/test.rst | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Doc/library/test.rst b/Doc/library/test.rst index d823e55918485b2..1711645a3ff370e 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1700,7 +1700,7 @@ The :mod:`!test.support.import_helper` module provides support for import tests. byte-compiled files of the module. -.. function:: import_fresh_module(name, fresh=(), blocked=(), deprecated=False) +.. function:: import_fresh_module(name, fresh=(), blocked=(), *, deprecated=False, usefrozen=False) This function imports and returns a fresh copy of the named Python module by removing the named module from ``sys.modules`` before doing the import. @@ -1721,6 +1721,9 @@ The :mod:`!test.support.import_helper` module provides support for import tests. Module and package deprecation messages are suppressed during this import if *deprecated* is ``True``. + If *usefrozen* is False (the default) then the frozen importer is + disabled (except for essential modules like importlib._bootstrap). + This function will raise :exc:`ImportError` if the named module cannot be imported. @@ -1774,11 +1777,16 @@ The :mod:`!test.support.import_helper` module provides support for import tests. 3147/488 pyc file must exist. -.. class:: CleanImport(*module_names) +.. class:: CleanImport(*module_names, usefrozen=False) A context manager to force import to return a new module reference. This is useful for testing module-level behaviors, such as the emission of a - :exc:`DeprecationWarning` on import. Example usage:: + :exc:`DeprecationWarning` on import. + + If *usefrozen* is False (the default) then the frozen importer is + disabled (except for essential modules like importlib._bootstrap). + + Example usage:: with CleanImport('foo'): importlib.import_module('foo') # New reference. From e2169fcb33da1facc4b9f05fe503dc21f17361ff Mon Sep 17 00:00:00 2001 From: David C Ellis Date: Mon, 5 Oct 2026 13:45:19 +0100 Subject: [PATCH 03/10] Wrap importlib._bootstrap module name in code backticks --- Doc/library/test.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/test.rst b/Doc/library/test.rst index 1711645a3ff370e..8db5c3f61a6c410 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1722,7 +1722,7 @@ The :mod:`!test.support.import_helper` module provides support for import tests. if *deprecated* is ``True``. If *usefrozen* is False (the default) then the frozen importer is - disabled (except for essential modules like importlib._bootstrap). + disabled (except for essential modules like ``importlib._bootstrap``). This function will raise :exc:`ImportError` if the named module cannot be imported. @@ -1784,7 +1784,7 @@ The :mod:`!test.support.import_helper` module provides support for import tests. :exc:`DeprecationWarning` on import. If *usefrozen* is False (the default) then the frozen importer is - disabled (except for essential modules like importlib._bootstrap). + disabled (except for essential modules like ``importlib._bootstrap``). Example usage:: From 60d867ed8e8b1bb5f4ef216aff1df95d30d3f9b7 Mon Sep 17 00:00:00 2001 From: David C Ellis Date: Mon, 5 Oct 2026 13:51:33 +0100 Subject: [PATCH 04/10] Document allow_compile on `make_legacy_pyc`. --- Doc/library/test.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Doc/library/test.rst b/Doc/library/test.rst index 8db5c3f61a6c410..d4c9c352d28316f 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1769,12 +1769,14 @@ The :mod:`!test.support.import_helper` module provides support for import tests. Delete *name* from ``sys.modules``. -.. function:: make_legacy_pyc(source) +.. function:: make_legacy_pyc(source, allow_compile=False) Move a :pep:`3147`/:pep:`488` pyc file to its legacy pyc location and return the file system path to the legacy pyc file. The *source* value is the file system path to the source file. It does not need to exist, however the PEP - 3147/488 pyc file must exist. + 3147/488 pyc file must exist or *allow_compile* must be set. + + *allow_compile* will create a .pyc file if it does not exist. .. class:: CleanImport(*module_names, usefrozen=False) From 39b089c8cd6172f12244040b22097e7b55212504 Mon Sep 17 00:00:00 2001 From: David C Ellis Date: Mon, 5 Oct 2026 14:56:46 +0100 Subject: [PATCH 05/10] Document what CleanImport actually does to distinguish from other similar tools. --- Doc/library/test.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/test.rst b/Doc/library/test.rst index d4c9c352d28316f..a18e602857fa1b1 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1785,6 +1785,10 @@ The :mod:`!test.support.import_helper` module provides support for import tests. is useful for testing module-level behaviors, such as the emission of a :exc:`DeprecationWarning` on import. + When created, this makes a copy of :data:`sys.modules` and removes names from + *module_names* from the original. On exit, the original module references are + restored. + If *usefrozen* is False (the default) then the frozen importer is disabled (except for essential modules like ``importlib._bootstrap``). From ce53ea1de91200af5fd847ad063b45ca78740261 Mon Sep 17 00:00:00 2001 From: David C Ellis Date: Mon, 5 Oct 2026 16:28:54 +0100 Subject: [PATCH 06/10] Add documentation for missing functions --- Doc/library/test.rst | 95 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 5 deletions(-) diff --git a/Doc/library/test.rst b/Doc/library/test.rst index a18e602857fa1b1..ec8cab312e8cf13 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1696,19 +1696,19 @@ The :mod:`!test.support.import_helper` module provides support for import tests. .. function:: forget(modname) - Remove the module named *modname* from ``sys.modules`` and delete any + Remove the module named *modname* from :data:`sys.modules` and delete any byte-compiled files of the module. .. function:: import_fresh_module(name, fresh=(), blocked=(), *, deprecated=False, usefrozen=False) This function imports and returns a fresh copy of the named Python module - by removing the named module from ``sys.modules`` before doing the import. + by removing the named module from :data:`sys.modules` before doing the import. Note that unlike :func:`reload`, the original module is not affected by this operation. *fresh* is an iterable of additional module names that are also removed - from the ``sys.modules`` cache before doing the import. + from the :data:`sys.modules` cache before doing the import. *blocked* is an iterable of module names that are replaced with ``None`` in the module cache during the import to ensure that attempts to import @@ -1716,7 +1716,7 @@ The :mod:`!test.support.import_helper` module provides support for import tests. The named module and any modules named in the *fresh* and *blocked* parameters are saved before starting the import and then reinserted into - ``sys.modules`` when the fresh import is complete. + :data:`sys.modules` when the fresh import is complete. Module and package deprecation messages are suppressed during this import if *deprecated* is ``True``. @@ -1766,7 +1766,7 @@ The :mod:`!test.support.import_helper` module provides support for import tests. .. function:: unload(name) - Delete *name* from ``sys.modules``. + Delete *name* from :data:`sys.modules`. .. function:: make_legacy_pyc(source, allow_compile=False) @@ -1811,6 +1811,91 @@ The :mod:`!test.support.import_helper` module provides support for import tests. will be reverted at the end of the block. +.. function:: create_module(name, loader=None, *, ispkg=False) + + Create a new, empty :term:`module` or :term:`package` and return it. + + *name*, *loader* and *ispkg* (as ``is_package``) are passed to + :class:`importlib.machinery.ModuleSpec`. + + +.. function:: add_module(spec, *, parents=True) + + Create a :term:`module` from a name or spec and add it to :data:`sys.modules`. + + If *parents* is ``True`` then also create any missing parent modules. + + Return the new module. + + +.. function:: add_package(spec, *, parents=True) + + Create a :term:`package` from a name and add it to :data:`sys.modules`. + + If *parents* is ``True`` then also create any missing parent modules. + + Return the new package. + + +.. function:: ensure_module_imported(name, *, clearnone=True) + + Import and return the named module. + + If the module is already imported, this is returned. Otherwise, import + the module and return it. If the module is not found a new, empty module + will be created. + + If *clearnone* is ``True``, this will first remove any ``None`` values that + would block the import. + + +.. function:: frozen_modules(enabled=True) + + A context manager that forces frozen modules to be used or excluded. + + This only applies to modules that have not yet been imported. Some essential + modules will always be imported frozen. + + +.. function:: isolated_modules() + + A context manager that makes a copy of :data:`sys.modules` on entry and restores + it on exit. + + +.. function:: multi_interp_extensions_check(enabled=True) + + A context manager that forces (if ``True``) or prevents legacy modules from being + allowed in subinterpreters. + + ("legacy" == single-phase init) + + This only applies to modules that haven't been imported yet. + It overrides the PyInterpreterConfig.check_multi_interp_extensions + setting. + + +.. function:: ready_to_import(name=None, source="") + + A context manager that will create a new python module *name* with *source* + source code in a temporary directory and inserts this directory at the start + of :data:`sys.path`. + + If the name already defines a module, this will be cleared from :data:`sys.modules` + on entry and restored on exit. + + Yields (name, path_to_script) + + +.. function:: ensure_lazy_imports(imported_module, modules_to_block, *, additional_code=None) + + Test that when *imported_module* is imported, none of the modules in *modules_to_block* + are imported as a side effect. + + *additional_code*, if given, should be additional python source code to execute before + checking that the blocked modules still haven't been imported. + + :mod:`!test.support.warnings_helper` --- Utilities for warnings tests ===================================================================== From 4b467522bf9a7e3c24626c1eb5e068701caa5853 Mon Sep 17 00:00:00 2001 From: David C Ellis Date: Mon, 5 Oct 2026 16:29:39 +0100 Subject: [PATCH 07/10] Code block --- Doc/library/test.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/test.rst b/Doc/library/test.rst index ec8cab312e8cf13..78274e5aef85d3a 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1871,7 +1871,7 @@ The :mod:`!test.support.import_helper` module provides support for import tests. ("legacy" == single-phase init) This only applies to modules that haven't been imported yet. - It overrides the PyInterpreterConfig.check_multi_interp_extensions + It overrides the ``PyInterpreterConfig.check_multi_interp_extensions`` setting. From 5e411d7d71746be7f9c5a67b327d80899ba06d48 Mon Sep 17 00:00:00 2001 From: David C Ellis Date: Mon, 5 Oct 2026 16:53:55 +0100 Subject: [PATCH 08/10] Rephrase some sentences --- Doc/library/test.rst | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Doc/library/test.rst b/Doc/library/test.rst index 78274e5aef85d3a..a7582cd0777b891 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1865,10 +1865,8 @@ The :mod:`!test.support.import_helper` module provides support for import tests. .. function:: multi_interp_extensions_check(enabled=True) - A context manager that forces (if ``True``) or prevents legacy modules from being - allowed in subinterpreters. - - ("legacy" == single-phase init) + A context manager that forces (if ``True``) or prevents legacy (single-phase init) + modules from being allowed in subinterpreters. This only applies to modules that haven't been imported yet. It overrides the ``PyInterpreterConfig.check_multi_interp_extensions`` @@ -1892,7 +1890,7 @@ The :mod:`!test.support.import_helper` module provides support for import tests. Test that when *imported_module* is imported, none of the modules in *modules_to_block* are imported as a side effect. - *additional_code*, if given, should be additional python source code to execute before + *additional_code* if given should be additional python source code to execute before checking that the blocked modules still haven't been imported. From 501e633f2869b1a040d14ee25a3b16ebbe6cfe51 Mon Sep 17 00:00:00 2001 From: David C Ellis Date: Mon, 5 Oct 2026 17:31:44 +0100 Subject: [PATCH 09/10] Add headings and order to the import_helper section --- Doc/library/test.rst | 203 +++++++++++++++++++++++-------------------- 1 file changed, 110 insertions(+), 93 deletions(-) diff --git a/Doc/library/test.rst b/Doc/library/test.rst index a7582cd0777b891..985be1918b8906e 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1693,64 +1693,48 @@ The :mod:`!test.support.import_helper` module provides support for import tests. .. versionadded:: 3.10 +Creating Module Objects +----------------------- -.. function:: forget(modname) +.. function:: create_module(name, loader=None, *, ispkg=False) - Remove the module named *modname* from :data:`sys.modules` and delete any - byte-compiled files of the module. + Create a new, empty :term:`module` or :term:`package` and return it. + *name*, *loader* and *ispkg* (as ``is_package``) are passed to + :class:`importlib.machinery.ModuleSpec`. -.. function:: import_fresh_module(name, fresh=(), blocked=(), *, deprecated=False, usefrozen=False) - This function imports and returns a fresh copy of the named Python module - by removing the named module from :data:`sys.modules` before doing the import. - Note that unlike :func:`reload`, the original module is not affected by - this operation. +.. function:: add_module(spec, *, parents=True) - *fresh* is an iterable of additional module names that are also removed - from the :data:`sys.modules` cache before doing the import. + Create a :term:`module` from a name or spec and add it to :data:`sys.modules`. - *blocked* is an iterable of module names that are replaced with ``None`` - in the module cache during the import to ensure that attempts to import - them raise :exc:`ImportError`. + If *parents* is ``True`` then also create any missing parent modules. - The named module and any modules named in the *fresh* and *blocked* - parameters are saved before starting the import and then reinserted into - :data:`sys.modules` when the fresh import is complete. + Return the new module. - Module and package deprecation messages are suppressed during this import - if *deprecated* is ``True``. - If *usefrozen* is False (the default) then the frozen importer is - disabled (except for essential modules like ``importlib._bootstrap``). +.. function:: add_package(spec, *, parents=True) - This function will raise :exc:`ImportError` if the named module cannot be - imported. + Create a :term:`package` from a name and add it to :data:`sys.modules`. - Example use:: + If *parents* is ``True`` then also create any missing parent modules. - # Get copies of the warnings module for testing without affecting the - # version being used by the rest of the test suite. One copy uses the - # C implementation, the other is forced to use the pure Python fallback - # implementation - py_warnings = import_fresh_module('warnings', blocked=['_warnings']) - c_warnings = import_fresh_module('warnings', fresh=['_warnings']) + Return the new package. - .. versionadded:: 3.1 +Manipulating sys.modules +------------------------ -.. function:: import_module(name, deprecated=False, *, required_on=()) - This function imports and returns the named module. Unlike a normal - import, this function raises :exc:`unittest.SkipTest` if the module - cannot be imported. +.. function:: unload(name) - Module and package deprecation messages are suppressed during this import - if *deprecated* is ``True``. If a module is required on a platform but - optional for others, set *required_on* to an iterable of platform prefixes - which will be compared against :data:`sys.platform`. + Remove the module named *name* from :data:`sys.modules`. - .. versionadded:: 3.1 + +.. function:: forget(modname) + + Remove the module named *modname* from :data:`sys.modules` and delete any + byte-compiled files of the module. .. function:: modules_setup() @@ -1760,23 +1744,14 @@ The :mod:`!test.support.import_helper` module provides support for import tests. .. function:: modules_cleanup(oldmodules) - Remove modules except for *oldmodules* and ``encodings`` in order to - preserve internal cache. + Remove modules except for *oldmodules* and ``encodings`` from :data:`sys.modules` + in order to preserve internal cache. -.. function:: unload(name) - - Delete *name* from :data:`sys.modules`. - - -.. function:: make_legacy_pyc(source, allow_compile=False) - - Move a :pep:`3147`/:pep:`488` pyc file to its legacy pyc location and return the file - system path to the legacy pyc file. The *source* value is the file system - path to the source file. It does not need to exist, however the PEP - 3147/488 pyc file must exist or *allow_compile* must be set. +.. function:: isolated_modules() - *allow_compile* will create a .pyc file if it does not exist. + A context manager that makes a copy of :data:`sys.modules` on entry and restores + it on exit. .. class:: CleanImport(*module_names, usefrozen=False) @@ -1795,46 +1770,62 @@ The :mod:`!test.support.import_helper` module provides support for import tests. Example usage:: with CleanImport('foo'): - importlib.import_module('foo') # New reference. - + importlib.import_module('foo') # New reference. -.. class:: DirsOnSysPath(*paths) - A context manager to temporarily add directories to :data:`sys.path`. +Special importers +----------------- - This makes a copy of :data:`sys.path`, appends any directories given - as positional arguments, then reverts :data:`sys.path` to the copied - settings when the context ends. - - Note that *all* :data:`sys.path` modifications in the body of the - context manager, including replacement of the object, - will be reverted at the end of the block. +.. function:: import_module(name, deprecated=False, *, required_on=()) + This function imports and returns the named module. Unlike a normal + import, this function raises :exc:`unittest.SkipTest` if the module + cannot be imported. -.. function:: create_module(name, loader=None, *, ispkg=False) + Module and package deprecation messages are suppressed during this import + if *deprecated* is ``True``. If a module is required on a platform but + optional for others, set *required_on* to an iterable of platform prefixes + which will be compared against :data:`sys.platform`. - Create a new, empty :term:`module` or :term:`package` and return it. + .. versionadded:: 3.1 - *name*, *loader* and *ispkg* (as ``is_package``) are passed to - :class:`importlib.machinery.ModuleSpec`. +.. function:: import_fresh_module(name, fresh=(), blocked=(), *, deprecated=False, usefrozen=False) + This function imports and returns a fresh copy of the named Python module + by removing the named module from :data:`sys.modules` before doing the import. + Note that unlike :func:`reload`, the original module is not affected by + this operation. -.. function:: add_module(spec, *, parents=True) + *fresh* is an iterable of additional module names that are also removed + from the :data:`sys.modules` cache before doing the import. - Create a :term:`module` from a name or spec and add it to :data:`sys.modules`. + *blocked* is an iterable of module names that are replaced with ``None`` + in the module cache during the import to ensure that attempts to import + them raise :exc:`ImportError`. - If *parents* is ``True`` then also create any missing parent modules. + The named module and any modules named in the *fresh* and *blocked* + parameters are saved before starting the import and then reinserted into + :data:`sys.modules` when the fresh import is complete. - Return the new module. + Module and package deprecation messages are suppressed during this import + if *deprecated* is ``True``. + If *usefrozen* is False (the default) then the frozen importer is + disabled (except for essential modules like ``importlib._bootstrap``). -.. function:: add_package(spec, *, parents=True) + This function will raise :exc:`ImportError` if the named module cannot be + imported. - Create a :term:`package` from a name and add it to :data:`sys.modules`. + Example use:: - If *parents* is ``True`` then also create any missing parent modules. + # Get copies of the warnings module for testing without affecting the + # version being used by the rest of the test suite. One copy uses the + # C implementation, the other is forced to use the pure Python fallback + # implementation + py_warnings = import_fresh_module('warnings', blocked=['_warnings']) + c_warnings = import_fresh_module('warnings', fresh=['_warnings']) - Return the new package. + .. versionadded:: 3.1 .. function:: ensure_module_imported(name, *, clearnone=True) @@ -1849,6 +1840,47 @@ The :mod:`!test.support.import_helper` module provides support for import tests. would block the import. +Manipulating sys.path +--------------------- + +.. class:: DirsOnSysPath(*paths) + + A context manager to temporarily add directories to :data:`sys.path`. + + This makes a copy of :data:`sys.path`, appends any directories given + as positional arguments, then reverts :data:`sys.path` to the copied + settings when the context ends. + + Note that *all* :data:`sys.path` modifications in the body of the + context manager, including replacement of the object, + will be reverted at the end of the block. + + +.. function:: ready_to_import(name=None, source="") + + A context manager that will create a new python module *name* with *source* + source code in a temporary directory and inserts this directory at the start + of :data:`sys.path`. + + If the name matches an existing module, this will be cleared from :data:`sys.modules` + on entry and restored on exit. + + Yields (name, path_to_script) + + +Manipulating internals +---------------------- + +.. function:: make_legacy_pyc(source, allow_compile=False) + + Move a :pep:`3147`/:pep:`488` pyc file to its legacy pyc location and return the file + system path to the legacy pyc file. The *source* value is the file system + path to the source file. It does not need to exist, however the PEP + 3147/488 pyc file must exist or *allow_compile* must be set. + + *allow_compile* will create a .pyc file if it does not exist. + + .. function:: frozen_modules(enabled=True) A context manager that forces frozen modules to be used or excluded. @@ -1857,12 +1889,6 @@ The :mod:`!test.support.import_helper` module provides support for import tests. modules will always be imported frozen. -.. function:: isolated_modules() - - A context manager that makes a copy of :data:`sys.modules` on entry and restores - it on exit. - - .. function:: multi_interp_extensions_check(enabled=True) A context manager that forces (if ``True``) or prevents legacy (single-phase init) @@ -1873,17 +1899,8 @@ The :mod:`!test.support.import_helper` module provides support for import tests. setting. -.. function:: ready_to_import(name=None, source="") - - A context manager that will create a new python module *name* with *source* - source code in a temporary directory and inserts this directory at the start - of :data:`sys.path`. - - If the name already defines a module, this will be cleared from :data:`sys.modules` - on entry and restored on exit. - - Yields (name, path_to_script) - +Lazy imports +------------ .. function:: ensure_lazy_imports(imported_module, modules_to_block, *, additional_code=None) From 0170e1b161fcc42aa7963fe84d2dc34bfe02123e Mon Sep 17 00:00:00 2001 From: David C Ellis Date: Mon, 5 Oct 2026 18:11:51 +0100 Subject: [PATCH 10/10] Indicate that modules_to_block should be names not modules. --- Doc/library/test.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/test.rst b/Doc/library/test.rst index 985be1918b8906e..2855dad51a82987 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1904,7 +1904,7 @@ Lazy imports .. function:: ensure_lazy_imports(imported_module, modules_to_block, *, additional_code=None) - Test that when *imported_module* is imported, none of the modules in *modules_to_block* + Test that when *imported_module* is imported, none of the modules named in *modules_to_block* are imported as a side effect. *additional_code* if given should be additional python source code to execute before