Skip to content

Commit e20679a

Browse files
committed
gh-111696, PEP 737: Add PyType_GetFullyQualifiedName() function
Rewrite tests on type names in Python, they were written in C.
1 parent a76288a commit e20679a

File tree

11 files changed

+169
-102
lines changed

11 files changed

+169
-102
lines changed

Doc/c-api/type.rst

+8
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,14 @@ Type Objects
185185
186186
.. versionadded:: 3.11
187187
188+
.. c:function:: PyObject* PyType_GetFullyQualifiedName(PyTypeObject *type)
189+
190+
Return the type's fully qualified name. Equivalent to
191+
``f"{type.__module__}.{type.__qualname__}"``, or ``type.__qualname__`` if
192+
``type.__module__`` is not a string or is equal to ``"builtins"``.
193+
194+
.. versionadded:: 3.13
195+
188196
.. c:function:: void* PyType_GetSlot(PyTypeObject *type, int slot)
189197
190198
Return the function pointer stored in the given slot. If the

Doc/data/stable_abi.dat

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/whatsnew/3.13.rst

+6
Original file line numberDiff line numberDiff line change
@@ -1649,6 +1649,12 @@ New Features
16491649
between native integer types and Python :class:`int` objects.
16501650
(Contributed by Steve Dower in :gh:`111140`.)
16511651

1652+
* Add :c:func:`PyType_GetFullyQualifiedName` function to get the type's fully
1653+
qualified name. Equivalent to ``f"{type.__module__}.{type.__qualname__}"``,
1654+
or ``type.__qualname__`` if ``type.__module__`` is not a string or is equal
1655+
to ``"builtins"``.
1656+
(Contributed by Victor Stinner in :gh:`111696`.)
1657+
16521658

16531659
Porting to Python 3.13
16541660
----------------------

Include/object.h

+3
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,9 @@ PyAPI_FUNC(void *) PyType_GetModuleState(PyTypeObject *);
522522
PyAPI_FUNC(PyObject *) PyType_GetName(PyTypeObject *);
523523
PyAPI_FUNC(PyObject *) PyType_GetQualName(PyTypeObject *);
524524
#endif
525+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030D0000
526+
PyAPI_FUNC(PyObject *) PyType_GetFullyQualifiedName(PyTypeObject *);
527+
#endif
525528
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030C0000
526529
PyAPI_FUNC(PyObject *) PyType_FromMetaclass(PyTypeObject*, PyObject*, PyType_Spec*, PyObject*);
527530
PyAPI_FUNC(void *) PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls);

Lib/test/test_capi/test_misc.py

+65-11
Original file line numberDiff line numberDiff line change
@@ -1100,21 +1100,75 @@ class Data(_testcapi.ObjExtraData):
11001100
del d.extra
11011101
self.assertIsNone(d.extra)
11021102

1103-
def test_get_type_module_name(self):
1103+
def test_get_type_name(self):
1104+
class MyType:
1105+
pass
1106+
1107+
from _testcapi import get_type_name, get_type_qualname, get_type_fullyqualname
1108+
from _testinternalcapi import get_type_module_name
1109+
11041110
from collections import OrderedDict
11051111
ht = _testcapi.get_heaptype_for_name()
1106-
for cls, expected in {
1107-
int: 'builtins',
1108-
OrderedDict: 'collections',
1109-
ht: '_testcapi',
1110-
}.items():
1111-
with self.subTest(repr(cls)):
1112-
modname = _testinternalcapi.get_type_module_name(cls)
1113-
self.assertEqual(modname, expected)
1112+
for cls, fullname, modname, qualname, name in (
1113+
(int,
1114+
'int',
1115+
'builtins',
1116+
'int',
1117+
'int'),
1118+
(OrderedDict,
1119+
'collections.OrderedDict',
1120+
'collections',
1121+
'OrderedDict',
1122+
'OrderedDict'),
1123+
(ht,
1124+
'_testcapi.HeapTypeNameType',
1125+
'_testcapi',
1126+
'HeapTypeNameType',
1127+
'HeapTypeNameType'),
1128+
(MyType,
1129+
f'{__name__}.CAPITest.test_get_type_name.<locals>.MyType',
1130+
__name__,
1131+
'CAPITest.test_get_type_name.<locals>.MyType',
1132+
'MyType'),
1133+
):
1134+
with self.subTest(cls=repr(cls)):
1135+
self.assertEqual(get_type_fullyqualname(cls), fullname)
1136+
self.assertEqual(get_type_module_name(cls), modname)
1137+
self.assertEqual(get_type_qualname(cls), qualname)
1138+
self.assertEqual(get_type_name(cls), name)
11141139

