https://github.com/python/cpython/commit/19c3a2ff91ccf7444efadbc8f7e67269060050a2
commit: 19c3a2ff91ccf7444efadbc8f7e67269060050a2
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2024-03-14T16:19:36Z
summary:

gh-111696, PEP 737: Add PyType_GetFullyQualifiedName() function (#116815)

Rewrite tests on type names in Python, they were written in C.

files:
A Misc/NEWS.d/next/C API/2024-03-14-15-17-11.gh-issue-111696.YmnvAi.rst
M Doc/c-api/type.rst
M Doc/data/stable_abi.dat
M Doc/whatsnew/3.13.rst
M Include/object.h
M Lib/test/test_capi/test_misc.py
M Lib/test/test_stable_abi_ctypes.py
M Misc/stable_abi.toml
M Modules/_testcapimodule.c
M Objects/typeobject.c
M PC/python3dll.c

diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst
index 5aaa8147dd3176..c5234233ba7124 100644
--- a/Doc/c-api/type.rst
+++ b/Doc/c-api/type.rst
@@ -185,6 +185,14 @@ Type Objects
 
    .. versionadded:: 3.11
 
+.. c:function:: PyObject* PyType_GetFullyQualifiedName(PyTypeObject *type)
+
+   Return the type's fully qualified name. Equivalent to
+   ``f"{type.__module__}.{type.__qualname__}"``, or ``type.__qualname__`` if
+   ``type.__module__`` is not a string or is equal to ``"builtins"``.
+
+   .. versionadded:: 3.13
+
 .. c:function:: void* PyType_GetSlot(PyTypeObject *type, int slot)
 
    Return the function pointer stored in the given slot. If the
diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index 25629b4da053da..03fe3cef3843b6 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -677,6 +677,7 @@ function,PyType_FromSpecWithBases,3.3,,
 function,PyType_GenericAlloc,3.2,,
 function,PyType_GenericNew,3.2,,
 function,PyType_GetFlags,3.2,,
+function,PyType_GetFullyQualifiedName,3.13,,
 function,PyType_GetModule,3.10,,
 function,PyType_GetModuleState,3.10,,
 function,PyType_GetName,3.11,,
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index ea45fa7c825dfe..cbb5e02aef1ce3 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -1658,6 +1658,12 @@ New Features
   between native integer types and Python :class:`int` objects.
   (Contributed by Steve Dower in :gh:`111140`.)
 
+* Add :c:func:`PyType_GetFullyQualifiedName` function to get the type's fully
+  qualified name. Equivalent to ``f"{type.__module__}.{type.__qualname__}"``,
+  or ``type.__qualname__`` if ``type.__module__`` is not a string or is equal
+  to ``"builtins"``.
+  (Contributed by Victor Stinner in :gh:`111696`.)
+
 
 Porting to Python 3.13
 ----------------------
diff --git a/Include/object.h b/Include/object.h
index 05187fe5dc4f20..3f6f1ab1e68cc6 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -522,6 +522,9 @@ PyAPI_FUNC(void *) PyType_GetModuleState(PyTypeObject *);
 PyAPI_FUNC(PyObject *) PyType_GetName(PyTypeObject *);
 PyAPI_FUNC(PyObject *) PyType_GetQualName(PyTypeObject *);
 #endif
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030D0000
+PyAPI_FUNC(PyObject *) PyType_GetFullyQualifiedName(PyTypeObject *);
+#endif
 #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030C0000
 PyAPI_FUNC(PyObject *) PyType_FromMetaclass(PyTypeObject*, PyObject*, 
PyType_Spec*, PyObject*);
 PyAPI_FUNC(void *) PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls);
diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py
index c1395ab00077cb..6b4f535cc6550a 100644
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -1100,21 +1100,75 @@ class Data(_testcapi.ObjExtraData):
         del d.extra
         self.assertIsNone(d.extra)
 
-    def test_get_type_module_name(self):
+    def test_get_type_name(self):
+        class MyType:
+            pass
+
+        from _testcapi import get_type_name, get_type_qualname, 
get_type_fullyqualname
+        from _testinternalcapi import get_type_module_name
+
         from collections import OrderedDict
         ht = _testcapi.get_heaptype_for_name()
