]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-111696, PEP 737: Add %T and %N to PyUnicode_FromFormat() (#116839)
authorVictor Stinner <vstinner@python.org>
Thu, 14 Mar 2024 22:23:00 +0000 (23:23 +0100)
committerGitHub <noreply@github.com>
Thu, 14 Mar 2024 22:23:00 +0000 (22:23 +0000)
Doc/c-api/unicode.rst
Doc/whatsnew/3.13.rst
Include/internal/pycore_typeobject.h
Lib/test/test_capi/test_unicode.py
Misc/NEWS.d/next/C API/2024-03-14-22-30-07.gh-issue-111696.76UMKi.rst [new file with mode: 0644]
Objects/typeobject.c
Objects/unicodeobject.c

index 666ffe89605c56fa9a0356f88c76539a7fbff3f7..78eec14e3a24d650d2e807833c5fc89cda2c9c98 100644 (file)
@@ -518,6 +518,26 @@ APIs:
         - :c:expr:`PyObject*`
         - The result of calling :c:func:`PyObject_Repr`.
 
+      * - ``T``
+        - :c:expr:`PyObject*`
+        - Get the fully qualified name of an object type;
+          call :c:func:`PyType_GetFullyQualifiedName`.
+
+      * - ``T#``
+        - :c:expr:`PyObject*`
+        - Similar to ``T`` format, but use a colon (``:``) as separator between
+          the module name and the qualified name.
+
+      * - ``N``
+        - :c:expr:`PyTypeObject*`
+        - Get the fully qualified name of a type;
+          call :c:func:`PyType_GetFullyQualifiedName`.
+
+      * - ``N#``
+        - :c:expr:`PyTypeObject*`
+        - Similar to ``N`` format, but use a colon (``:``) as separator between
+          the module name and the qualified name.
+
    .. note::
       The width formatter unit is number of characters rather than bytes.
       The precision formatter unit is number of bytes or :c:type:`wchar_t`
@@ -553,6 +573,9 @@ APIs:
       In previous versions it caused all the rest of the format string to be
       copied as-is to the result string, and any extra arguments discarded.
 
+   .. versionchanged:: 3.13
+      Support for ``%T``, ``%T#``, ``%N`` and ``%N#`` formats added.
+
 
 .. c:function:: PyObject* PyUnicode_FromFormatV(const char *format, va_list vargs)
 
index f42197c001f18f65fafdd997ac5ac3573cefbf93..856c6ee1d6e3f06b717c5be8a998cb7f8369d31a 100644 (file)
@@ -1668,6 +1668,12 @@ New Features
   Equivalent to getting the ``type.__module__`` attribute.
   (Contributed by Eric Snow and Victor Stinner in :gh:`111696`.)
 
+* Add support for ``%T``, ``%T#``, ``%N`` and ``%N#`` formats to
+  :c:func:`PyUnicode_FromFormat`: format the fully qualified name of an object
+  type and of a type: call :c:func:`PyType_GetModuleName`. See :pep:`737` for
+  more information.
+  (Contributed by Victor Stinner in :gh:`111696`.)
+
 
 Porting to Python 3.13
 ----------------------
index 5c32d49e85c97b7a59d94053d013b05d269dd6d5..8a25935f308178fb34c6240806986d4777a3e92d 100644 (file)
@@ -150,6 +150,8 @@ extern PyTypeObject _PyBufferWrapper_Type;
 PyAPI_FUNC(PyObject*) _PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj,
                                  PyObject *name, int *meth_found);
 
+extern PyObject* _PyType_GetFullyQualifiedName(PyTypeObject *type, char sep);
+
 
 #ifdef __cplusplus
 }
index bb6161abf4da81db0b983fe4deb3aac56a0d6789..91c425e483f0fff65d8b06c1f8d2857830498468 100644 (file)
@@ -609,6 +609,40 @@ class CAPITest(unittest.TestCase):
         check_format('xyz',
                      b'%V', None, b'xyz')
 
+        # test %T
+        check_format('type: str',
+                     b'type: %T', py_object("abc"))
+        check_format(f'type: st',
+                     b'type: %.2T', py_object("abc"))
+        check_format(f'type:        str',
+                     b'type: %10T', py_object("abc"))
+
+        class LocalType:
+            pass
+        obj = LocalType()
+        fullname = f'{__name__}.{LocalType.__qualname__}'
+        check_format(f'type: {fullname}',
+                     b'type: %T', py_object(obj))
+        fullname_alt = f'{__name__}:{LocalType.__qualname__}'
+        check_format(f'type: {fullname_alt}',
+                     b'type: %T#', py_object(obj))
+
+        # test %N
+        check_format('type: str',
+                     b'type: %N', py_object(str))
+        check_format(f'type: st',
+                     b'type: %.2N', py_object(str))
+        check_format(f'type:        str',
+                     b'type: %10N', py_object(str))
+
+        check_format(f'type: {fullname}',
+                     b'type: %N', py_object(type(obj)))
+        check_format(f'type: {fullname_alt}',
+                     b'type: %N#', py_object(type(obj)))
+        with self.assertRaisesRegex(TypeError, "%N argument must be a type"):
+            check_format('type: str',
+                         b'type: %N', py_object("abc"))
+
         # test %ls
         check_format('abc', b'%ls', c_wchar_p('abc'))
         check_format('\u4eba\u6c11', b'%ls', c_wchar_p('\u4eba\u6c11'))
