diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst index 65aaad0df9ee66c..569418d2aead12a 100644 --- a/Doc/library/importlib.rst +++ b/Doc/library/importlib.rst @@ -1240,6 +1240,13 @@ an :term:`importer`. .. versionchanged:: 3.6 Accepts a :term:`path-like object`. +.. function:: load_source(module_name, filename) + + Load a module from a filename: execute the module and add it to + :data:`sys.modules`. + + .. versionadded:: 3.12 + .. function:: source_hash(source_bytes) Return the hash of *source_bytes* as bytes. A hash-based ``.pyc`` file embeds diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 1e063ed7d2263d2..6d841cdd5e50382 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -570,6 +570,12 @@ fractions * Objects of type :class:`fractions.Fraction` now support float-style formatting. (Contributed by Mark Dickinson in :gh:`100161`.) +importlib +--------- + +* Add :func:`importlib.util.load_source` to load a module from a filename. + (Contributed by Victor Stinner in :gh:`104212`.) + inspect ------- @@ -1371,6 +1377,9 @@ Removed * Replace ``imp.new_module(name)`` with ``types.ModuleType(name)``. + * Replace ``imp.load_source(module_name, filename)`` + with ``importlib.util.load_source(module_name, filename)``. + * Removed the ``suspicious`` rule from the documentation Makefile, and removed ``Doc/tools/rstlint.py``, both in favor of `sphinx-lint `_. diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index f4d6e82331516f9..95fff9b109467e6 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -10,6 +10,7 @@ from ._bootstrap_external import decode_source from ._bootstrap_external import source_from_cache from ._bootstrap_external import spec_from_file_location +from ._bootstrap_external import SourceFileLoader import _imp import sys @@ -246,3 +247,13 @@ def exec_module(self, module): loader_state['__class__'] = module.__class__ module.__spec__.loader_state = loader_state module.__class__ = _LazyModule + + +def load_source(module_name, filename): + """Load a module from a filename.""" + loader = SourceFileLoader(module_name, filename) + module = types.ModuleType(loader.name) + module.__file__ = filename + sys.modules[module.__name__] = module + loader.exec_module(module) + return module diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py index e967adc9451c813..302ddfc0d9f8038 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -1,3 +1,4 @@ +from test.test_importlib import fixtures from test.test_importlib import util abc = util.import_importlib('importlib.abc') @@ -12,6 +13,7 @@ import string import sys from test import support +from test.support import import_helper, os_helper import textwrap import types import unittest @@ -758,5 +760,28 @@ def test_complete_multi_phase_init_module(self): self.run_with_own_gil(script) +class LoadSourceTests(unittest.TestCase): + def test_load_source(self): + modname = 'test_load_source_mod' + filename = 'load_source_filename' + + self.assertNotIn(modname, sys.modules) + self.addCleanup(import_helper.unload, modname) + + # Use a temporary directory to remove __pycache__/ subdirectory + with fixtures.tempdir_as_cwd(): + with open(filename, "w", encoding="utf8") as fp: + print("attr = 'load_source_attr'", file=fp) + + mod = importlib.util.load_source(modname, filename) + + self.assertIsInstance(mod, types.ModuleType) + self.assertEqual(mod.__name__, modname) + self.assertEqual(mod.__file__, filename) + self.assertIn(modname, sys.modules) + self.assertIs(sys.modules[modname], mod) + self.assertEqual(mod.attr, 'load_source_attr') + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2023-06-14-00-54-11.gh-issue-104212.4IJaKp.rst b/Misc/NEWS.d/next/Library/2023-06-14-00-54-11.gh-issue-104212.4IJaKp.rst new file mode 100644 index 000000000000000..168e993407d3085 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-06-14-00-54-11.gh-issue-104212.4IJaKp.rst @@ -0,0 +1,2 @@ +Add :func:`importlib.util.load_source` to load a module from a filename. +Patch by Victor Stinner.