1140+
# override __module__
11151141
ht.__module__ = 'test_module'
1116-
modname = _testinternalcapi.get_type_module_name(ht)
1117-
self.assertEqual(modname, 'test_module')
1142+
self.assertEqual(get_type_fullyqualname(ht), 'test_module.HeapTypeNameType')
1143+
self.assertEqual(get_type_module_name(ht), 'test_module')
1144+
self.assertEqual(get_type_qualname(ht), 'HeapTypeNameType')
1145+
self.assertEqual(get_type_name(ht), 'HeapTypeNameType')
1146+
1147+
# override __name__ and __qualname__
1148+
MyType.__name__ = 'my_name'
1149+
MyType.__qualname__ = 'my_qualname'
1150+
self.assertEqual(get_type_fullyqualname(MyType), f'{__name__}.my_qualname')
1151+
self.assertEqual(get_type_module_name(MyType), __name__)
1152+
self.assertEqual(get_type_qualname(MyType), 'my_qualname')
1153+
self.assertEqual(get_type_name(MyType), 'my_name')
1154+
1155+
# override also __module__
1156+
MyType.__module__ = 'my_module'
1157+
self.assertEqual(get_type_fullyqualname(MyType), 'my_module.my_qualname')
1158+
self.assertEqual(get_type_module_name(MyType), 'my_module')
1159+
self.assertEqual(get_type_qualname(MyType), 'my_qualname')
1160+
self.assertEqual(get_type_name(MyType), 'my_name')
1161+
1162+
# PyType_GetFullyQualifiedName() ignores the module if it's "builtins"
1163+
# or "__main__" of it is not a string
1164+
MyType.__module__ = 'builtins'
1165+
self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
1166+
MyType.__module__ = '__main__'
1167+
self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
1168+
MyType.__module__ = 123
1169+
self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
1170+
1171+
11181172

11191173
@requires_limited_api
11201174
class TestHeapTypeRelative(unittest.TestCase):

Lib/test/test_stable_abi_ctypes.py

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add :c:func:`PyType_GetFullyQualifiedName` function to get the type's fully
2+
qualified name. Equivalent to ``f"{type.__module__}.{type.__qualname__}"``, or
3+
``type.__qualname__`` if ``type.__module__`` is not a string or is equal to
4+
``"builtins"``. Patch by Victor Stinner.

Misc/stable_abi.toml

+2
Original file line numberDiff line numberDiff line change
@@ -2496,3 +2496,5 @@
24962496
[typedef.PyCFunctionFastWithKeywords]
24972497
added = '3.13'
24982498
# "abi-only" since 3.10. (Same story as PyCFunctionFast.)
2499+
[function.PyType_GetFullyQualifiedName]
2500+
added = '3.13'

Modules/_testcapimodule.c

+17-68
Original file line numberDiff line numberDiff line change
@@ -597,83 +597,31 @@ get_heaptype_for_name(PyObject *self, PyObject *Py_UNUSED(ignored))
597597
return PyType_FromSpec(&HeapTypeNameType_Spec);
598598
}
599599

600+
600601
static PyObject *
601-
test_get_type_name(PyObject *self, PyObject *Py_UNUSED(ignored))
602+
get_type_name(PyObject *self, PyObject *type)
602603
{
603-
PyObject *tp_name = PyType_GetName(&PyLong_Type);
604-
assert(strcmp(PyUnicode_AsUTF8(tp_name), "int") == 0);
605-
Py_DECREF(tp_name);
606-
607-
tp_name = PyType_GetName(&PyModule_Type);
608-
assert(strcmp(PyUnicode_AsUTF8(tp_name), "module") == 0);
609-
Py_DECREF(tp_name);
610-
611-
PyObject *HeapTypeNameType = PyType_FromSpec(&HeapTypeNameType_Spec);
612-
if (HeapTypeNameType == NULL) {
613-
Py_RETURN_NONE;
614-
}
615-
tp_name = PyType_GetName((PyTypeObject *)HeapTypeNameType);
616-
assert(strcmp(PyUnicode_AsUTF8(tp_name), "HeapTypeNameType") == 0);
617-
Py_DECREF(tp_name);
618-
619-
PyObject *name = PyUnicode_FromString("test_name");
620-
if (name == NULL) {
621-
goto done;
622-
}
623-
if (PyObject_SetAttrString(HeapTypeNameType, "__name__", name) < 0) {
624-
Py_DECREF(name);
625-
goto done;
626-
}
627-
tp_name = PyType_GetName((PyTypeObject *)HeapTypeNameType);
628-
assert(strcmp(PyUnicode_AsUTF8(tp_name), "test_name") == 0);
629-
Py_DECREF(name);
630-
Py_DECREF(tp_name);
631-
632-
done:
633-
Py_DECREF(HeapTypeNameType);
634-
Py_RETURN_NONE;
604+
assert(PyType_Check(type));
605+
return PyType_GetName((PyTypeObject *)type);
635606
}
636607

