Skip to content
Prev Previous commit
Next Next commit
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
  • Loading branch information
youknowdot committed Oct 2, 2026
commit 2847724cc577a19ee5f64bb1a231f723fd7e7c14
15 changes: 14 additions & 1 deletion crates/vm/src/stdlib/_imp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -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())
Expand Down
45 changes: 45 additions & 0 deletions extra_tests/snippets/stdlib_imp_lazy_attributes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import _imp

from testutils import assert_raises, skip_if_unsupported


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)