Add the eager-runtime hook required by Python 3.15 importlib (#8946)

* Add the eager-runtime importlib lazy-attribute hook

Expose _imp._set_lazy_attributes(modobj, name, /), used by CPython 3.15 importlib after eager loading. Match its empty pending-lazy-module registry behavior, including name type and string-subclass hashing checks. This does not implement lazy imports or change the target version or bundled libraries.

Assisted-by: Codex:model-version-unavailable

* Consolidate lazy import hook tests into stdlib_imp

Move the distinct direct-hook argument and hashing contracts into the existing _imp snippet and remove the separate file. Preserve every pre-existing assertion and the CPython version guard; canonical 3.15 lazy-import tests already cover feature-level registry and reification behavior.

Assisted-by: Codex:model-version-unavailable
This commit is contained in:
youknowdot authored and GitHub committed 2026-10-05 08:29:00 +00:00
1 parent f1ebf16ce0
commit 04d9990616
2 files changed
+57 -2

No files matched your search

+14 -1
View File
@@ -193,7 +193,7 @@ fn find_frozen(name: &str, vm: &VirtualMachine) -> Result<FrozenModule, FrozenEr
#[pymodule(with(lock))]
mod _imp {
use crate::{
PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
AsObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
builtins::{PyBytesRef, PyCode, PyMemoryView, PyModule, PyStrRef, PyUtf8StrRef},
import, version,
};
@@ -214,6 +214,19 @@ mod _imp {
Vec::new()
}
// CPython removes the name from its pending lazy-module registry here.
// RustPython currently performs only eager imports, so that registry is empty.
#[pyfunction]
fn _set_lazy_attributes(
_modobj: PyObjectRef,
name: PyStrRef,
vm: &VirtualMachine,
) -> PyResult<()> {
// Even an empty set checks the hash of a str subclass.
name.as_object().hash(vm)?;
Ok(())
}
#[pyfunction]
fn is_builtin(name: PyUtf8StrRef, vm: &VirtualMachine) -> bool {
vm.state.module_defs.contains_key(name.as_str())
+43 -1
View File
@@ -1,7 +1,7 @@
import _imp
import time as import_time
from testutils import assert_raises
from testutils import assert_raises, skip_if_unsupported
assert _imp.is_builtin("time") == True
assert _imp.is_builtin("os") == False
@@ -42,3 +42,45 @@ data, ispkg, origname = _imp.find_frozen("__hello__", withdata=True)
assert ispkg is False
assert origname == "__hello__"
assert _imp.get_frozen_object("__hello__", data).co_name == "<module>"
def test_lazy_attributes_eager_registry():
# CPython accepts any first argument and a string name. In an eager-only
# interpreter there are no pending lazy module names to remove.
for modobj in (None, object(), {}, 42):
assert _imp._set_lazy_attributes(modobj, "package.module") is None
assert _imp._set_lazy_attributes(modobj, "\ud800") is None
for name in (None, 1, b"package.module", [], {}):
with assert_raises(TypeError):
_imp._set_lazy_attributes(None, name)
with assert_raises(TypeError):
_imp._set_lazy_attributes(modobj=None, name="package.module")
class ModuleName(str):
hashes = 0
def __hash__(self):
self.hashes += 1
return 42
name = ModuleName("package.module")
assert _imp._set_lazy_attributes(None, name) is None
assert name.hashes == 1
class UnhashableName(str):
__hash__ = None
with assert_raises(TypeError):
_imp._set_lazy_attributes(None, UnhashableName("package.module"))
class HashErrorName(str):
def __hash__(self):
raise RuntimeError("name hash failed")
with assert_raises(RuntimeError):
_imp._set_lazy_attributes(None, HashErrorName("package.module"))
skip_if_unsupported(3, 15, test_lazy_attributes_eager_registry)