637608

638609
static PyObject *
639-
test_get_type_qualname(PyObject *self, PyObject *Py_UNUSED(ignored))
610+
get_type_qualname(PyObject *self, PyObject *type)
640611
{
641-
PyObject *tp_qualname = PyType_GetQualName(&PyLong_Type);
642-
assert(strcmp(PyUnicode_AsUTF8(tp_qualname), "int") == 0);
643-
Py_DECREF(tp_qualname);
644-
645-
tp_qualname = PyType_GetQualName(&PyODict_Type);
646-
assert(strcmp(PyUnicode_AsUTF8(tp_qualname), "OrderedDict") == 0);
647-
Py_DECREF(tp_qualname);
648-
649-
PyObject *HeapTypeNameType = PyType_FromSpec(&HeapTypeNameType_Spec);
650-
if (HeapTypeNameType == NULL) {
651-
Py_RETURN_NONE;
652-
}
653-
tp_qualname = PyType_GetQualName((PyTypeObject *)HeapTypeNameType);
654-
assert(strcmp(PyUnicode_AsUTF8(tp_qualname), "HeapTypeNameType") == 0);
655-
Py_DECREF(tp_qualname);
612+
assert(PyType_Check(type));
613+
return PyType_GetQualName((PyTypeObject *)type);
614+
}
656615

657-
PyObject *spec_name = PyUnicode_FromString(HeapTypeNameType_Spec.name);
658-
if (spec_name == NULL) {
659-
goto done;
660-
}
661-
if (PyObject_SetAttrString(HeapTypeNameType,
662-
"__qualname__", spec_name) < 0) {
663-
Py_DECREF(spec_name);
664-
goto done;
665-
}
666-
tp_qualname = PyType_GetQualName((PyTypeObject *)HeapTypeNameType);
667-
assert(strcmp(PyUnicode_AsUTF8(tp_qualname),
668-
"_testcapi.HeapTypeNameType") == 0);
669-
Py_DECREF(spec_name);
670-
Py_DECREF(tp_qualname);
671616

672-
done:
673-
Py_DECREF(HeapTypeNameType);
674-
Py_RETURN_NONE;
617+
static PyObject *
618+
get_type_fullyqualname(PyObject *self, PyObject *type)
619+
{
620+
assert(PyType_Check(type));
621+
return PyType_GetFullyQualifiedName((PyTypeObject *)type);
675622
}
676623