-        for cls, expected in {
-            int: 'builtins',
-            OrderedDict: 'collections',
-            ht: '_testcapi',
-        }.items():
-            with self.subTest(repr(cls)):
-                modname = _testinternalcapi.get_type_module_name(cls)
-                self.assertEqual(modname, expected)
+        for cls, fullname, modname, qualname, name in (
+            (int,
+             'int',
+             'builtins',
+             'int',
+             'int'),
+            (OrderedDict,
+             'collections.OrderedDict',
+             'collections',
+             'OrderedDict',
+             'OrderedDict'),
+            (ht,
+             '_testcapi.HeapTypeNameType',
+             '_testcapi',
+             'HeapTypeNameType',
+             'HeapTypeNameType'),
+            (MyType,
+             f'{__name__}.CAPITest.test_get_type_name.<locals>.MyType',
+             __name__,
+             'CAPITest.test_get_type_name.<locals>.MyType',
+             'MyType'),
+        ):
+            with self.subTest(cls=repr(cls)):
+                self.assertEqual(get_type_fullyqualname(cls), fullname)
+                self.assertEqual(get_type_module_name(cls), modname)
+                self.assertEqual(get_type_qualname(cls), qualname)
+                self.assertEqual(get_type_name(cls), name)
 
+        # override __module__
         ht.__module__ = 'test_module'
-        modname = _testinternalcapi.get_type_module_name(ht)
-        self.assertEqual(modname, 'test_module')
+        self.assertEqual(get_type_fullyqualname(ht), 
'test_module.HeapTypeNameType')
+        self.assertEqual(get_type_module_name(ht), 'test_module')
+        self.assertEqual(get_type_qualname(ht), 'HeapTypeNameType')
+        self.assertEqual(get_type_name(ht), 'HeapTypeNameType')
+
+        # override __name__ and __qualname__
+        MyType.__name__ = 'my_name'
+        MyType.__qualname__ = 'my_qualname'
+        self.assertEqual(get_type_fullyqualname(MyType), 
f'{__name__}.my_qualname')
+        self.assertEqual(get_type_module_name(MyType), __name__)
+        self.assertEqual(get_type_qualname(MyType), 'my_qualname')
+        self.assertEqual(get_type_name(MyType), 'my_name')
+
+        # override also __module__
+        MyType.__module__ = 'my_module'
+        self.assertEqual(get_type_fullyqualname(MyType), 
'my_module.my_qualname')
+        self.assertEqual(get_type_module_name(MyType), 'my_module')
+        self.assertEqual(get_type_qualname(MyType), 'my_qualname')
+        self.assertEqual(get_type_name(MyType), 'my_name')
+
+        # PyType_GetFullyQualifiedName() ignores the module if it's "builtins"
+        # or "__main__" of it is not a string
+        MyType.__module__ = 'builtins'
+        self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
+        MyType.__module__ = '__main__'
+        self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
+        MyType.__module__ = 123
+        self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
+
+
 
 @requires_limited_api
 class TestHeapTypeRelative(unittest.TestCase):
diff --git a/Lib/test/test_stable_abi_ctypes.py 
b/Lib/test/test_stable_abi_ctypes.py
index 8bd373976426ef..f0b449ac1708a1 100644
--- a/Lib/test/test_stable_abi_ctypes.py
+++ b/Lib/test/test_stable_abi_ctypes.py
@@ -706,6 +706,7 @@ def test_windows_feature_macros(self):
     "PyType_GenericAlloc",
     "PyType_GenericNew",
     "PyType_GetFlags",
+    "PyType_GetFullyQualifiedName",
     "PyType_GetModule",
     "PyType_GetModuleState",
     "PyType_GetName",
diff --git a/Misc/NEWS.d/next/C 
API/2024-03-14-15-17-11.gh-issue-111696.YmnvAi.rst b/Misc/NEWS.d/next/C 
API/2024-03-14-15-17-11.gh-issue-111696.YmnvAi.rst
new file mode 100644
index 00000000000000..3d87c56bf2493a
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2024-03-14-15-17-11.gh-issue-111696.YmnvAi.rst     
@@ -0,0 +1,4 @@
+Add :c:func:`PyType_GetFullyQualifiedName` function to get the type's fully
+qualified name. Equivalent to ``f"{type.__module__}.{type.__qualname__}"``, or
+``type.__qualname__`` if ``type.__module__`` is not a string or is equal to
+``"builtins"``. Patch by Victor Stinner.
diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml
index ca7cf02961571e..c76a3cea4da3f7 100644
--- a/Misc/stable_abi.toml
+++ b/Misc/stable_abi.toml
@@ -2496,3 +2496,5 @@
 [typedef.PyCFunctionFastWithKeywords]
     added = '3.13'
     # "abi-only" since 3.10.  (Same story as PyCFunctionFast.)
