GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Fix test_import: import machinery, circular imports, and script shadowing by youknowone · Pull Request #7034 · RustPython/RustPython

Fix test_import: import machinery, circular imports, and script shadowing #7034

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Sign up for GitHub

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
youknowone merged 2 commits into RustPython:main from youknowone:test_import
Feb 8, 2026

Uh oh!

There was an error while loading. Please reload this page.

Merged

Fix test_import: import machinery, circular imports, and script shadowing #7034

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
2 commits
Select commit Hold shift + click to select a range
0fc6522
Fix test_import: import machinery and errors
youknowone Feb 7, 2026
987cf42
Propagate __bool__ errors from fromlist in import
youknowone Feb 8, 2026
File filter
Filter by extension

Clear filters
Conversations
Failed to load comments.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Jump to
Jump to file
Failed to load files.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Diff view
Diff view

There are no files selected for viewing

2 changes: 2 additions & 0 deletions 2 Lib/__hello_only__.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
initialized = True
print("Hello world!")
2 changes: 0 additions & 2 deletions 2 Lib/test/test_frozen.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ def test_frozen(self):
__hello__.main()
self.assertEqual(out.getvalue(), 'Hello world!\n')

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: unexpectedly identical: <class '_frozen_importlib.FrozenImporter'>
def test_frozen_submodule_in_unfrozen_package(self):
with import_helper.CleanImport('__phello__', '__phello__.spam'):
with import_helper.frozen_modules(enabled=False):
Expand All @@ -40,7 +39,6 @@ def test_frozen_submodule_in_unfrozen_package(self):
self.assertIs(spam.__spec__.loader,
importlib.machinery.FrozenImporter)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: unexpectedly identical: <class '_frozen_importlib.FrozenImporter'>
def test_unfrozen_submodule_in_frozen_package(self):
with import_helper.CleanImport('__phello__', '__phello__.spam'):
with import_helper.frozen_modules(enabled=True):
Expand Down
9 changes: 4 additions & 5 deletions 9 Lib/test/test_import/__init__.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ def run():
finally:
del sys.path[0]

@unittest.expectedFailure # TODO: RUSTPYTHON; FileNotFoundError: [WinError 2] No such file or directory: 'built-in'
@unittest.expectedFailure # TODO: RUSTPYTHON; no C extension support
@unittest.skipUnless(sys.platform == "win32", "Windows-specific")
def test_dll_dependency_import(self):
from _winapi import GetModuleFileName
Expand Down Expand Up @@ -814,7 +814,6 @@ def test_dll_dependency_import(self):
env=env,
cwd=os.path.dirname(pyexe))

@unittest.expectedFailure # TODO: RUSTPYTHON; _imp.get_frozen_object("x", b"6\'\xd5Cu\x12"). TypeError: expected at most 1 arguments, got 2
def test_issue105979(self):
# this used to crash
with self.assertRaises(ImportError) as cm:
Expand Down Expand Up @@ -1239,7 +1238,8 @@ def test_script_shadowing_stdlib_sys_path_modification(self):
stdout, stderr = popen.communicate()
self.assertRegex(stdout, expected_error)

@unittest.skip("TODO: RUSTPYTHON; AttributeError: module \"_imp\" has no attribute \"create_dynamic\"")
# TODO: RUSTPYTHON: _imp.create_dynamic is for C extensions, not applicable
@unittest.skip("TODO: RustPython _imp.create_dynamic not implemented")
def test_create_dynamic_null(self):
with self.assertRaisesRegex(ValueError, 'embedded null character'):
class Spec:
Expand Down Expand Up @@ -1398,7 +1398,6 @@ def test_basics(self):
self.assertEqual(mod.code_filename, self.file_name)
self.assertEqual(mod.func_filename, self.file_name)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'another_module.py' != .../unlikely_module_name.py
def test_incorrect_code_name(self):
py_compile.compile(self.file_name, dfile="another_module.py")
mod = self.import_module()
Expand Down Expand Up @@ -2045,6 +2044,7 @@ def exec_module(*args):
else:
importlib.SourceLoader.exec_module = old_exec_module

