]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-129813, PEP 782: Add PyBytesWriter_Format() (#138824)
authorVictor Stinner <vstinner@python.org>
Fri, 12 Sep 2025 12:21:57 +0000 (14:21 +0200)
committerGitHub <noreply@github.com>
Fri, 12 Sep 2025 12:21:57 +0000 (14:21 +0200)
Modify PyBytes_FromFormatV() to use the public PyBytesWriter API
rather than the _PyBytesWriter private API.

Doc/c-api/bytes.rst
Doc/whatsnew/3.15.rst
Include/cpython/bytesobject.h
Lib/test/test_capi/test_bytes.py
Misc/NEWS.d/next/C_API/2025-09-12-13-05-20.gh-issue-129813.dJZpME.rst
Modules/_testcapi/bytes.c
Objects/bytesobject.c

index 0a3cbaa62712f8e198a3411f067f45dfa1e82631..8cc935cd06659c80d0fe45ef9aff07a21ea2929c 100644 (file)
@@ -307,6 +307,15 @@ High-level API
    On success, return ``0``.
    On error, set an exception and return ``-1``.
 
+.. c:function:: int PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
+
+   Similar to :c:func:`PyBytes_FromFormat`, but write the output directly at
+   the writer end. Grow the writer internal buffer on demand. Then add the
+   written size to the writer size.
+
+   On success, return ``0``.
+   On error, set an exception and return ``-1``.
+
 
 Getters
 ^^^^^^^
index 33c6659ea01775d62edf0256bdee424e7b598156..1d029e3914baf5014dc2144c9508d983b2717f80 100644 (file)
@@ -714,6 +714,7 @@ New features
   * :c:func:`PyBytesWriter_FinishWithPointer`
   * :c:func:`PyBytesWriter_FinishWithSize`
   * :c:func:`PyBytesWriter_Finish`
+  * :c:func:`PyBytesWriter_Format`
   * :c:func:`PyBytesWriter_GetData`
   * :c:func:`PyBytesWriter_GetSize`
   * :c:func:`PyBytesWriter_GrowAndUpdatePointer`
index 23a652cedf32cdb9cba94901b81b205b5f9f45cd..85bc2b827df8fb0bf5fdf18adc8c1120d7d40661 100644 (file)
@@ -68,6 +68,10 @@ PyAPI_FUNC(int) PyBytesWriter_WriteBytes(
     PyBytesWriter *writer,
     const void *bytes,
     Py_ssize_t size);
+PyAPI_FUNC(int) PyBytesWriter_Format(
+    PyBytesWriter *writer,
+    const char *format,
+    ...);
 
 PyAPI_FUNC(int) PyBytesWriter_Resize(
     PyBytesWriter *writer,
index cc6c932c7d907224bd2e4c690d3e117220162bb4..410ebab729c2cf410c50c02cb8700a22ee3f2e4e 100644 (file)
@@ -361,12 +361,26 @@ class BytesWriterTest(unittest.TestCase):
         writer.resize(len(b'number=123456'), b'456')
         self.assertEqual(writer.finish(), self.result_type(b'number=123456'))
 
+    def test_format_i(self):
+        # Test PyBytesWriter_Format()
+        writer = self.create_writer()
+        writer.format_i(b'x=%i', 123456)
+        self.assertEqual(writer.finish(), self.result_type(b'x=123456'))
+
+        writer = self.create_writer()
+        writer.format_i(b'x=%i, ', 123)
+        writer.format_i(b'y=%i', 456)
+        self.assertEqual(writer.finish(), self.result_type(b'x=123, y=456'))
+
     def test_example_abc(self):
         self.assertEqual(_testcapi.byteswriter_abc(), b'abc')
 
     def test_example_resize(self):
         self.assertEqual(_testcapi.byteswriter_resize(), b'Hello World')
 
+    def test_example_highlevel(self):
+        self.assertEqual(_testcapi.byteswriter_highlevel(), b'Hello World!')
+
 
 class ByteArrayWriterTest(BytesWriterTest):
     result_type = bytearray
@@ -374,5 +388,6 @@ class ByteArrayWriterTest(BytesWriterTest):
     def create_writer(self, alloc=0, string=b''):
         return _testcapi.PyBytesWriter(alloc, string, 1)
 
+
 if __name__ == "__main__":
     unittest.main()
index 30d07279918045fdc493cbe2c9d7281cf28301bd..e4abfb6f6ed4102da23f1e8613df08a57079859d 100644 (file)
@@ -5,6 +5,7 @@ Implement :pep:`782`, the :c:type:`PyBytesWriter` API. Add functions:
 * :c:func:`PyBytesWriter_FinishWithPointer`
 * :c:func:`PyBytesWriter_FinishWithSize`
 * :c:func:`PyBytesWriter_Finish`
+* :c:func:`PyBytesWriter_Format`
 * :c:func:`PyBytesWriter_GetData`
 * :c:func:`PyBytesWriter_GetSize`
 * :c:func:`PyBytesWriter_GrowAndUpdatePointer`
index 3530b6a4a42b44b8691e1e67b5357fa90efb36a4..388e65456c3a8b7f15ca94906e3499e4c3af380e 100644 (file)
@@ -163,6 +163,27 @@ writer_write_bytes(PyObject *self_raw, PyObject *args)
 }
 
 
+static PyObject*
+writer_format_i(PyObject *self_raw, PyObject *args)
+{
+    WriterObject *self = (WriterObject *)self_raw;
+    if (writer_check(self) < 0) {
+        return NULL;
+    }
+
+    char *format;
+    int value;
+    if (!PyArg_ParseTuple(args, "yi", &format, &value)) {
+        return NULL;
+    }
+
+    if (PyBytesWriter_Format(self->writer, format, value) < 0) {
+        return NULL;
+    }
+    Py_RETURN_NONE;
+}
+
+
 static PyObject*
 writer_resize(PyObject *self_raw, PyObject *args)
 {
@@ -241,6 +262,7 @@ writer_finish_with_size(PyObject *self_raw, PyObject *args)
 
 static PyMethodDef writer_methods[] = {
     {"write_bytes", _PyCFunction_CAST(writer_write_bytes), METH_VARARGS},
+    {"format_i", _PyCFunction_CAST(writer_format_i), METH_VARARGS},
     {"resize", _PyCFunction_CAST(writer_resize), METH_VARARGS},
     {"get_size", _PyCFunction_CAST(writer_get_size), METH_NOARGS},
     {"finish", _PyCFunction_CAST(writer_finish), METH_NOARGS},
@@ -309,11 +331,33 @@ byteswriter_resize(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
 }
 
 
+static PyObject *
+byteswriter_highlevel(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+    PyBytesWriter *writer = PyBytesWriter_Create(0);
+    if (writer == NULL) {
+        goto error;
+    }
+    if (PyBytesWriter_WriteBytes(writer, "Hello", -1) < 0) {
+        goto error;
+    }
+    if (PyBytesWriter_Format(writer, " %s!", "World") < 0) {
+        goto error;
+    }
+    return PyBytesWriter_Finish(writer);
+
+error:
+    PyBytesWriter_Discard(writer);
+    return NULL;
+}
+
+
 static PyMethodDef test_methods[] = {
     {"bytes_resize", bytes_resize, METH_VARARGS},
     {"bytes_join", bytes_join, METH_VARARGS},
     {"byteswriter_abc", byteswriter_abc, METH_NOARGS},
     {"byteswriter_resize", byteswriter_resize, METH_NOARGS},
+    {"byteswriter_highlevel", byteswriter_highlevel, METH_NOARGS},
     {NULL},
 };
 
index aa099af8cf17f39c437db4e33fdd69aa1240f2d7..fc9e1bef80f03783e6a199a63678f78e2d36cc20 100644 (file)
@@ -196,10 +196,11 @@ PyBytes_FromString(const char *str)
     return (PyObject *) op;
 }
 
-PyObject *
-PyBytes_FromFormatV(const char *format, va_list vargs)
+
+static char*
+bytes_fromformat(PyBytesWriter *writer, Py_ssize_t writer_pos,
+                 const char *format, va_list vargs)
 {
-    char *s;
     const char *f;
     const char *p;
     Py_ssize_t prec;
@@ -213,21 +214,20 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
        Longest 64-bit pointer representation:
        "0xffffffffffffffff\0" (19 bytes). */
     char buffer[21];
-    _PyBytesWriter writer;
 
-    _PyBytesWriter_Init(&writer);
+    char *s = (char*)PyBytesWriter_GetData(writer) + writer_pos;
 
-    s = _PyBytesWriter_Alloc(&writer, strlen(format));
-    if (s == NULL)
-        return NULL;
-    writer.overallocate = 1;
-
-#define WRITE_BYTES(str) \
+#define WRITE_BYTES_LEN(str, len_expr) \
     do { \
-        s = _PyBytesWriter_WriteBytes(&writer, s, (str), strlen(str)); \
-        if (s == NULL) \
+        size_t len = (len_expr); \
+        s = PyBytesWriter_GrowAndUpdatePointer(writer, len, s); \
+        if (s == NULL) { \
             goto error; \
+        } \
+        memcpy(s, (str), len); \
+        s += len; \
     } while (0)
+#define WRITE_BYTES(str) WRITE_BYTES_LEN(str, strlen(str))
 
     for (f = format; *f; f++) {
         if (*f != '%') {
@@ -268,10 +268,6 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
             ++f;
         }
 
-        /* subtract bytes preallocated for the format string
-           (ex: 2 for "%s") */
-        writer.min_size -= (f - p + 1);
-
         switch (*f) {
         case 'c':
         {
@@ -282,7 +278,6 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
                                 "expects an integer in range [0; 255]");
                 goto error;
             }
-            writer.min_size++;
             *s++ = (unsigned char)c;
             break;
         }
@@ -341,9 +336,7 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
                     i++;
                 }
             }
-            s = _PyBytesWriter_WriteBytes(&writer, s, p, i);
-            if (s == NULL)
-                goto error;
+            WRITE_BYTES_LEN(p, i);
             break;
         }
 