624+
677625
static PyObject *
678626
test_get_type_dict(PyObject *self, PyObject *Py_UNUSED(ignored))
679627
{
@@ -3317,8 +3265,9 @@ static PyMethodDef TestMethods[] = {
33173265
{"test_buildvalue_N", test_buildvalue_N, METH_NOARGS},
33183266
{"test_get_statictype_slots", test_get_statictype_slots, METH_NOARGS},
33193267
{"get_heaptype_for_name", get_heaptype_for_name, METH_NOARGS},
3320-
{"test_get_type_name", test_get_type_name, METH_NOARGS},
3321-
{"test_get_type_qualname", test_get_type_qualname, METH_NOARGS},
3268+
{"get_type_name", get_type_name, METH_O},
3269+
{"get_type_qualname", get_type_qualname, METH_O},
3270+
{"get_type_fullyqualname", get_type_fullyqualname, METH_O},
33223271
{"test_get_type_dict", test_get_type_dict, METH_NOARGS},
33233272
{"_test_thread_state", test_thread_state, METH_VARARGS},
33243273
#ifndef MS_WINDOWS

Objects/typeobject.c

+61-23
Original file line numberDiff line numberDiff line change
@@ -1201,6 +1201,60 @@ type_set_module(PyTypeObject *type, PyObject *value, void *context)
12011201
return PyDict_SetItem(dict, &_Py_ID(__module__), value);
12021202
}
12031203

1204+
1205+
static PyObject*
1206+
type_fullyqualname(PyTypeObject *type, int is_repr)
1207+
{
1208+
// type is a static type and PyType_Ready() was not called on it yet?
1209+
if (type->tp_name == NULL) {
1210+
PyErr_SetString(PyExc_TypeError, "static type not initialized");
1211+
return NULL;
1212+
}
1213+
1214+
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
1215+
return PyUnicode_FromString(type->tp_name);
1216+
}
1217+
1218+
PyObject *qualname = type_qualname(type, NULL);
1219+
if (qualname == NULL) {
1220+
return NULL;
1221+
}
1222+
1223+
PyObject *module = type_module(type, NULL);
1224+
if (module == NULL) {
1225+
if (is_repr) {
1226+
// type_repr() ignores type_module() errors
1227+
PyErr_Clear();
1228+
return qualname;
1229+
}
1230+
1231+
Py_DECREF(qualname);
1232+
return NULL;
1233+
}
1234+
1235+
PyObject *result;
1236+
if (PyUnicode_Check(module)
1237+
&& !_PyUnicode_Equal(module, &_Py_ID(builtins))
1238+
&& !_PyUnicode_Equal(module, &_Py_ID(__main__)))
1239+
{
1240+
result = PyUnicode_FromFormat("%U.%U", module, qualname);
1241+
}
1242+
else {
1243+
result = Py_NewRef(qualname);
1244+
}
1245+
Py_DECREF(module);
1246+
Py_DECREF(qualname);
1247+
return result;
1248+
}
1249+
1250+
1251+
PyObject *
1252+
PyType_GetFullyQualifiedName(PyTypeObject *type)
1253+
{
1254+
return type_fullyqualname(type, 0);
1255+
}
1256+
1257+
12041258
static PyObject *
12051259
type_abstractmethods(PyTypeObject *type, void *context)
12061260
{
@@ -1699,37 +1753,21 @@ static PyGetSetDef type_getsets[] = {
16991753
};
17001754

17011755
static PyObject *
1702-
type_repr(PyObject *self)
1756+
type_repr(PyTypeObject *type)
17031757
{
1704-
PyTypeObject *type = (PyTypeObject *)self;
17051758
if (type->tp_name == NULL) {
1706-
// type_repr() called before the type is fully initialized
1707-
// by PyType_Ready().
1759+
// If type_repr() is called before the type is fully initialized
1760+
// by PyType_Ready(), just format the type memory address.
17081761
return PyUnicode_FromFormat("<class at %p>", type);
17091762
}
17101763

1711-
PyObject *mod, *name, *rtn;
1712-
1713-
mod = type_module(type, NULL);
1714-
if (mod == NULL)
1715-
PyErr_Clear();
1716-
else if (!PyUnicode_Check(mod)) {
1717-
Py_SETREF(mod, NULL);
1718-
}
1719-
name = type_qualname(type, NULL);
1764+
PyObject *name = type_fullyqualname(type, 1);
17201765
if (name == NULL) {
1721-
Py_XDECREF(mod);
17221766
return NULL;
17231767
}
1724-
1725-
if (mod != NULL && !_PyUnicode_Equal(mod, &_Py_ID(builtins)))
1726-
rtn = PyUnicode_FromFormat("<class '%U.%U'>", mod, name);
1727-
else
1728-
rtn = PyUnicode_FromFormat("<class '%s'>", type->tp_name);
1729-
1730-
Py_XDECREF(mod);
1768+
PyObject *result = PyUnicode_FromFormat("<class '%U'>", name);
17311769
Py_DECREF(name);
1732-
return rtn;
1770+
return result;
17331771
}
17341772

17351773
static PyObject *
@@ -5617,7 +5655,7 @@ PyTypeObject PyType_Type = {
56175655
0, /* tp_getattr */
56185656
0, /* tp_setattr */
56195657
0, /* tp_as_async */
5620-
type_repr, /* tp_repr */
5658+
(reprfunc)type_repr, /* tp_repr */
56215659
&type_as_number, /* tp_as_number */
56225660
0, /* tp_as_sequence */
56235661
0, /* tp_as_mapping */

PC/python3dll.c

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)