@unittest.expectedFailure # TODO: RUSTPYTHON; subprocess fails on Windows
@unittest.skipUnless(TESTFN_UNENCODABLE, 'need TESTFN_UNENCODABLE')
def test_unencodable_filename(self):
# Issue #11619: The Python parser and the import machinery must not
Expand Down Expand Up @@ -2095,7 +2095,6 @@ def test_rebinding(self):
from test.test_import.data.circular_imports.subpkg import util
self.assertIs(util.util, rebinding.util)

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module "test.test_import.data.circular_imports" has no attribute "binding"
def test_binding(self):
try:
import test.test_import.data.circular_imports.binding
Expand Down
1 change: 1 addition & 0 deletions 1 Lib/test/test_importlib/frozen/test_loader.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from test.support import captured_stdout, import_helper, STDLIB_DIR
import contextlib
import os.path
import sys
import types
import unittest
import warnings
Expand Down
4 changes: 4 additions & 0 deletions 4 Lib/test/test_importlib/test_locks.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ def test_all_locks(self):
Source_LifetimeTests
) = test_util.test_both(LifetimeTests, init=init)

# TODO: RUSTPYTHON; dead weakref module locks not cleaned up in frozen bootstrap
Frozen_LifetimeTests.test_all_locks = unittest.skip("TODO: RUSTPYTHON")(
Frozen_LifetimeTests.test_all_locks)


def setUpModule():
thread_info = threading_helper.threading_setup()
Expand Down
1 change: 1 addition & 0 deletions 1 Lib/test/test_importlib/test_windows.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def test_raises_deprecation_warning(self):

@unittest.skipUnless(sys.platform.startswith('win'), 'requires Windows')
class WindowsExtensionSuffixTests:
@unittest.expectedFailure # TODO: RUSTPYTHON; no C extension (.pyd) support
def test_tagged_suffix(self):
suffixes = self.machinery.EXTENSION_SUFFIXES
abi_flags = "t" if support.Py_GIL_DISABLED else ""
Expand Down
5 changes: 4 additions & 1 deletion 5 Lib/test/test_importlib/util.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
import tempfile
import types

_testsinglephase = import_helper.import_module("_testsinglephase")
try:
_testsinglephase = import_helper.import_module("_testsinglephase")
except unittest.SkipTest:
_testsinglephase = None # TODO: RUSTPYTHON


