Skip to content

Commit 9c588a9

Browse files
committed
Add PyImport_ImportModuleAttr() and PyType_GetQualName()
Add functions: * PyImport_ImportModuleAttr() * PyImport_ImportModuleAttrString() * PyType_GetFullyQualifiedName() * PyType_GetModuleName() * PyType_GetQualName() Fix PyUnicode_EqualToUTF8AndSize() on PyPy: call PyUnicode_READY(). Closes #188
1 parent ebbf075 commit 9c588a9

4 files changed

Lines changed: 177 additions & 3 deletions

File tree

‎docs/api.rst‎

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,14 @@ Python 3.14
306306

307307
Not available on PyPy. ``Py_REFCNT()`` semantics is different on PyPy.
308308

309+
.. c:function:: PyObject* PyImport_ImportModuleAttr(PyObject *mod_name, PyObject *attr_name)
310+
311+
See `PyImport_ImportModuleAttr() documentation <https://docs.python.org/dev/c-api/import.html#c.PyImport_ImportModuleAttr>`__.
312+
313+
.. c:function:: PyObject* PyImport_ImportModuleAttrString(const char *mod_name, const char *attr_name)
314+
315+
See `PyImport_ImportModuleAttrString() documentation <https://docs.python.org/dev/c-api/import.html#c.PyImport_ImportModuleAttrString>`__.
316+
309317
Not supported:
310318

311319
* ``PyConfig_Names()``
@@ -500,6 +508,14 @@ Python 3.13
500508

501509
See `PyDict_SetDefaultRef() documentation <https://docs.python.org/dev/c-api/dict.html#c.PyDict_SetDefaultRef>`__.
502510

511+
.. c:function:: PyObject* PyType_GetModuleName(PyTypeObject *type)
512+
513+
See `PyType_GetModuleName() documentation <https://docs.python.org/dev/c-api/type.html#c.PyType_GetModuleName>`__.
514+
515+
.. c:function:: PyObject* PyType_GetFullyQualifiedName(PyTypeObject *type)
516+
517+
See `PyType_GetFullyQualifiedName() documentation <https://docs.python.org/dev/c-api/type.html#c.PyType_GetFullyQualifiedName>`__.
518+
503519

504520
Not supported:
505521

@@ -510,8 +526,6 @@ Not supported:
510526
* ``PyObject_GenericHash()``.
511527
* ``PySys_Audit()``.
512528
* ``PySys_AuditTuple()``.
513-
* ``PyType_GetFullyQualifiedName()``
514-
* ``PyType_GetModuleName()``
515529

516530
Python 3.12
517531
-----------
@@ -653,11 +667,15 @@ Python 3.11
653667

654668
Not available on PyPy nor on limited C API 3.10 and older.
655669

670+
.. c:function:: PyObject* PyType_GetQualName(PyTypeObject *type)
671+
672+
See `PyType_GetQualName() documentation <https://docs.python.org/dev/c-api/type.html#c.PyType_GetQualName>`__.
673+
674+
656675
Not supported:
657676

658677
* ``PyType_GetModuleByDef()``.
659678
* ``PyType_GetName()``.
660-
* ``PyType_GetQualName()``.
661679
* ``Py_Version`` constant.
662680
* ``PyErr_GetHandledException()``, ``PyErr_SetHandledException()``.
663681
* ``PyFrame_GetGenerator()``.

‎docs/changelog.rst‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
Changelog
22
=========
33

4+
* 2026-10-06: Add functions:
5+
6+
* ``PyImport_ImportModuleAttr()``
7+
* ``PyImport_ImportModuleAttrString()``
8+
* ``PyType_GetFullyQualifiedName()``
9+
* ``PyType_GetModuleName()``
10+
* ``PyType_GetQualName()``
11+
412
* 2026-10-04: Add support for the limited C API (``Py_LIMITED_API``).
513
* 2026-09-23: ``PyUnstable_Object_IsUniquelyReferenced()``,
614
``PyUnstable_TryIncRef()`` and ``PyUnstable_EnableTryIncRef()`` are no longer

