Skip to content

Commit d4f85cf

Browse files
authored
Provide detailed error for circular from imports (#5972)
1 parent ed43383 commit d4f85cf

File tree

2 files changed

+40
-10
lines changed

2 files changed

+40
-10
lines changed

Lib/test/test_import/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,8 +1380,6 @@ def test_crossreference2(self):
13801380
self.assertIn('partially initialized module', errmsg)
13811381
self.assertIn('circular import', errmsg)
13821382

1383-
# TODO: RUSTPYTHON
1384-
@unittest.expectedFailure
13851383
def test_circular_from_import(self):
13861384
with self.assertRaises(ImportError) as cm:
13871385
import test.test_import.data.circular_imports.from_cycle1

vm/src/frame.rs

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,19 +1363,38 @@ impl ExecutingFrame<'_> {
13631363
fn import_from(&mut self, vm: &VirtualMachine, idx: bytecode::NameIdx) -> PyResult {
13641364
let module = self.top_value();
13651365
let name = self.code.names[idx as usize];
1366-
let err = || vm.new_import_error(format!("cannot import name '{name}'"), name.to_owned());
1366+
13671367
// Load attribute, and transform any error into import error.
13681368
if let Some(obj) = vm.get_attribute_opt(module.to_owned(), name)? {
13691369
return Ok(obj);
13701370
}
13711371
// fallback to importing '{module.__name__}.{name}' from sys.modules
1372-
let mod_name = module
1373-
.get_attr(identifier!(vm, __name__), vm)
1374-
.map_err(|_| err())?;
1375-
let mod_name = mod_name.downcast::<PyStr>().map_err(|_| err())?;
1376-
let full_mod_name = format!("{mod_name}.{name}");
1377-
let sys_modules = vm.sys_module.get_attr("modules", vm).map_err(|_| err())?;
1378-
sys_modules.get_item(&full_mod_name, vm).map_err(|_| err())
1372+
let fallback_module = (|| {
1373+
let mod_name = module.get_attr(identifier!(vm, __name__), vm).ok()?;
1374+
let mod_name = mod_name.downcast_ref::<PyStr>()?;
1375+
let full_mod_name = format!("{mod_name}.{name}");
1376+
let sys_modules = vm.sys_module.get_attr("modules", vm).ok()?;
1377+
sys_modules.get_item(&full_mod_name, vm).ok()
1378+
})();
1379+
1380+
if let Some(sub_module) = fallback_module {
1381+
return Ok(sub_module);
1382+
}
1383+
1384+
if is_module_initializing(module, vm) {
1385+
let module_name = module
1386+
.get_attr(identifier!(vm, __name__), vm)
1387+
.ok()
1388+
.and_then(|n| n.downcast_ref::<PyStr>().map(|s| s.as_str().to_owned()))
1389+
.unwrap_or_else(|| "<unknown>".to_owned());
1390+
1391+
let msg = format!(
1392+
"cannot import name '{name}' from partially initialized module '{module_name}' (most likely due to a circular import)",
1393+
);
1394+
Err(vm.new_import_error(msg, name.to_owned()))
1395+
} else {
1396+
Err(vm.new_import_error(format!("cannot import name '{name}'"), name.to_owned()))
1397+
}
13791398
}
13801399

13811400
#[cfg_attr(feature = "flame-it", flame("Frame"))]
@@ -2372,3 +2391,16 @@ impl fmt::Debug for Frame {
23722391
)
23732392
}
23742393
}
2394+
2395+
fn is_module_initializing(module: &PyObject, vm: &VirtualMachine) -> bool {
2396+
let Ok(spec) = module.get_attr(&vm.ctx.new_str("__spec__"), vm) else {
2397+
return false;
2398+
};
2399+
if vm.is_none(&spec) {
2400+
return false;
2401+
}
2402+
let Ok(initializing_attr) = spec.get_attr(&vm.ctx.new_str("_initializing"), vm) else {
2403+
return false;
2404+
};
2405+
initializing_attr.try_to_bool(vm).unwrap_or(false)
2406+
}

0 commit comments

Comments
 (0)