diff --git a/Misc/NEWS.d/next/C API/2024-03-14-22-30-07.gh-issue-111696.76UMKi.rst b/Misc/NEWS.d/next/C API/2024-03-14-22-30-07.gh-issue-111696.76UMKi.rst
new file mode 100644 (file)
index 0000000..44c15e4
--- /dev/null
@@ -0,0 +1,4 @@
+Add support for ``%T``, ``%T#``, ``%N`` and ``%N#`` formats to
+:c:func:`PyUnicode_FromFormat`: format the fully qualified name of an object
+type and of a type: call :c:func:`PyType_GetModuleName`. See :pep:`737` for
+more information. Patch by Victor Stinner.
index 1c5729c589da938ad4c8e7ef207cb13f154fab23..b73dfba37529a3ba0586ff31940b619e715a46d5 100644 (file)
@@ -1208,7 +1208,7 @@ type_set_module(PyTypeObject *type, PyObject *value, void *context)
 
 
 PyObject *
-PyType_GetFullyQualifiedName(PyTypeObject *type)
+_PyType_GetFullyQualifiedName(PyTypeObject *type, char sep)
 {
     if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
         return PyUnicode_FromString(type->tp_name);
@@ -1230,7 +1230,7 @@ PyType_GetFullyQualifiedName(PyTypeObject *type)
         && !_PyUnicode_Equal(module, &_Py_ID(builtins))
         && !_PyUnicode_Equal(module, &_Py_ID(__main__)))
     {
-        result = PyUnicode_FromFormat("%U.%U", module, qualname);
+        result = PyUnicode_FromFormat("%U%c%U", module, sep, qualname);
     }
     else {
         result = Py_NewRef(qualname);
@@ -1240,6 +1240,12 @@ PyType_GetFullyQualifiedName(PyTypeObject *type)
     return result;
 }
 
+PyObject *
+PyType_GetFullyQualifiedName(PyTypeObject *type)
+{
+    return _PyType_GetFullyQualifiedName(type, '.');
+}
+
 
 static PyObject *
 type_abstractmethods(PyTypeObject *type, void *context)
index 0a569a950e88e290b621bf9bef2db14556ea83a1..c8f647a7a71135bf3101506a458a237bc3ff837f 100644 (file)
@@ -2791,6 +2791,64 @@ unicode_fromformat_arg(_PyUnicodeWriter *writer,
         break;
     }
 
+    case 'T':
+    {
+        PyObject *obj = va_arg(*vargs, PyObject *);
+        PyTypeObject *type = (PyTypeObject *)Py_NewRef(Py_TYPE(obj));
+
+        PyObject *type_name;
+        if (f[1] == '#') {
+            type_name = _PyType_GetFullyQualifiedName(type, ':');
+            f++;
+        }
+        else {
+            type_name = PyType_GetFullyQualifiedName(type);
+        }
+        Py_DECREF(type);
+        if (!type_name) {
+            return NULL;
+        }
+
+        if (unicode_fromformat_write_str(writer, type_name,
+                                         width, precision, flags) == -1) {
+            Py_DECREF(type_name);
+            return NULL;
+        }
+        Py_DECREF(type_name);
+        break;
+    }
+
+    case 'N':
+    {
+        PyObject *type_raw = va_arg(*vargs, PyObject *);
+        assert(type_raw != NULL);
+
+        if (!PyType_Check(type_raw)) {
+            PyErr_SetString(PyExc_TypeError, "%N argument must be a type");
+            return NULL;
+        }
+        PyTypeObject *type = (PyTypeObject*)type_raw;
+
+        PyObject *type_name;
+        if (f[1] == '#') {
+            type_name = _PyType_GetFullyQualifiedName(type, ':');
+            f++;
+        }
+        else {
+            type_name = PyType_GetFullyQualifiedName(type);
+        }
+        if (!type_name) {
+            return NULL;
+        }
+        if (unicode_fromformat_write_str(writer, type_name,
+                                         width, precision, flags) == -1) {
+            Py_DECREF(type_name);
+            return NULL;
+        }
+        Py_DECREF(type_name);
+        break;
+    }
+
     default:
     invalid_format:
         PyErr_Format(PyExc_SystemError, "invalid format string: %s", p);