‎pythoncapi_compat.h‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,13 @@ PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t str_
10101010
PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
10111011

10121012
#ifndef Py_LIMITED_API
1013+
if (PyUnicode_READY(unicode) < 0) {
1014+
// Memory allocation failure. The API cannot report error,
1015+
// so ignore the exception and return 0.
1016+
res = 0;
1017+
goto done;
1018+
}
1019+
10131020
if (PyUnicode_IS_ASCII(unicode)) {
10141021
utf8 = PyUnicode_DATA(unicode);
10151022
len = PyUnicode_GET_LENGTH(unicode);
@@ -2858,6 +2865,92 @@ PyUnstable_SetImmortal(PyObject *op)
28582865
}
28592866
#endif
28602867

2868+
2869+
// Python 3.14 added PyImport_ImportModuleAttr()
2870+
// and PyImport_ImportModuleAttrString()
2871+
#if PY_VERSION_HEX < 0x030E0000 || defined(Py_LIMITED_API)
2872+
static inline PyObject*
2873+
PyImport_ImportModuleAttr(PyObject *mod_name, PyObject *attr_name)
2874+
{
2875+
PyObject *mod = PyImport_Import(mod_name);
2876+
if (mod == NULL) {
2877+
return NULL;
2878+
}
2879+
PyObject *result = PyObject_GetAttr(mod, attr_name);
2880+
Py_DECREF(mod);
2881+
return result;
2882+
}
2883+
2884+
static inline PyObject *
2885+
PyImport_ImportModuleAttrString(const char *mod_name, const char *attr_name)
2886+
{
2887+
PyObject *mod_name_obj = PyUnicode_FromString(mod_name);
2888+
if (mod_name_obj == NULL) {
2889+
return NULL;
2890+
}
2891+
PyObject *attr_name_obj = PyUnicode_FromString(attr_name);
2892+
if (attr_name_obj == NULL) {
2893+
Py_DECREF(mod_name_obj);
2894+
return NULL;
2895+
}
2896+
PyObject *result = PyImport_ImportModuleAttr(mod_name_obj, attr_name_obj);
2897+
Py_DECREF(attr_name_obj);
2898+
Py_DECREF(mod_name_obj);
2899+
return result;
2900+
}
2901+
#endif
2902+
2903+
2904+
// Python 3.11 added PyType_GetQualName()
2905+
#if (PY_VERSION_HEX < 0x030B0000 \
2906+
|| (defined(Py_LIMITED_API) && Py_LIMITED_API+0 < 0x030B0000))
2907+
static inline PyObject*
2908+
PyType_GetQualName(PyTypeObject *type)
2909+
{
2910+
return PyObject_GetAttrString(_PyObject_CAST(type), "__qualname__");
2911+
}
2912+
#endif
2913+
2914+
2915+
// Python 3.13 added PyType_GetModuleName() and PyType_GetFullyQualifiedName()
2916+
#if PY_VERSION_HEX < 0x030D0000
2917+
static inline PyObject*
2918+
PyType_GetModuleName(PyTypeObject *type)
2919+
{
2920+
return PyObject_GetAttrString(_PyObject_CAST(type), "__module__");
2921+
}
2922+
2923+
static inline PyObject *
2924+
PyType_GetFullyQualifiedName(PyTypeObject *type)
2925+
{
2926+
PyObject *qualname = PyType_GetQualName(type);
2927+
if (qualname == NULL) {
2928+
return NULL;
2929+
}
2930+
2931+
PyObject *module = PyType_GetModuleName(type);
2932+
if (module == NULL) {
2933+
Py_DECREF(qualname);
2934+
return NULL;
2935+
}
2936+
2937+
PyObject *result;
2938+
if (PyUnicode_Check(module)
2939+
&& PyUnicode_EqualToUTF8(module, "builtins") == 0
2940+
&& PyUnicode_EqualToUTF8(module, "__main__") == 0)
2941+
{
2942+
result = PyUnicode_FromFormat("%U.%U", module, qualname);
2943+
}
2944+
else {
2945+
result = Py_NewRef(qualname);
2946+
}
2947+
Py_DECREF(module);
2948+
Py_DECREF(qualname);
2949+
return result;
2950+
}
2951+
#endif
2952+
2953+
28612954
#ifdef __cplusplus
28622955
}
28632956
#endif