@@ -362,31 +355,45 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
             break;
 
         case '%':
-            writer.min_size++;
             *s++ = '%';
             break;
 
         default:
-            if (*f == 0) {
-                /* fix min_size if we reached the end of the format string */
-                writer.min_size++;
-            }
-
             /* invalid format string: copy unformatted string and exit */
             WRITE_BYTES(p);
-            return _PyBytesWriter_Finish(&writer, s);
+            return s;
         }
     }
 
 #undef WRITE_BYTES
+#undef WRITE_BYTES_LEN
 
-    return _PyBytesWriter_Finish(&writer, s);
+    return s;
 
  error:
-    _PyBytesWriter_Dealloc(&writer);
     return NULL;
 }
 
+
+PyObject *
+PyBytes_FromFormatV(const char *format, va_list vargs)
+{
+    Py_ssize_t alloc = strlen(format);
+    PyBytesWriter *writer = PyBytesWriter_Create(alloc);
+    if (writer == NULL) {
+        return NULL;
+    }
+
+    char *s = bytes_fromformat(writer, 0, format, vargs);
+    if (s == NULL) {
+        PyBytesWriter_Discard(writer);
+        return NULL;
+    }
+
+    return PyBytesWriter_FinishWithPointer(writer, s);
+}
+
+
 PyObject *
 PyBytes_FromFormat(const char *format, ...)
 {
@@ -399,6 +406,7 @@ PyBytes_FromFormat(const char *format, ...)
     return ret;
 }
 
+
 /* Helpers for formatstring */
 
 Py_LOCAL_INLINE(PyObject *)
@@ -4048,3 +4056,21 @@ PyBytesWriter_WriteBytes(PyBytesWriter *writer,
     memcpy(buf + pos, bytes, size);
     return 0;
 }
+
+
+int
+PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
+{
+    Py_ssize_t pos = writer->size;
+    if (PyBytesWriter_Grow(writer, strlen(format)) < 0) {
+        return -1;
+    }
+
+    va_list vargs;
+    va_start(vargs, format);
+    char *buf = bytes_fromformat(writer, pos, format, vargs);
+    va_end(vargs);
+
+    Py_ssize_t size = buf - byteswriter_data(writer);
+    return PyBytesWriter_Resize(writer, size);
+}