+[function.PyType_GetFullyQualifiedName]
+    added = '3.13'
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index b5e646f904b2d1..07f96466abdfc9 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -597,83 +597,31 @@ get_heaptype_for_name(PyObject *self, PyObject 
*Py_UNUSED(ignored))
     return PyType_FromSpec(&HeapTypeNameType_Spec);
 }
 
+
 static PyObject *
-test_get_type_name(PyObject *self, PyObject *Py_UNUSED(ignored))
+get_type_name(PyObject *self, PyObject *type)
 {
-    PyObject *tp_name = PyType_GetName(&PyLong_Type);
-    assert(strcmp(PyUnicode_AsUTF8(tp_name), "int") == 0);
-    Py_DECREF(tp_name);
-
-    tp_name = PyType_GetName(&PyModule_Type);
-    assert(strcmp(PyUnicode_AsUTF8(tp_name), "module") == 0);
-    Py_DECREF(tp_name);
-
-    PyObject *HeapTypeNameType = PyType_FromSpec(&HeapTypeNameType_Spec);
-    if (HeapTypeNameType == NULL) {
-        Py_RETURN_NONE;
-    }
-    tp_name = PyType_GetName((PyTypeObject *)HeapTypeNameType);
-    assert(strcmp(PyUnicode_AsUTF8(tp_name), "HeapTypeNameType") == 0);
-    Py_DECREF(tp_name);
-
-    PyObject *name = PyUnicode_FromString("test_name");
-    if (name == NULL) {
-        goto done;
-    }
-    if (PyObject_SetAttrString(HeapTypeNameType, "__name__", name) < 0) {
-        Py_DECREF(name);
-        goto done;
-    }
-    tp_name = PyType_GetName((PyTypeObject *)HeapTypeNameType);
-    assert(strcmp(PyUnicode_AsUTF8(tp_name), "test_name") == 0);
-    Py_DECREF(name);
-    Py_DECREF(tp_name);
-
-  done:
-    Py_DECREF(HeapTypeNameType);
-    Py_RETURN_NONE;
+    assert(PyType_Check(type));
+    return PyType_GetName((PyTypeObject *)type);
 }
 
 
 static PyObject *
-test_get_type_qualname(PyObject *self, PyObject *Py_UNUSED(ignored))
+get_type_qualname(PyObject *self, PyObject *type)
 {
-    PyObject *tp_qualname = PyType_GetQualName(&PyLong_Type);
-    assert(strcmp(PyUnicode_AsUTF8(tp_qualname), "int") == 0);
-    Py_DECREF(tp_qualname);
-
-    tp_qualname = PyType_GetQualName(&PyODict_Type);
-    assert(strcmp(PyUnicode_AsUTF8(tp_qualname), "OrderedDict") == 0);
-    Py_DECREF(tp_qualname);
-
-    PyObject *HeapTypeNameType = PyType_FromSpec(&HeapTypeNameType_Spec);
-    if (HeapTypeNameType == NULL) {
-        Py_RETURN_NONE;
-    }
-    tp_qualname = PyType_GetQualName((PyTypeObject *)HeapTypeNameType);
-    assert(strcmp(PyUnicode_AsUTF8(tp_qualname), "HeapTypeNameType") == 0);
-    Py_DECREF(tp_qualname);
+    assert(PyType_Check(type));
+    return PyType_GetQualName((PyTypeObject *)type);
+}
 
-    PyObject *spec_name = PyUnicode_FromString(HeapTypeNameType_Spec.name);
-    if (spec_name == NULL) {
-        goto done;
-    }
-    if (PyObject_SetAttrString(HeapTypeNameType,
-                               "__qualname__", spec_name) < 0) {
-        Py_DECREF(spec_name);
-        goto done;
-    }
-    tp_qualname = PyType_GetQualName((PyTypeObject *)HeapTypeNameType);
-    assert(strcmp(PyUnicode_AsUTF8(tp_qualname),
-                  "_testcapi.HeapTypeNameType") == 0);
-    Py_DECREF(spec_name);
-    Py_DECREF(tp_qualname);
 
-  done:
-    Py_DECREF(HeapTypeNameType);
-    Py_RETURN_NONE;
+static PyObject *
+get_type_fullyqualname(PyObject *self, PyObject *type)
+{
+    assert(PyType_Check(type));
+    return PyType_GetFullyQualifiedName((PyTypeObject *)type);
 }
 