‎tests/test_pythoncapi_compat_cext.c‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,28 @@ test_import(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
757757
Py_DECREF(mod2);
758758
Py_DECREF(mod);
759759

760+
// Test PyImport_ImportModuleAttr()
761+
PyObject *mod_name = PyUnicode_FromString("sys");
762+
assert(mod_name != NULL);
763+
PyObject *attr_name = PyUnicode_FromString("platform");
764+
assert(attr_name != NULL);
765+
PyObject *attr = PyImport_ImportModuleAttr(mod_name, attr_name);
766+
Py_DECREF(mod_name);
767+
Py_DECREF(attr_name);
768+
if (attr == NULL) {
769+
return NULL;
770+
}
771+
assert(PyUnicode_Check(attr));
772+
Py_DECREF(attr);
773+
774+
// Test PyImport_ImportModuleAttrString()
775+
attr = PyImport_ImportModuleAttrString("sys", "platform");
776+
if (attr == NULL) {
777+
return NULL;
778+
}
779+
assert(PyUnicode_Check(attr));
780+
Py_DECREF(attr);
781+
760782
Py_RETURN_NONE;
761783
}
762784

@@ -2540,6 +2562,38 @@ test_set_immortal(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
25402562
#endif
25412563

25422564

2565+
static PyObject *
2566+
test_type(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
2567+
{
2568+
PyObject *attr = PyImport_ImportModuleAttrString("datetime", "time");
2569+
if (attr == NULL) {
2570+
return NULL;
2571+
}
2572+
assert(PyType_Check(attr));
2573+
PyTypeObject *type = (PyTypeObject*)attr;
2574+
2575+
PyObject *modname = PyType_GetModuleName(type);
2576+
assert(modname != NULL);
2577+
assert(PyUnicode_EqualToUTF8(modname, "datetime") == 1);
2578+
Py_DECREF(modname);
2579+
Py_DECREF(type);
2580+
2581+
PyObject *qualname = PyType_GetQualName(type);
2582+
assert(qualname != NULL);
2583+
assert(PyUnicode_EqualToUTF8(qualname, "time") == 1);
2584+
Py_DECREF(qualname);
2585+
Py_DECREF(type);
2586+
2587+
PyObject *fqn = PyType_GetFullyQualifiedName(type);
2588+
assert(fqn != NULL);
2589+
assert(PyUnicode_EqualToUTF8(fqn, "datetime.time") == 1);
2590+
Py_DECREF(fqn);
2591+
Py_DECREF(type);
2592+
2593+
Py_RETURN_NONE;
2594+
}
2595+
2596+
25432597
static struct PyMethodDef methods[] = {
25442598
{"test_object", test_object, METH_NOARGS, _Py_NULL},
25452599
{"test_py_is", test_py_is, METH_NOARGS, _Py_NULL},
@@ -2614,6 +2668,7 @@ static struct PyMethodDef methods[] = {
26142668
#if 0x030D0000 <= PY_VERSION_HEX && !defined(PYPY_VERSION) && !defined(Py_LIMITED_API)
26152669
{"test_set_immortal", test_set_immortal, METH_NOARGS, _Py_NULL},
26162670
#endif
2671+
{"test_type", test_type, METH_NOARGS, _Py_NULL},
26172672
{_Py_NULL, _Py_NULL, 0, _Py_NULL}
26182673
};
26192674

0 commit comments

Comments
 (0)