BUILTINS = types.SimpleNamespace()
Expand Down
14 changes: 11 additions & 3 deletions 14 crates/codegen/src/compile.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -2065,11 +2065,19 @@ impl Compiler {
let idx = self.name(&name.name);
emit!(self, Instruction::ImportName { idx });
if let Some(alias) = &name.asname {
for part in name.name.split('.').skip(1) {
let parts: Vec<&str> = name.name.split('.').skip(1).collect();
for (i, part) in parts.iter().enumerate() {
let idx = self.name(part);
self.emit_load_attr(idx);
emit!(self, Instruction::ImportFrom { idx });
if i < parts.len() - 1 {
emit!(self, Instruction::Swap { index: 2 });
emit!(self, Instruction::PopTop);
}
}
self.store_name(alias.as_str())?;
if !parts.is_empty() {
emit!(self, Instruction::PopTop);
}
self.store_name(alias.as_str())?
} else {
self.store_name(name.name.split('.').next().unwrap())?
}
Expand Down
1 change: 1 addition & 0 deletions 1 crates/vm/Lib/python_builtins/__hello_only__.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
../../../../Lib/__hello_only__.py
132 changes: 119 additions & 13 deletions 132 crates/vm/src/builtins/module.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
class::PyClassImpl,
convert::ToPyObject,
function::{FuncArgs, PyMethodDef, PySetterValue},
import::{get_spec_file_origin, is_possibly_shadowing_path, is_stdlib_module_name},
types::{GetAttr, Initializer, Representable},
};

Expand Down Expand Up @@ -152,20 +153,96 @@ impl Py<PyModule> {
if let Ok(getattr) = self.dict().get_item(identifier!(vm, __getattr__), vm) {
return getattr.call((name.to_owned(),), vm);
}
let module_name = if let Some(name) = self.name(vm) {
format!(" '{name}'")
let dict = self.dict();

// Get the raw __name__ object (may be a str subclass)
let mod_name_obj = dict
.get_item_opt(identifier!(vm, __name__), vm)
.ok()
.flatten();
let mod_name_str = mod_name_obj
.as_ref()
.and_then(|n| n.downcast_ref::<PyStr>().map(|s| s.as_str().to_owned()));

// If __name__ is not set or not a string, use a simpler error message
let mod_display = match mod_name_str.as_deref() {
Some(s) => s,
None => {
return Err(vm.new_attribute_error(format!("module has no attribute '{name}'")));
}
};

let spec = dict
.get_item_opt(vm.ctx.intern_str("__spec__"), vm)
.ok()
.flatten()
.filter(|s| !vm.is_none(s));

let origin = get_spec_file_origin(&spec, vm);

let is_possibly_shadowing = origin
.as_ref()
.map(|o| is_possibly_shadowing_path(o, vm))
.unwrap_or(false);
// Use the ORIGINAL __name__ object for stdlib check (may raise TypeError
// if __name__ is an unhashable str subclass)
let is_possibly_shadowing_stdlib = if is_possibly_shadowing {
if let Some(ref mod_name) = mod_name_obj {
is_stdlib_module_name(mod_name, vm)?
} else {
false
}
} else {
"".to_owned()
false
};
Err(vm.new_attribute_error(format!("module{module_name} has no attribute '{name}'")))
}

fn name(&self, vm: &VirtualMachine) -> Option<PyStrRef> {
let name = self
.as_object()
.generic_getattr_opt(identifier!(vm, __name__), None, vm)
.unwrap_or_default()?;
name.downcast::<PyStr>().ok()
if is_possibly_shadowing_stdlib {
let origin = origin.as_ref().unwrap();
Err(vm.new_attribute_error(format!(
"module '{mod_display}' has no attribute '{name}' \
(consider renaming '{origin}' since it has the same \
name as the standard library module named '{mod_display}' \
and prevents importing that standard library module)"
)))
} else {
let is_initializing = PyModule::is_initializing(&dict, vm);
if is_initializing {
if is_possibly_shadowing {
let origin = origin.as_ref().unwrap();
Err(vm.new_attribute_error(format!(
"module '{mod_display}' has no attribute '{name}' \
(consider renaming '{origin}' if it has the same name \
as a library you intended to import)"
)))
} else if let Some(ref origin) = origin {
Err(vm.new_attribute_error(format!(
"partially initialized module '{mod_display}' from '{origin}' \
has no attribute '{name}' \
(most likely due to a circular import)"
)))
} else {
Err(vm.new_attribute_error(format!(
"partially initialized module '{mod_display}' \
has no attribute '{name}' \
(most likely due to a circular import)"
)))
}
} else {
// Check for uninitialized submodule
let submodule_initializing =
is_uninitialized_submodule(mod_name_str.as_ref(), name, vm);
if submodule_initializing {
Err(vm.new_attribute_error(format!(
"cannot access submodule '{name}' of module '{mod_display}' \
(most likely due to a circular import)"
)))
} else {
Err(vm.new_attribute_error(format!(
"module '{mod_display}' has no attribute '{name}'"
)))
}
}
}
}

// TODO: to be replaced by the commented-out dict method above once dictoffset land
Expand Down Expand Up @@ -361,8 +438,8 @@ impl GetAttr for PyModule {
impl Representable for PyModule {
#[inline]
fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
let importlib = vm.import("_frozen_importlib", 0)?;
let module_repr = importlib.get_attr("_module_repr", vm)?;
// Use cached importlib reference (like interp->importlib)
let module_repr = vm.importlib.get_attr("_module_repr", vm)?;
let repr = module_repr.call((zelf.to_owned(),), vm)?;
repr.downcast()
.map_err(|_| vm.new_type_error("_module_repr did not return a string"))
Expand All @@ -377,3 +454,32 @@ impl Representable for PyModule {
pub(crate) fn init(context: &Context) {
PyModule::extend_class(context, context.types.module_type);
}

/// Check if {module_name}.{name} is an uninitialized submodule in sys.modules.
fn is_uninitialized_submodule(
module_name: Option<&String>,
name: &Py<PyStr>,
vm: &VirtualMachine,
) -> bool {
let mod_name = match module_name {
Some(n) => n.as_str(),
None => return false,
};
let full_name = format!("{mod_name}.{name}");
let sys_modules = match vm.sys_module.get_attr("modules", vm).ok() {
Some(m) => m,
None => return false,
};
let sub_mod = match sys_modules.get_item(&full_name, vm).ok() {
Some(m) => m,
None => return false,
};
let spec = match sub_mod.get_attr("__spec__", vm).ok() {
Some(s) if !vm.is_none(&s) => s,
_ => return false,
};
spec.get_attr("_initializing", vm)
.ok()
.and_then(|v| v.try_to_bool(vm).ok())
.unwrap_or(false)
}
Loading
Oops, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Loading
Oops, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.