Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Lib/test/test_import/data/lazy_imports/module_with_getattr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Test module with __getattr__ for gh-144957."""

def __getattr__(name):
"""Provide dynamic attributes."""
if name == "dynamic_attr":
return "from_getattr"
raise AttributeError(f"module has no attribute {name!r}")
17 changes: 17 additions & 0 deletions Lib/test/test_import/test_lazy_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,23 @@ def test_basic_used(self):
import test.test_import.data.lazy_imports.basic_used
self.assertIn("test.test_import.data.lazy_imports.basic2", sys.modules)

def test_lazy_import_with_getattr(self):
"""Lazy imports work with module __getattr__ (gh-144957)."""
code = textwrap.dedent("""
import sys
sys.set_lazy_imports("normal")
lazy from test.test_import.data.lazy_imports.module_with_getattr import dynamic_attr
assert dynamic_attr == "from_getattr"
print("OK")
""")
result = subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("OK", result.stdout)


class GlobalLazyImportModeTests(unittest.TestCase):
"""Tests for sys.set_lazy_imports() global mode control."""
Expand Down
18 changes: 18 additions & 0 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -3084,6 +3084,24 @@ _PyEval_ImportFrom(PyThreadState *tstate, PyObject *v, PyObject *name)
PyObject *fullmodname, *mod_name, *origin, *mod_name_or_unknown, *errmsg, *spec;

if (PyObject_GetOptionalAttr(v, name, &x) != 0) {
// gh-144957: If we got a lazy import object, the module might have
// __getattr__ that should be tried first
if (x != NULL && PyLazyImport_CheckExact(x)) {
PyObject *getattr_func;
if (PyObject_GetOptionalAttr(v, &_Py_ID(__getattr__), &getattr_func) < 0) {
Py_DECREF(x);
return NULL;
}
if (getattr_func != NULL) {
PyObject *result = PyObject_CallOneArg(getattr_func, name);
Py_DECREF(getattr_func);
if (result != NULL) {
Py_DECREF(x);
return result;
}
PyErr_Clear();
}
}
return x;
}
/* Issue #17636: in case this failed because of a circular relative
Expand Down
Loading