gh-138310: Adds sys.audit event for import_module (#138311)

* Updates sys.audit calls for imports to include import_module
* Adds unit tests for existing and new functionality
This commit is contained in:
Lisa Roach
2025-09-19 06:21:42 -07:00
committed by GitHub
parent 7257b24140
commit 2fd43a1ffe
8 changed files with 98 additions and 27 deletions

View 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:

View 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

View File

View File

View File

View 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()

View 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 \

View 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