Skip to content

Commit c3a44b9

Browse files
committed
gh-104212: Add importlib.util.load_source() function
1 parent 457a459 commit c3a44b9

File tree

5 files changed

+54
-0
lines changed

5 files changed

+54
-0
lines changed

Doc/library/importlib.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,13 @@ an :term:`importer`.
12401240
.. versionchanged:: 3.6
12411241
Accepts a :term:`path-like object`.
12421242

1243+
.. function:: load_source(module_name, filename)
1244+
1245+
Load a module from a filename: execute the module and add it to
1246+
:data:`sys.modules`.
1247+
1248+
.. versionadded:: 3.12
1249+
12431250
.. function:: source_hash(source_bytes)
12441251

12451252
Return the hash of *source_bytes* as bytes. A hash-based ``.pyc`` file embeds

Doc/whatsnew/3.12.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,12 @@ fractions
570570
* Objects of type :class:`fractions.Fraction` now support float-style
571571
formatting. (Contributed by Mark Dickinson in :gh:`100161`.)
572572

573+
importlib
574+
---------
575+
576+
* Add :func:`importlib.util.load_source` to load a module from a filename.
577+
(Contributed by Victor Stinner in :gh:`104212`.)
578+
573579
inspect
574580
-------
575581

@@ -1371,6 +1377,9 @@ Removed
13711377

13721378
* Replace ``imp.new_module(name)`` with ``types.ModuleType(name)``.
13731379

1380+
* Replace ``imp.load_source(module_name, filename)``
1381+
with ``importlib.util.load_source(module_name, filename)``.
1382+
13741383
* Removed the ``suspicious`` rule from the documentation Makefile, and
13751384
removed ``Doc/tools/rstlint.py``, both in favor of `sphinx-lint
13761385
<https://github.com/sphinx-contrib/sphinx-lint>`_.

Lib/importlib/util.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from ._bootstrap_external import decode_source
1111
from ._bootstrap_external import source_from_cache
1212
from ._bootstrap_external import spec_from_file_location
13+
from ._bootstrap_external import SourceFileLoader
1314

1415
import _imp
1516
import sys
@@ -246,3 +247,13 @@ def exec_module(self, module):
246247
loader_state['__class__'] = module.__class__
247248
module.__spec__.loader_state = loader_state
248249
module.__class__ = _LazyModule
250+
251+
252+
def load_source(module_name, filename):
253+
"""Load a module from a filename."""
254+
loader = SourceFileLoader(module_name, filename)
255+
module = types.ModuleType(loader.name)
256+
module.__file__ = filename
257+
sys.modules[module.__name__] = module
258+
loader.exec_module(module)
259+
return module

Lib/test/test_importlib/test_util.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from test.test_importlib import fixtures
12
from test.test_importlib import util
23

34
abc = util.import_importlib('importlib.abc')
@@ -12,6 +13,7 @@
1213
import string
1314
import sys
1415
from test import support
16+
from test.support import import_helper, os_helper
1517
import textwrap
1618
import types
1719
import unittest
@@ -758,5 +760,28 @@ def test_complete_multi_phase_init_module(self):
758760
self.run_with_own_gil(script)
759761

760762

763+
class LoadSourceTests(unittest.TestCase):
764+
def test_load_source(self):
765+
modname = 'test_load_source_mod'
766+
filename = 'load_source_filename'
767+
768+
self.assertNotIn(modname, sys.modules)
769+
self.addCleanup(import_helper.unload, modname)
770+
771+
# Use a temporary directory to remove __pycache__/ subdirectory
772+
with fixtures.tempdir_as_cwd():
773+
with open(filename, "w", encoding="utf8") as fp:
774+
print("attr = 'load_source_attr'", file=fp)
775+
776+
mod = importlib.util.load_source(modname, filename)
777+
778+
self.assertIsInstance(mod, types.ModuleType)
779+
self.assertEqual(mod.__name__, modname)
780+
self.assertEqual(mod.__file__, filename)
781+
self.assertIn(modname, sys.modules)
782+
self.assertIs(sys.modules[modname], mod)
783+
self.assertEqual(mod.attr, 'load_source_attr')
784+
785+
761786
if __name__ == '__main__':
762787
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :func:`importlib.util.load_source` to load a module from a filename.
2+
Patch by Victor Stinner.

0 commit comments

Comments
 (0)