Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Doc/c-api/type.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1649,6 +1649,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
----------------------
Expand Down
3 changes: 3 additions & 0 deletions Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
76 changes: 65 additions & 11 deletions Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2496,3 +2496,5 @@
[typedef.PyCFunctionFastWithKeywords]
added = '3.13'
# "abi-only" since 3.10. (Same story as PyCFunctionFast.)
[function.PyType_GetFullyQualifiedName]
added = '3.13'
85 changes: 17 additions & 68 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down Expand Up @@ -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
Expand Down
62 changes: 50 additions & 12 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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 *
Expand Down
1 change: 1 addition & 0 deletions PC/python3dll.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.