Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
26 changes: 19 additions & 7 deletions mypyc/lib-rt/misc_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -1224,14 +1224,26 @@ static int CPyImport_InitSpecClasses(void) {
return 0;
}

// Look up an attribute directly from the module dict. Unlike
// PyObject_GetOptionalAttrString, this doesn't call a module-level __getattr__,
// which isn't safe to call before the module has been initialized.
static int CPyImport_GetModuleDictItem(PyObject *modobj, const char *name, PyObject **result) {
PyObject *dict = PyModule_GetDict(modobj); // Borrowed
if (dict == NULL) {
*result = NULL;
return -1;
}
return PyDict_GetItemStringRef(dict, name, result);
}

// Set __package__ before executing the module body so it is available
// during module initialization. For a package, __package__ is the module
// name itself. For a non-package submodule "a.b.c", it is "a.b". For a
// top-level non-package module, it is "".
static int CPyImport_SetModulePackage(PyObject *modobj, PyObject *module_name,
Py_ssize_t is_package) {
PyObject *pkg = NULL;
int rc = PyObject_GetOptionalAttrString(modobj, "__package__", &pkg);
int rc = CPyImport_GetModuleDictItem(modobj, "__package__", &pkg);
if (rc < 0) {
return -1;
}
Expand Down Expand Up @@ -1271,7 +1283,7 @@ static int CPyImport_SetModuleFile(PyObject *modobj, PyObject *module_name,
PyObject *shared_lib_file, PyObject *ext_suffix,
Py_ssize_t is_package) {
PyObject *file = NULL;
int rc = PyObject_GetOptionalAttrString(modobj, "__file__", &file);
int rc = CPyImport_GetModuleDictItem(modobj, "__file__", &file);
if (rc < 0) {
return -1;
}
Expand Down Expand Up @@ -1396,7 +1408,7 @@ static int CPyImport_SetModuleFile(PyObject *modobj, PyObject *module_name,
// Returns 0 on success, -1 on error.
static int CPyImport_SetModulePath(PyObject *modobj) {
PyObject *existing_path = NULL;
int rc = PyObject_GetOptionalAttrString(modobj, "__path__", &existing_path);
int rc = CPyImport_GetModuleDictItem(modobj, "__path__", &existing_path);
if (rc < 0) {
return -1;
}
Expand All @@ -1405,7 +1417,7 @@ static int CPyImport_SetModulePath(PyObject *modobj) {
return 0;
}
PyObject *file = NULL;
rc = PyObject_GetOptionalAttrString(modobj, "__file__", &file);
rc = CPyImport_GetModuleDictItem(modobj, "__file__", &file);
if (rc <= 0) {
return rc;
}
Expand Down Expand Up @@ -1435,7 +1447,7 @@ static int CPyImport_SetModulePath(PyObject *modobj) {
static int CPyImport_SetModuleSpec(PyObject *modobj, PyObject *module_name,
Py_ssize_t is_package) {
PyObject *spec = NULL;
int rc = PyObject_GetOptionalAttrString(modobj, "__spec__", &spec);
int rc = CPyImport_GetModuleDictItem(modobj, "__spec__", &spec);
if (rc < 0) {
return -1;
}
Expand All @@ -1449,7 +1461,7 @@ static int CPyImport_SetModuleSpec(PyObject *modobj, PyObject *module_name,
return -1;
}
PyObject *file = NULL;
if (PyObject_GetOptionalAttrString(modobj, "__file__", &file) < 0) {
if (CPyImport_GetModuleDictItem(modobj, "__file__", &file) < 0) {
return -1;
}
if (file == NULL) {
Expand Down Expand Up @@ -1479,7 +1491,7 @@ static int CPyImport_SetModuleSpec(PyObject *modobj, PyObject *module_name,
if (is_package) {
// Set submodule_search_locations from __path__
PyObject *path = NULL;
if (PyObject_GetOptionalAttrString(modobj, "__path__", &path) < 0) {
if (CPyImport_GetModuleDictItem(modobj, "__path__", &path) < 0) {
Py_DECREF(spec);
Py_DECREF(loader);
return -1;
Expand Down
20 changes: 20 additions & 0 deletions mypyc/test-data/run-multimodule.test
Original file line number Diff line number Diff line change
Expand Up @@ -4198,3 +4198,23 @@ assert o.ITEMS == [1, 2]
b: Any = Base()
assert b.COMPUTED == 7
assert b.ITEMS is Base.ITEMS

[case testImportPackageWithModuleGetattr]
[file other_pkg/__init__.py]
def __getattr__(name: str) -> str:
if name.startswith("__"):
raise AttributeError(name)
return name
[file other_pkg/other_sub.py]
x = 1
[file driver.py]
import importlib.machinery
import other_pkg.other_sub

assert other_pkg.other_sub.x == 1
ext_suffixes = importlib.machinery.EXTENSION_SUFFIXES
assert any(other_pkg.__file__.endswith(s) for s in ext_suffixes), other_pkg.__file__
assert other_pkg.__path__
assert other_pkg.__spec__ is not None
assert other_pkg.__package__ == "other_pkg"
assert other_pkg.missing == "missing"
Loading