]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-138310: Adds sys.audit event for import_module (#138311)
authorLisa Roach <lisaroach14@gmail.com>
Fri, 19 Sep 2025 13:21:42 +0000 (06:21 -0700)
committerGitHub <noreply@github.com>
Fri, 19 Sep 2025 13:21:42 +0000 (06:21 -0700)
* Updates sys.audit calls for imports to include import_module
* Adds unit tests for existing and new functionality

Lib/importlib/_bootstrap.py
Lib/test/audit-tests.py
Lib/test/audit_test_data/__init__.py [new file with mode: 0644]
Lib/test/audit_test_data/submodule.py [new file with mode: 0644]
Lib/test/audit_test_data/submodule2.py [new file with mode: 0644]
Lib/test/test_audit.py
Makefile.pre.in
Python/import.c

index 499da1e04efea8d3442cc05622d5b066cd311aba..43c66765dd97792cf9afcf62028a0e01255285ea 100644 (file)
@@ -1307,6 +1307,14 @@ _ERR_MSG_PREFIX = 'No module named '
 
 def _find_and_load_unlocked(name, import_):
     path = None
+    sys.audit(
+        "import",
+        name,
+        path,
+        getattr(sys, "path", None),
+        getattr(sys, "meta_path", None),
+        getattr(sys, "path_hooks", None)
+    )
     parent = name.rpartition('.')[0]
     parent_spec = None
     if parent:
index 6884ac0dbe6ff03b9b0b6d99e4bc4bbe0c8aae2b..a893932169a089b47664963b193e4a396adebbd9 100644 (file)
@@ -8,6 +8,8 @@ module with arguments identifying each test.
 import contextlib
 import os
 import sys
+import unittest.mock
+from test.support import swap_item
 
 
 class TestHook:
@@ -672,6 +674,84 @@ def test_sys_remote_exec():
         assertEqual(event_script_path, tmp_file.name)
         assertEqual(remote_event_script_path, tmp_file.name)
 
+def test_import_module():
+    import importlib
+
+    with TestHook() as hook:
+        importlib.import_module("importlib")  # already imported, won't get logged
+        importlib.import_module("email") # standard library module
+        importlib.import_module("pythoninfo")  # random module
+        importlib.import_module(".audit_test_data.submodule", "test")  # relative import
+        importlib.import_module("test.audit_test_data.submodule2")  # absolute import
+        importlib.import_module("_testcapi")  # extension module
+
+    actual = [a for e, a in hook.seen if e == "import"]
+    assertSequenceEqual(
+        [
+            ("email", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("pythoninfo", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("test.audit_test_data.submodule", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("test.audit_test_data", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("test.audit_test_data.submodule2", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("_testcapi", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("_testcapi", unittest.mock.ANY, None, None, None)
+        ],
+        actual,
+    )
+
+def test_builtin__import__():
+    import importlib # noqa: F401
+
+    with TestHook() as hook:
+        __import__("importlib")
+        __import__("email")
+        __import__("pythoninfo")
+        __import__("audit_test_data.submodule", level=1, globals={"__package__": "test"})
+        __import__("test.audit_test_data.submodule2")
+        __import__("_testcapi")
+
+    actual = [a for e, a in hook.seen if e == "import"]
+    assertSequenceEqual(
+        [
+            ("email", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("pythoninfo", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("test.audit_test_data.submodule", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("test.audit_test_data", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("test.audit_test_data.submodule2", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("_testcapi", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("_testcapi", unittest.mock.ANY, None, None, None)
+        ],
+        actual,
+    )
+
+def test_import_statement():
+    import importlib # noqa: F401
+    # Set __package__ so relative imports work
+    with swap_item(globals(), "__package__", "test"):
+        with TestHook() as hook:
+            import importlib # noqa: F401
+            import email # noqa: F401
+            import pythoninfo # noqa: F401
+            from .audit_test_data import submodule # noqa: F401
+            import test.audit_test_data.submodule2 # noqa: F401
+            import _testcapi # noqa: F401
+
+    actual = [a for e, a in hook.seen if e == "import"]
+    # Import statement ordering is different because the package is
+    # loaded first and then the submodule
+    assertSequenceEqual(
+        [
+            ("email", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("pythoninfo", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("test.audit_test_data", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("test.audit_test_data.submodule", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("test.audit_test_data.submodule2", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("_testcapi", None, sys.path, sys.meta_path, sys.path_hooks),
+            ("_testcapi", unittest.mock.ANY, None, None, None)
+        ],
+        actual,
+    )
+
 if __name__ == "__main__":
     from test.support import suppress_msvcrt_asserts
 
diff --git a/Lib/test/audit_test_data/__init__.py b/Lib/test/audit_test_data/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Lib/test/audit_test_data/submodule.py b/Lib/test/audit_test_data/submodule.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Lib/test/audit_test_data/submodule2.py b/Lib/test/audit_test_data/submodule2.py
new file mode 100644 (file)
index 0000000..e69de29
index 077765fcda210a6d39a06437489c7d8a4d475cab..db4e1eb9999c1fa43bbcab29fbf3e9de2ee44d89 100644 (file)
@@ -331,5 +331,14 @@ class AuditTest(unittest.TestCase):
         if returncode:
             self.fail(stderr)
 
+    def test_import_module(self):
+        self.do_test("test_import_module")
+
+    def test_builtin__import__(self):
+        self.do_test("test_builtin__import__")
+
+    def test_import_statement(self):
+        self.do_test("test_import_statement")
+
 if __name__ == "__main__":
     unittest.main()
index 9f00ca1c0d541efb4c8db6af849e2fb256c120b5..da036b198d11f8195b96977aaaf24bb6113e25d1 100644 (file)
@@ -2594,6 +2594,7 @@ TESTSUBDIRS=      idlelib/idle_test \
                test/test_ast \
                test/test_ast/data \
                test/archivetestdata \
+               test/audit_test_data \
                test/audiodata \
                test/certdata \
                test/certdata/capath \
index 9dee20ecb63c913c465e10acc92f3261a46bd101..d01c4d478283ff6ce1469ce2d887669e4e0b9a33 100644 (file)
@@ -3681,33 +3681,6 @@ import_find_and_load(PyThreadState *tstate, PyObject *abs_name)
 
     PyTime_t t1 = 0, accumulated_copy = accumulated;
 
-    PyObject *sys_path, *sys_meta_path, *sys_path_hooks;
-    if (PySys_GetOptionalAttrString("path", &sys_path) < 0) {
-        return NULL;
-    }
-    if (PySys_GetOptionalAttrString("meta_path", &sys_meta_path) < 0) {
-        Py_XDECREF(sys_path);
-        return NULL;
-    }
-    if (PySys_GetOptionalAttrString("path_hooks", &sys_path_hooks) < 0) {
-        Py_XDECREF(sys_meta_path);
-        Py_XDECREF(sys_path);
-        return NULL;
-    }
-    if (_PySys_Audit(tstate, "import", "OOOOO",
-                     abs_name, Py_None, sys_path ? sys_path : Py_None,
-                     sys_meta_path ? sys_meta_path : Py_None,
-                     sys_path_hooks ? sys_path_hooks : Py_None) < 0) {
-        Py_XDECREF(sys_path_hooks);
-        Py_XDECREF(sys_meta_path);
-        Py_XDECREF(sys_path);
-        return NULL;
-    }
-    Py_XDECREF(sys_path_hooks);
-    Py_XDECREF(sys_meta_path);
-    Py_XDECREF(sys_path);
-
-
     /* XOptions is initialized after first some imports.
      * So we can't have negative cache before completed initialization.
      * Anyway, importlib._find_and_load is much slower than