https://github.com/python/cpython/commit/3bebe46d3413195ee18c5c9ada83a35d4fd1d0e7
commit: 3bebe46d3413195ee18c5c9ada83a35d4fd1d0e7
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2025-01-30T11:17:29Z
summary:

gh-128911: Add PyImport_ImportModuleAttr() function (#128912)

Add PyImport_ImportModuleAttr() and
PyImport_ImportModuleAttrString() functions.

* Add unit tests.
* Replace _PyImport_GetModuleAttr()
  with PyImport_ImportModuleAttr().
* Replace _PyImport_GetModuleAttrString()
  with PyImport_ImportModuleAttrString().
* Remove "pycore_import.h" includes, no longer needed.

files:
A Misc/NEWS.d/next/C_API/2025-01-16-12-47-01.gh-issue-128911.mHVJ4x.rst
A Modules/_testcapi/import.c
M Doc/c-api/import.rst
M Doc/data/refcounts.dat
M Doc/whatsnew/3.14.rst
M Include/cpython/import.h
M Include/internal/pycore_import.h
M Lib/test/test_capi/test_import.py
M Modules/Setup.stdlib.in
M Modules/_ctypes/callbacks.c
M Modules/_ctypes/stgdict.c
M Modules/_cursesmodule.c
M Modules/_datetimemodule.c
M Modules/_decimal/_decimal.c
M Modules/_elementtree.c
M Modules/_json.c
M Modules/_lsprof.c
M Modules/_operator.c
M Modules/_pickle.c
M Modules/_sqlite/connection.c
M Modules/_sqlite/module.c
M Modules/_sre/sre.c
M Modules/_testcapi/parts.h
M Modules/_testcapimodule.c
M Modules/_zoneinfo.c
M Modules/arraymodule.c
M Modules/cjkcodecs/cjkcodecs.h
M Modules/faulthandler.c
M Modules/posixmodule.c
M Modules/selectmodule.c
M Modules/timemodule.c
M Objects/abstract.c
M Objects/fileobject.c
M Objects/memoryobject.c
M PCbuild/_testcapi.vcxproj
M PCbuild/_testcapi.vcxproj.filters
M Parser/pegen.c
M Parser/tokenizer/file_tokenizer.c
M Python/import.c
M Python/pylifecycle.c

diff --git a/Doc/c-api/import.rst b/Doc/c-api/import.rst
index 6e48644c8fef8b..1cab3ce3061ec9 100644
--- a/Doc/c-api/import.rst
+++ b/Doc/c-api/import.rst
@@ -325,3 +325,24 @@ Importing Modules
    If Python is initialized multiple times, :c:func:`PyImport_AppendInittab` or
    :c:func:`PyImport_ExtendInittab` must be called before each Python
    initialization.
+
+
+.. c:function:: PyObject* PyImport_ImportModuleAttr(PyObject *mod_name, 
PyObject *attr_name)
+
+   Import the module *mod_name* and get its attribute *attr_name*.
+
+   Names must be Python :class:`str` objects.
+
+   Helper function combining :c:func:`PyImport_Import` and
+   :c:func:`PyObject_GetAttr`. For example, it can raise :exc:`ImportError` if
+   the module is not found, and :exc:`AttributeError` if the attribute doesn't
+   exist.
+
+   .. versionadded:: 3.14
+
+.. c:function:: PyObject* PyImport_ImportModuleAttrString(const char 
*mod_name, const char *attr_name)
+
+   Similar to :c:func:`PyImport_ImportModuleAttr`, but names are UTF-8 encoded
+   strings instead of Python :class:`str` objects.
+
+   .. versionadded:: 3.14
diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat
index e78754e24e23d8..d709d2d91b0eb0 100644
--- a/Doc/data/refcounts.dat
+++ b/Doc/data/refcounts.dat
@@ -3052,3 +3052,11 @@ _Py_c_quot:Py_complex:divisor::
 _Py_c_sum:Py_complex:::
 _Py_c_sum:Py_complex:left::
 _Py_c_sum:Py_complex:right::
+
+PyImport_ImportModuleAttr:PyObject*::+1:
+PyImport_ImportModuleAttr:PyObject*:mod_name:0:
+PyImport_ImportModuleAttr:PyObject*:attr_name:0:
+
+PyImport_ImportModuleAttrString:PyObject*::+1:
+PyImport_ImportModuleAttrString:const char *:mod_name::
+PyImport_ImportModuleAttrString:const char *:attr_name::
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 2c10d7fefd44ab..8d4cb94ae2d805 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -1322,6 +1322,11 @@ New features
 * Add :c:func:`PyUnstable_IsImmortal` for determining whether an object is 
:term:`immortal`,
   for debugging purposes.
 
+* Add :c:func:`PyImport_ImportModuleAttr` and
+  :c:func:`PyImport_ImportModuleAttrString` helper functions to import a module
+  and get an attribute of the module.
+  (Contributed by Victor Stinner in :gh:`128911`.)
+
 
 Limited C API changes
 ---------------------
diff --git a/Include/cpython/import.h b/Include/cpython/import.h
index 0fd61c28cafa0e..0ce0b1ee6cce2a 100644
--- a/Include/cpython/import.h
+++ b/Include/cpython/import.h
@@ -21,3 +21,10 @@ struct _frozen {
    collection of frozen modules: */
 
 PyAPI_DATA(const struct _frozen *) PyImport_FrozenModules;
+
+PyAPI_FUNC(PyObject*) PyImport_ImportModuleAttr(
+    PyObject *mod_name,
+    PyObject *attr_name);
+PyAPI_FUNC(PyObject*) PyImport_ImportModuleAttrString(
+    const char *mod_name,
+    const char *attr_name);
diff --git a/Include/internal/pycore_import.h b/Include/internal/pycore_import.h
index 318c712bdfa174..5fe60df0a92fbc 100644
--- a/Include/internal/pycore_import.h
+++ b/Include/internal/pycore_import.h
@@ -31,12 +31,6 @@ extern int _PyImport_FixupBuiltin(
     PyObject *modules
     );
 
-// Export for many shared extensions, like '_json'
-PyAPI_FUNC(PyObject*) _PyImport_GetModuleAttr(PyObject *, PyObject *);
-
-// Export for many shared extensions, like '_datetime'
-PyAPI_FUNC(PyObject*) _PyImport_GetModuleAttrString(const char *, const char 
*);
-
 
 struct _import_runtime_state {
     /* The builtin modules (defined in config.c). */
diff --git a/Lib/test/test_capi/test_import.py 
b/Lib/test/test_capi/test_import.py
index 94f96728d9174b..25136624ca4ed9 100644
--- a/Lib/test/test_capi/test_import.py
+++ b/Lib/test/test_capi/test_import.py
@@ -7,6 +7,7 @@
 from test.support import import_helper
 from test.support.warnings_helper import check_warnings
 
+_testcapi = import_helper.import_module('_testcapi')
 _testlimitedcapi = import_helper.import_module('_testlimitedcapi')
 NULL = None
 
@@ -148,7 +149,7 @@ def check_frozen_import(self, import_frozen_module):
         try:
             self.assertEqual(import_frozen_module('zipimport'), 1)
 
-            # import zipimport again
+            # import zipimport again
             self.assertEqual(import_frozen_module('zipimport'), 1)
         finally:
             sys.modules['zipimport'] = old_zipimport
@@ -317,6 +318,59 @@ def test_executecodemoduleobject(self):
         # CRASHES execute_code_func(NULL, code, NULL, NULL)
         # CRASHES execute_code_func(name, NULL, NULL, NULL)
 
+    def check_importmoduleattr(self, importmoduleattr):
+        self.assertIs(importmoduleattr('sys', 'argv'), sys.argv)
+        self.assertIs(importmoduleattr('types', 'ModuleType'), 
types.ModuleType)
+
+        # module name containing a dot
+        attr = importmoduleattr('email.message', 'Message')
+        from email.message import Message
+        self.assertIs(attr, Message)
+
+        with self.assertRaises(ImportError):
+            # nonexistent module
+            importmoduleattr('nonexistentmodule', 'attr')
+        with self.assertRaises(AttributeError):
+            # nonexistent attribute
+            importmoduleattr('sys', 'nonexistentattr')
+        with self.assertRaises(AttributeError):
+            # attribute name containing a dot
+            importmoduleattr('sys', 'implementation.name')
+
+    def test_importmoduleattr(self):
+        # Test PyImport_ImportModuleAttr()
+        importmoduleattr = _testcapi.PyImport_ImportModuleAttr
+        self.check_importmoduleattr(importmoduleattr)
+
+        # Invalid module name type
+        for mod_name in (object(), 123, b'bytes'):
+            with self.subTest(mod_name=mod_name):
+                with self.assertRaises(TypeError):
+                    importmoduleattr(mod_name, "attr")
+
+        # Invalid attribute name type
+        for attr_name in (object(), 123, b'bytes'):
+            with self.subTest(attr_name=attr_name):
+                with self.assertRaises(TypeError):
+                    importmoduleattr("sys", attr_name)
+
+        with self.assertRaises(SystemError):
+            importmoduleattr(NULL, "argv")
+        # CRASHES importmoduleattr("sys", NULL)
+
+    def test_importmoduleattrstring(self):
+        # Test PyImport_ImportModuleAttrString()
+        importmoduleattr = _testcapi.PyImport_ImportModuleAttrString
+        self.check_importmoduleattr(importmoduleattr)
+
+        with self.assertRaises(UnicodeDecodeError):
+            importmoduleattr(b"sys\xff", "argv")
+        with self.assertRaises(UnicodeDecodeError):
+            importmoduleattr("sys", b"argv\xff")
+
+        # CRASHES importmoduleattr(NULL, "argv")
+        # CRASHES importmoduleattr("sys", NULL)
+
     # TODO: test PyImport_GetImporter()
     # TODO: test PyImport_ReloadModule()
     # TODO: test PyImport_ExtendInittab()
diff --git 
a/Misc/NEWS.d/next/C_API/2025-01-16-12-47-01.gh-issue-128911.mHVJ4x.rst 
b/Misc/NEWS.d/next/C_API/2025-01-16-12-47-01.gh-issue-128911.mHVJ4x.rst
new file mode 100644
index 00000000000000..d32cd00cd5d605
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-01-16-12-47-01.gh-issue-128911.mHVJ4x.rst
@@ -0,0 +1,3 @@
+Add :c:func:`PyImport_ImportModuleAttr` and 
:c:func:`PyImport_ImportModuleAttrString`
+helper functions to import a module and get an attribute of the module. Patch
+by Victor Stinner.
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 6b6a8ae57a5119..563bbc1bda6223 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -162,7 +162,7 @@
 @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c 
_xxtestfuzz/fuzzer.c
 @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
 @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c 
_testinternalcapi/test_lock.c _testinternalcapi/pytime.c 
_testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c 
_testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c 
_testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c 
_testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c 
_testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c 
_testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c 
_testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c 
_testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c 
_testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c  
_testcapi/config.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c 
_testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c 
_testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c 
_testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c 
_testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c 
_testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c 
_testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c 
_testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c 
_testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c 
_testcapi/config.c _testcapi/import.c
 @MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c 
_testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c 
_testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c 
_testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c 
_testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c 
_testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c 
_testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c 
_testlimitedcapi/tuple.c _testlimitedcapi/unicode.c 
_testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c
 @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
 @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_ctypes/callbacks.c b/Modules/_ctypes/callbacks.c
index 89c0749a093765..591206a78035c5 100644
--- a/Modules/_ctypes/callbacks.c
+++ b/Modules/_ctypes/callbacks.c
@@ -492,7 +492,7 @@ long Call_GetClassObject(REFCLSID rclsid, REFIID riid, 
LPVOID *ppv)
     if (context == NULL)
         context = PyUnicode_InternFromString("_ctypes.DllGetClassObject");
 
-    func = _PyImport_GetModuleAttrString("ctypes", "DllGetClassObject");
+    func = PyImport_ImportModuleAttrString("ctypes", "DllGetClassObject");
     if (!func) {
         PyErr_WriteUnraisable(context ? context : Py_None);
         /* There has been a warning before about this already */
diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c
index 5ca5b62427600d..d63a46a3bc23d2 100644
--- a/Modules/_ctypes/stgdict.c
+++ b/Modules/_ctypes/stgdict.c
@@ -257,7 +257,7 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject 
*fields, int isStruct
         goto error;
     }
 
-    PyObject *layout_func = _PyImport_GetModuleAttrString("ctypes._layout",
+    PyObject *layout_func = PyImport_ImportModuleAttrString("ctypes._layout",
                                                           "get_layout");
     if (!layout_func) {
         goto error;
diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c
index c6835738348ff9..7213a5be07de4b 100644
--- a/Modules/_cursesmodule.c
+++ b/Modules/_cursesmodule.c
@@ -226,7 +226,7 @@ _PyCursesCheckFunction(int called, const char *funcname)
     if (called == TRUE) {
         return 1;
     }
-    PyObject *exc = _PyImport_GetModuleAttrString("_curses", "error");
+    PyObject *exc = PyImport_ImportModuleAttrString("_curses", "error");
     if (exc != NULL) {
         PyErr_Format(exc, "must call %s() first", funcname);
         Py_DECREF(exc);
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index ff2e6d6a098ad9..a486af7833c2c6 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -1839,7 +1839,7 @@ wrap_strftime(PyObject *object, PyObject *format, 
PyObject *timetuple,
     assert(object && format && timetuple);
     assert(PyUnicode_Check(format));
 
-    PyObject *strftime = _PyImport_GetModuleAttrString("time", "strftime");
+    PyObject *strftime = PyImport_ImportModuleAttrString("time", "strftime");
     if (strftime == NULL) {
         return NULL;
     }
@@ -2022,7 +2022,7 @@ static PyObject *
 time_time(void)
 {
     PyObject *result = NULL;
-    PyObject *time = _PyImport_GetModuleAttrString("time", "time");
+    PyObject *time = PyImport_ImportModuleAttrString("time", "time");
 
     if (time != NULL) {
         result = PyObject_CallNoArgs(time);
@@ -2040,7 +2040,7 @@ build_struct_time(int y, int m, int d, int hh, int mm, 
int ss, int dstflag)
     PyObject *struct_time;
     PyObject *result;
 
-    struct_time = _PyImport_GetModuleAttrString("time", "struct_time");
+    struct_time = PyImport_ImportModuleAttrString("time", "struct_time");
     if (struct_time == NULL) {
         return NULL;
     }
diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c
index 78cf6b1426493b..3dcb3e9870c8a4 100644
--- a/Modules/_decimal/_decimal.c
+++ b/Modules/_decimal/_decimal.c
@@ -3474,7 +3474,7 @@ pydec_format(PyObject *dec, PyObject *context, PyObject 
*fmt, decimal_state *sta
     PyObject *u;
 
     if (state->PyDecimal == NULL) {
-        state->PyDecimal = _PyImport_GetModuleAttrString("_pydecimal", 
"Decimal");
+        state->PyDecimal = PyImport_ImportModuleAttrString("_pydecimal", 
"Decimal");
         if (state->PyDecimal == NULL) {
             return NULL;
         }
diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c
index 355f322d304c2f..b5b0b82571f882 100644
--- a/Modules/_elementtree.c
+++ b/Modules/_elementtree.c
@@ -16,7 +16,6 @@
 #endif
 
 #include "Python.h"
-#include "pycore_import.h"        // _PyImport_GetModuleAttrString()
 #include "pycore_pyhash.h"        // _Py_HashSecret
 
 #include <stddef.h>               // offsetof()
@@ -4393,7 +4392,7 @@ module_exec(PyObject *m)
     CREATE_TYPE(m, st->Element_Type, &element_spec);
     CREATE_TYPE(m, st->XMLParser_Type, &xmlparser_spec);
 
-    st->deepcopy_obj = _PyImport_GetModuleAttrString("copy", "deepcopy");
+    st->deepcopy_obj = PyImport_ImportModuleAttrString("copy", "deepcopy");
     if (st->deepcopy_obj == NULL) {
         goto error;
     }
@@ -4403,7 +4402,7 @@ module_exec(PyObject *m)
         goto error;
 
     /* link against pyexpat */
-    if (!(st->expat_capsule = _PyImport_GetModuleAttrString("pyexpat", 
"expat_CAPI")))
+    if (!(st->expat_capsule = PyImport_ImportModuleAttrString("pyexpat", 
"expat_CAPI")))
         goto error;
     if (!(st->expat_capi = PyCapsule_GetPointer(st->expat_capsule, 
PyExpat_CAPSULE_NAME)))
         goto error;
diff --git a/Modules/_json.c b/Modules/_json.c
index 31a5e935e13ad9..5532e252819bbd 100644
--- a/Modules/_json.c
+++ b/Modules/_json.c
@@ -302,7 +302,7 @@ raise_errmsg(const char *msg, PyObject *s, Py_ssize_t end)
     /* Use JSONDecodeError exception to raise a nice looking ValueError 
subclass */
     _Py_DECLARE_STR(json_decoder, "json.decoder");
     PyObject *JSONDecodeError =
-         _PyImport_GetModuleAttr(&_Py_STR(json_decoder), 
&_Py_ID(JSONDecodeError));
+         PyImport_ImportModuleAttr(&_Py_STR(json_decoder), 
&_Py_ID(JSONDecodeError));
     if (JSONDecodeError == NULL) {
         return;
     }
diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c
index 51ad9fc7da8492..29d9e5b6ef2cbe 100644
--- a/Modules/_lsprof.c
+++ b/Modules/_lsprof.c
@@ -775,7 +775,7 @@ _lsprof_Profiler_enable_impl(ProfilerObject *self, int 
subcalls,
         return NULL;
     }
 
-    PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring");
+    PyObject* monitoring = PyImport_ImportModuleAttrString("sys", 
"monitoring");
     if (!monitoring) {
         return NULL;
     }
@@ -857,7 +857,7 @@ _lsprof_Profiler_disable_impl(ProfilerObject *self)
     }
     if (self->flags & POF_ENABLED) {
         PyObject* result = NULL;
-        PyObject* monitoring = _PyImport_GetModuleAttrString("sys", 
"monitoring");
+        PyObject* monitoring = PyImport_ImportModuleAttrString("sys", 
"monitoring");
 
         if (!monitoring) {
             return NULL;
@@ -973,7 +973,7 @@ profiler_init_impl(ProfilerObject *self, PyObject *timer, 
double timeunit,
     Py_XSETREF(self->externalTimer, Py_XNewRef(timer));
     self->tool_id = PY_MONITORING_PROFILER_ID;
 
-    PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring");
+    PyObject* monitoring = PyImport_ImportModuleAttrString("sys", 
"monitoring");
     if (!monitoring) {
         return -1;
     }
diff --git a/Modules/_operator.c b/Modules/_operator.c
index ce3ef015710223..59987b8f143da2 100644
--- a/Modules/_operator.c
+++ b/Modules/_operator.c
@@ -1868,7 +1868,7 @@ methodcaller_reduce(methodcallerobject *mc, PyObject 
*Py_UNUSED(ignored))
         PyObject *constructor;
         PyObject *newargs[2];
 
-        partial = _PyImport_GetModuleAttrString("functools", "partial");
+        partial = PyImport_ImportModuleAttrString("functools", "partial");
         if (!partial)
             return NULL;
 
diff --git a/Modules/_pickle.c b/Modules/_pickle.c
index a6cfb2deeb23c1..5641f93391c551 100644
--- a/Modules/_pickle.c
+++ b/Modules/_pickle.c
@@ -362,7 +362,7 @@ _Pickle_InitState(PickleState *st)
     }
     Py_CLEAR(compat_pickle);
 
-    st->codecs_encode = _PyImport_GetModuleAttrString("codecs", "encode");
+    st->codecs_encode = PyImport_ImportModuleAttrString("codecs", "encode");
     if (st->codecs_encode == NULL) {
         goto error;
     }
@@ -373,7 +373,7 @@ _Pickle_InitState(PickleState *st)
         goto error;
     }
 
-    st->partial = _PyImport_GetModuleAttrString("functools", "partial");
+    st->partial = PyImport_ImportModuleAttrString("functools", "partial");
     if (!st->partial)
         goto error;
 
diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c
index 62598ecc864120..0c98f5065ee303 100644
--- a/Modules/_sqlite/connection.c
+++ b/Modules/_sqlite/connection.c
@@ -34,7 +34,6 @@
 #include "prepare_protocol.h"
 #include "util.h"
 
-#include "pycore_import.h"        // _PyImport_GetModuleAttrString()
 #include "pycore_modsupport.h"    // _PyArg_NoKeywords()
 #include "pycore_pyerrors.h"      // _PyErr_ChainExceptions1()
 #include "pycore_pylifecycle.h"   // _Py_IsInterpreterFinalizing()
@@ -2000,7 +1999,7 @@ pysqlite_connection_iterdump_impl(pysqlite_Connection 
*self,
         return NULL;
     }
 
-    PyObject *iterdump = _PyImport_GetModuleAttrString(MODULE_NAME ".dump", 
"_iterdump");
+    PyObject *iterdump = PyImport_ImportModuleAttrString(MODULE_NAME ".dump", 
"_iterdump");
     if (!iterdump) {
         if (!PyErr_Occurred()) {
             PyErr_SetString(self->OperationalError,
diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c
index 698e81d9b897d0..73d55fb44e2e15 100644
--- a/Modules/_sqlite/module.c
+++ b/Modules/_sqlite/module.c
@@ -33,8 +33,6 @@
 #include "row.h"
 #include "blob.h"
 
-#include "pycore_import.h"        // _PyImport_GetModuleAttrString()
-
 #if SQLITE_VERSION_NUMBER < 3015002
 #error "SQLite 3.15.2 or higher required"
 #endif
@@ -234,7 +232,7 @@ static int
 load_functools_lru_cache(PyObject *module)
 {
     pysqlite_state *state = pysqlite_get_state(module);
-    state->lru_cache = _PyImport_GetModuleAttrString("functools", "lru_cache");
+    state->lru_cache = PyImport_ImportModuleAttrString("functools", 
"lru_cache");
     if (state->lru_cache == NULL) {
         return -1;
     }
diff --git a/Modules/_sre/sre.c b/Modules/_sre/sre.c
index d0025dd21e045b..0d8d4843d33c1b 100644
--- a/Modules/_sre/sre.c
+++ b/Modules/_sre/sre.c
@@ -1169,7 +1169,7 @@ compile_template(_sremodulestate *module_state,
     /* delegate to Python code */
     PyObject *func = module_state->compile_template;
     if (func == NULL) {
-        func = _PyImport_GetModuleAttrString("re", "_compile_template");
+        func = PyImport_ImportModuleAttrString("re", "_compile_template");
         if (func == NULL) {
             return NULL;
         }
diff --git a/Modules/_testcapi/import.c b/Modules/_testcapi/import.c
new file mode 100644
index 00000000000000..27d37498f3cd83
--- /dev/null
+++ b/Modules/_testcapi/import.c
@@ -0,0 +1,44 @@
+#include "parts.h"
+#include "util.h"
+
+// Test PyImport_ImportModuleAttr()
+static PyObject *
+pyimport_importmoduleattr(PyObject *self, PyObject *args)
+{
+    PyObject *mod_name, *attr_name;
+    if (!PyArg_ParseTuple(args, "OO", &mod_name, &attr_name)) {
+        return NULL;
+    }
+    NULLABLE(mod_name);
+    NULLABLE(attr_name);
+
+    return PyImport_ImportModuleAttr(mod_name, attr_name);
+}
+
+
+// Test PyImport_ImportModuleAttrString()
+static PyObject *
+pyimport_importmoduleattrstring(PyObject *self, PyObject *args)
+{
+    const char *mod_name, *attr_name;
+    Py_ssize_t len;
+    if (!PyArg_ParseTuple(args, "z#z#", &mod_name, &len, &attr_name, &len)) {
+        return NULL;
+    }
+
+    return PyImport_ImportModuleAttrString(mod_name, attr_name);
+}
+
+
+static PyMethodDef test_methods[] = {
+    {"PyImport_ImportModuleAttr", pyimport_importmoduleattr, METH_VARARGS},
+    {"PyImport_ImportModuleAttrString", pyimport_importmoduleattrstring, 
METH_VARARGS},
+    {NULL},
+};
+
+int
+_PyTestCapi_Init_Import(PyObject *m)
+{
+    return PyModule_AddFunctions(m, test_methods);
+}
+
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index 65ba77596c760e..792552d8097312 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -61,5 +61,6 @@ int _PyTestCapi_Init_Time(PyObject *module);
 int _PyTestCapi_Init_Monitoring(PyObject *module);
 int _PyTestCapi_Init_Object(PyObject *module);
 int _PyTestCapi_Init_Config(PyObject *mod);
+int _PyTestCapi_Init_Import(PyObject *mod);
 
 #endif // Py_TESTCAPI_PARTS_H
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index c405a352ed74a1..4da23ba82d5756 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -4401,6 +4401,9 @@ PyInit__testcapi(void)
     if (_PyTestCapi_Init_Config(m) < 0) {
         return NULL;
     }
+    if (_PyTestCapi_Init_Import(m) < 0) {
+        return NULL;
+    }
 
     PyState_AddModule(m, &_testcapimodule);
     return m;
diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c
index c5292575c22f23..1fcea9ce8b1261 100644
--- a/Modules/_zoneinfo.c
+++ b/Modules/_zoneinfo.c
@@ -782,7 +782,7 @@ zoneinfo_reduce(PyObject *obj_self, PyObject *unused)
     if (self->source == SOURCE_FILE) {
         // Objects constructed from files cannot be pickled.
         PyObject *pickle_error =
-            _PyImport_GetModuleAttrString("pickle", "PicklingError");
+            PyImport_ImportModuleAttrString("pickle", "PicklingError");
         if (pickle_error == NULL) {
             return NULL;
         }
@@ -2554,7 +2554,7 @@ static PyObject *
 new_weak_cache(void)
 {
     PyObject *WeakValueDictionary =
-            _PyImport_GetModuleAttrString("weakref", "WeakValueDictionary");
+            PyImport_ImportModuleAttrString("weakref", "WeakValueDictionary");
     if (WeakValueDictionary == NULL) {
         return NULL;
     }
@@ -2732,12 +2732,12 @@ zoneinfomodule_exec(PyObject *m)
 
     /* Populate imports */
     state->_tzpath_find_tzfile =
-        _PyImport_GetModuleAttrString("zoneinfo._tzpath", "find_tzfile");
+        PyImport_ImportModuleAttrString("zoneinfo._tzpath", "find_tzfile");
     if (state->_tzpath_find_tzfile == NULL) {
         goto error;
     }
 
-    state->io_open = _PyImport_GetModuleAttrString("io", "open");
+    state->io_open = PyImport_ImportModuleAttrString("io", "open");
     if (state->io_open == NULL) {
         goto error;
     }
diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c
index 28c6a0ae05c598..dc1729a7a3a558 100644
--- a/Modules/arraymodule.c
+++ b/Modules/arraymodule.c
@@ -2285,7 +2285,7 @@ array_array___reduce_ex___impl(arrayobject *self, 
PyTypeObject *cls,
     assert(state != NULL);
 
     if (state->array_reconstructor == NULL) {
-        state->array_reconstructor = _PyImport_GetModuleAttrString(
+        state->array_reconstructor = PyImport_ImportModuleAttrString(
                 "array", "_array_reconstructor");
         if (state->array_reconstructor == NULL) {
             return NULL;
@@ -3206,7 +3206,7 @@ array_modexec(PyObject *m)
         return -1;
     }
 
-    PyObject *mutablesequence = _PyImport_GetModuleAttrString(
+    PyObject *mutablesequence = PyImport_ImportModuleAttrString(
             "collections.abc", "MutableSequence");
     if (!mutablesequence) {
         Py_DECREF((PyObject *)state->ArrayType);
diff --git a/Modules/cjkcodecs/cjkcodecs.h b/Modules/cjkcodecs/cjkcodecs.h
index 2b446ba5226ac0..737a7a042753a9 100644
--- a/Modules/cjkcodecs/cjkcodecs.h
+++ b/Modules/cjkcodecs/cjkcodecs.h
@@ -13,7 +13,6 @@
 
 #include "Python.h"
 #include "multibytecodec.h"
-#include "pycore_import.h"        // _PyImport_GetModuleAttrString()
 
 
 /* a unicode "undefined" code point */
@@ -299,7 +298,7 @@ add_codecs(cjkcodecs_module_state *st)                      
    \
 static PyObject *
 getmultibytecodec(void)
 {
-    return _PyImport_GetModuleAttrString("_multibytecodec", "__create_codec");
+    return PyImport_ImportModuleAttrString("_multibytecodec", 
"__create_codec");
 }
 
 static void
diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c
index b44b964b29484b..a15ced22677ab7 100644
--- a/Modules/faulthandler.c
+++ b/Modules/faulthandler.c
@@ -1346,7 +1346,7 @@ PyInit_faulthandler(void)
 static int
 faulthandler_init_enable(void)
 {
-    PyObject *enable = _PyImport_GetModuleAttrString("faulthandler", "enable");
+    PyObject *enable = PyImport_ImportModuleAttrString("faulthandler", 
"enable");
     if (enable == NULL) {
         return -1;
     }
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index a35a848a7ca4b8..6d9b365ea9ceb2 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -9877,7 +9877,7 @@ wait_helper(PyObject *module, pid_t pid, int status, 
struct rusage *ru)
         memset(ru, 0, sizeof(*ru));
     }
 
-    struct_rusage = _PyImport_GetModuleAttrString("resource", "struct_rusage");
+    struct_rusage = PyImport_ImportModuleAttrString("resource", 
"struct_rusage");
     if (struct_rusage == NULL)
         return NULL;
 
diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c
index e14e114a6dafd0..c75e2ba28c5b4e 100644
--- a/Modules/selectmodule.c
+++ b/Modules/selectmodule.c
@@ -14,7 +14,6 @@
 
 #include "Python.h"
 #include "pycore_fileutils.h"     // _Py_set_inheritable()
-#include "pycore_import.h"        // _PyImport_GetModuleAttrString()
 #include "pycore_time.h"          // _PyTime_FromSecondsObject()
 
 #include <stdbool.h>
@@ -1996,7 +1995,7 @@ kqueue_tracking_init(PyObject *module) {
     // Register a callback to invalidate kqueues with open fds after fork.
     PyObject *register_at_fork = NULL, *cb = NULL, *args = NULL,
              *kwargs = NULL, *result = NULL;
-    register_at_fork = _PyImport_GetModuleAttrString("posix",
+    register_at_fork = PyImport_ImportModuleAttrString("posix",
                                                      "register_at_fork");
     if (register_at_fork == NULL) {
         goto finally;
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
index 5d0cd52a93a2d3..8d2cbff662b9a3 100644
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -979,7 +979,7 @@ time_strptime(PyObject *self, PyObject *args)
 {
     PyObject *func, *result;
 
-    func = _PyImport_GetModuleAttrString("_strptime", "_strptime_time");
+    func = PyImport_ImportModuleAttrString("_strptime", "_strptime_time");
     if (!func) {
         return NULL;
     }
diff --git a/Objects/abstract.c b/Objects/abstract.c
index c92ef10aa79648..db7b9263711f68 100644
--- a/Objects/abstract.c
+++ b/Objects/abstract.c
@@ -583,7 +583,7 @@ PyBuffer_SizeFromFormat(const char *format)
     PyObject *fmt = NULL;
     Py_ssize_t itemsize = -1;
 
-    calcsize = _PyImport_GetModuleAttrString("struct", "calcsize");
+    calcsize = PyImport_ImportModuleAttrString("struct", "calcsize");
     if (calcsize == NULL) {
         goto done;
     }
diff --git a/Objects/fileobject.c b/Objects/fileobject.c
index c377d1bb28b56f..7025b5bcffc1c8 100644
--- a/Objects/fileobject.c
+++ b/Objects/fileobject.c
@@ -34,7 +34,7 @@ PyFile_FromFd(int fd, const char *name, const char *mode, int 
buffering, const c
     PyObject *open, *stream;
 
     /* import _io in case we are being used to open io.py */
-    open = _PyImport_GetModuleAttrString("_io", "open");
+    open = PyImport_ImportModuleAttrString("_io", "open");
     if (open == NULL)
         return NULL;
     stream = PyObject_CallFunction(open, "isisssO", fd, mode,
@@ -506,7 +506,7 @@ PyFile_OpenCodeObject(PyObject *path)
     if (hook) {
         f = hook(path, _PyRuntime.open_code_userdata);
     } else {
-        PyObject *open = _PyImport_GetModuleAttrString("_io", "open");
+        PyObject *open = PyImport_ImportModuleAttrString("_io", "open");
         if (open) {
             f = PyObject_CallFunction(open, "Os", path, "rb");
             Py_DECREF(open);
diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c
index ea4d24dc690768..331363b2babbef 100644
--- a/Objects/memoryobject.c
+++ b/Objects/memoryobject.c
@@ -2083,7 +2083,7 @@ struct_get_unpacker(const char *fmt, Py_ssize_t itemsize)
     PyObject *format = NULL;
     struct unpacker *x = NULL;
 
-    Struct = _PyImport_GetModuleAttrString("struct", "Struct");
+    Struct = PyImport_ImportModuleAttrString("struct", "Struct");
     if (Struct == NULL)
         return NULL;
 
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index c41235eac356af..733bb69acb16e2 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -127,6 +127,7 @@
     <ClCompile Include="..\Modules\_testcapi\run.c" />
     <ClCompile Include="..\Modules\_testcapi\monitoring.c" />
     <ClCompile Include="..\Modules\_testcapi\config.c" />
+    <ClCompile Include="..\Modules\_testcapi\import.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc" />
diff --git a/PCbuild/_testcapi.vcxproj.filters 
b/PCbuild/_testcapi.vcxproj.filters
index 0a00df655deefc..e8ddd537674a9c 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -114,6 +114,9 @@
     <ClCompile Include="..\Modules\_testcapi\config.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\Modules\_testcapi\import.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc">
diff --git a/Parser/pegen.c b/Parser/pegen.c
index bb98e7b184a4dc..83b0022e47d619 100644
--- a/Parser/pegen.c
+++ b/Parser/pegen.c
@@ -111,7 +111,7 @@ init_normalization(Parser *p)
     if (p->normalize) {
         return 1;
     }
-    p->normalize = _PyImport_GetModuleAttrString("unicodedata", "normalize");
+    p->normalize = PyImport_ImportModuleAttrString("unicodedata", "normalize");
     if (!p->normalize)
     {
         return 0;
diff --git a/Parser/tokenizer/file_tokenizer.c 
b/Parser/tokenizer/file_tokenizer.c
index 2750527da484aa..efe9fb9b56abaf 100644
--- a/Parser/tokenizer/file_tokenizer.c
+++ b/Parser/tokenizer/file_tokenizer.c
@@ -158,7 +158,7 @@ fp_setreadl(struct tok_state *tok, const char* enc)
         return 0;
     }
 
-    open = _PyImport_GetModuleAttrString("io", "open");
+    open = PyImport_ImportModuleAttrString("io", "open");
     if (open == NULL) {
         return 0;
     }
diff --git a/Python/import.c b/Python/import.c
index b3648e24d0e064..dd7a0b4b1ed8de 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -4111,7 +4111,7 @@ init_zipimport(PyThreadState *tstate, int verbose)
         PySys_WriteStderr("# installing zipimport hook\n");
     }
 
-    PyObject *zipimporter = _PyImport_GetModuleAttrString("zipimport", 
"zipimporter");
+    PyObject *zipimporter = PyImport_ImportModuleAttrString("zipimport", 
"zipimporter");
     if (zipimporter == NULL) {
         _PyErr_Clear(tstate); /* No zipimporter object -- okay */
         if (verbose) {
@@ -4174,7 +4174,7 @@ _PyImport_FiniExternal(PyInterpreterState *interp)
 /******************/
 
 PyObject *
-_PyImport_GetModuleAttr(PyObject *modname, PyObject *attrname)
+PyImport_ImportModuleAttr(PyObject *modname, PyObject *attrname)
 {
     PyObject *mod = PyImport_Import(modname);
     if (mod == NULL) {
@@ -4186,7 +4186,7 @@ _PyImport_GetModuleAttr(PyObject *modname, PyObject 
*attrname)
 }
 
 PyObject *
-_PyImport_GetModuleAttrString(const char *modname, const char *attrname)
+PyImport_ImportModuleAttrString(const char *modname, const char *attrname)
 {
     PyObject *pmodname = PyUnicode_FromString(modname);
     if (pmodname == NULL) {
@@ -4197,7 +4197,7 @@ _PyImport_GetModuleAttrString(const char *modname, const 
char *attrname)
         Py_DECREF(pmodname);
         return NULL;
     }
-    PyObject *result = _PyImport_GetModuleAttr(pmodname, pattrname);
+    PyObject *result = PyImport_ImportModuleAttr(pmodname, pattrname);
     Py_DECREF(pattrname);
     Py_DECREF(pmodname);
     return result;
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 00a98af998cfce..7031d743174650 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -2609,7 +2609,7 @@ create_stdio(const PyConfig *config, PyObject* io,
 
 #ifdef HAVE_WINDOWS_CONSOLE_IO
     /* Windows console IO is always UTF-8 encoded */
-    PyTypeObject *winconsoleio_type = (PyTypeObject *)_PyImport_GetModuleAttr(
+    PyTypeObject *winconsoleio_type = (PyTypeObject 
*)PyImport_ImportModuleAttr(
             &_Py_ID(_io), &_Py_ID(_WindowsConsoleIO));
     if (winconsoleio_type == NULL) {
         goto error;
@@ -2714,7 +2714,7 @@ init_set_builtins_open(void)
         goto error;
     }
 
-    if (!(wrapper = _PyImport_GetModuleAttrString("io", "open"))) {
+    if (!(wrapper = PyImport_ImportModuleAttrString("io", "open"))) {
         goto error;
     }
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to