summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/type.rst8
-rw-r--r--Doc/data/stable_abi.dat1
-rw-r--r--Doc/whatsnew/3.13.rst6
-rw-r--r--Include/object.h3
-rw-r--r--Lib/test/test_capi/test_misc.py76
-rw-r--r--Lib/test/test_stable_abi_ctypes.py1
-rw-r--r--Misc/NEWS.d/next/C API/2024-03-14-15-17-11.gh-issue-111696.YmnvAi.rst4
-rw-r--r--Misc/stable_abi.toml2
-rw-r--r--Modules/_testcapimodule.c85
-rw-r--r--Objects/typeobject.c62
-rwxr-xr-xPC/python3dll.c1
11 files changed, 158 insertions, 91 deletions
diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst
index 5aaa814..c523423 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 25629b4..03fe3ce 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 ea45fa7..cbb5e02 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 05187fe..3f6f1ab 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 c1395ab..6b4f535 100644
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -1100,21 +1100,75 @@ class CAPITest(unittest.TestCase):
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 8bd3739..f0b449a 100644
--- a/Lib/test/test_stable_abi_ctypes.py
+++ b/Lib/test/test_stable_abi_ctypes.py
@@ -706,6 +706,7 @@ SYMBOL_NAMES = (
"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 0000000..3d87c56
--- /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 ca7cf02..c76a3ce 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 b5e646f..07f9646 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 d8c3e92..e51adac 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 aa6bfe2..81d55af 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)