+
 static PyObject *
 test_get_type_dict(PyObject *self, PyObject *Py_UNUSED(ignored))
 {
@@ -3317,8 +3265,9 @@ static PyMethodDef TestMethods[] = {
     {"test_buildvalue_N",        test_buildvalue_N,              METH_NOARGS},
     {"test_get_statictype_slots", test_get_statictype_slots,     METH_NOARGS},
     {"get_heaptype_for_name",     get_heaptype_for_name,         METH_NOARGS},
-    {"test_get_type_name",        test_get_type_name,            METH_NOARGS},
-    {"test_get_type_qualname",    test_get_type_qualname,        METH_NOARGS},
+    {"get_type_name",            get_type_name,                  METH_O},
+    {"get_type_qualname",        get_type_qualname,              METH_O},
+    {"get_type_fullyqualname",   get_type_fullyqualname,         METH_O},
     {"test_get_type_dict",        test_get_type_dict,            METH_NOARGS},
     {"_test_thread_state",      test_thread_state,               METH_VARARGS},
 #ifndef MS_WINDOWS
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index d8c3e920106bc3..e51adac7e9d636 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -1201,6 +1201,41 @@ type_set_module(PyTypeObject *type, PyObject *value, 
void *context)
     return PyDict_SetItem(dict, &_Py_ID(__module__), value);
 }
 
+
+PyObject *
+PyType_GetFullyQualifiedName(PyTypeObject *type)
+{
+    if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
+        return PyUnicode_FromString(type->tp_name);
+    }
+
+    PyObject *qualname = type_qualname(type, NULL);
+    if (qualname == NULL) {
+        return NULL;
+    }
+
+    PyObject *module = type_module(type, NULL);
+    if (module == NULL) {
+        Py_DECREF(qualname);
+        return NULL;
+    }
+
+    PyObject *result;
+    if (PyUnicode_Check(module)
+        && !_PyUnicode_Equal(module, &_Py_ID(builtins))
+        && !_PyUnicode_Equal(module, &_Py_ID(__main__)))
+    {
+        result = PyUnicode_FromFormat("%U.%U", module, qualname);
+    }
+    else {
+        result = Py_NewRef(qualname);
+    }
+    Py_DECREF(module);
+    Py_DECREF(qualname);
+    return result;
+}
+
+
 static PyObject *
 type_abstractmethods(PyTypeObject *type, void *context)
 {
@@ -1708,28 +1743,31 @@ type_repr(PyObject *self)
         return PyUnicode_FromFormat("<class at %p>", type);
     }
 
-    PyObject *mod, *name, *rtn;
-
-    mod = type_module(type, NULL);
-    if (mod == NULL)
+    PyObject *mod = type_module(type, NULL);
+    if (mod == NULL) {
         PyErr_Clear();
+    }
     else if (!PyUnicode_Check(mod)) {
-        Py_SETREF(mod, NULL);
+        Py_CLEAR(mod);
     }
-    name = type_qualname(type, NULL);
+
+    PyObject *name = type_qualname(type, NULL);
     if (name == NULL) {
         Py_XDECREF(mod);
         return NULL;
     }
 
-    if (mod != NULL && !_PyUnicode_Equal(mod, &_Py_ID(builtins)))
-        rtn = PyUnicode_FromFormat("<class '%U.%U'>", mod, name);
-    else
-        rtn = PyUnicode_FromFormat("<class '%s'>", type->tp_name);
-
+    PyObject *result;
+    if (mod != NULL && !_PyUnicode_Equal(mod, &_Py_ID(builtins))) {
+        result = PyUnicode_FromFormat("<class '%U.%U'>", mod, name);
+    }
+    else {
+        result = PyUnicode_FromFormat("<class '%s'>", type->tp_name);
+    }
     Py_XDECREF(mod);
     Py_DECREF(name);
-    return rtn;
+
+    return result;
 }
 
 static PyObject *
diff --git a/PC/python3dll.c b/PC/python3dll.c
index aa6bfe2c4022db..81d55af7074383 100755
--- a/PC/python3dll.c
+++ b/PC/python3dll.c
@@ -637,6 +637,7 @@ EXPORT_FUNC(PyType_FromSpecWithBases)
 EXPORT_FUNC(PyType_GenericAlloc)
 EXPORT_FUNC(PyType_GenericNew)
 EXPORT_FUNC(PyType_GetFlags)
+EXPORT_FUNC(PyType_GetFullyQualifiedName)
 EXPORT_FUNC(PyType_GetModule)
 EXPORT_FUNC(PyType_GetModuleState)
 EXPORT_FUNC(PyType_GetName)

_______________________________________________
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