gh-129813, PEP 782: Add PyBytesWriter_Format() (#138824)

Modify PyBytes_FromFormatV() to use the public PyBytesWriter API
rather than the _PyBytesWriter private API.
This commit is contained in:
Victor Stinner 2025-09-12 14:21:57 +02:00 committed by GitHub
parent 419441a6e1
commit c3fca5d478
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 130 additions and 30 deletions

View file

@ -307,6 +307,15 @@ High-level API
On success, return ``0``. On success, return ``0``.
On error, set an exception and return ``-1``. 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 Getters
^^^^^^^ ^^^^^^^

View file

@ -714,6 +714,7 @@ New features
* :c:func:`PyBytesWriter_FinishWithPointer` * :c:func:`PyBytesWriter_FinishWithPointer`
* :c:func:`PyBytesWriter_FinishWithSize` * :c:func:`PyBytesWriter_FinishWithSize`
* :c:func:`PyBytesWriter_Finish` * :c:func:`PyBytesWriter_Finish`
* :c:func:`PyBytesWriter_Format`
* :c:func:`PyBytesWriter_GetData` * :c:func:`PyBytesWriter_GetData`
* :c:func:`PyBytesWriter_GetSize` * :c:func:`PyBytesWriter_GetSize`
* :c:func:`PyBytesWriter_GrowAndUpdatePointer` * :c:func:`PyBytesWriter_GrowAndUpdatePointer`

View file

@ -68,6 +68,10 @@ PyAPI_FUNC(int) PyBytesWriter_WriteBytes(
PyBytesWriter *writer, PyBytesWriter *writer,
const void *bytes, const void *bytes,
Py_ssize_t size); Py_ssize_t size);
PyAPI_FUNC(int) PyBytesWriter_Format(
PyBytesWriter *writer,
const char *format,
...);
PyAPI_FUNC(int) PyBytesWriter_Resize( PyAPI_FUNC(int) PyBytesWriter_Resize(
PyBytesWriter *writer, PyBytesWriter *writer,

View file

@ -361,12 +361,26 @@ def test_resize(self):
writer.resize(len(b'number=123456'), b'456') writer.resize(len(b'number=123456'), b'456')
self.assertEqual(writer.finish(), self.result_type(b'number=123456')) 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): def test_example_abc(self):
self.assertEqual(_testcapi.byteswriter_abc(), b'abc') self.assertEqual(_testcapi.byteswriter_abc(), b'abc')
def test_example_resize(self): def test_example_resize(self):
self.assertEqual(_testcapi.byteswriter_resize(), b'Hello World') self.assertEqual(_testcapi.byteswriter_resize(), b'Hello World')
def test_example_highlevel(self):
self.assertEqual(_testcapi.byteswriter_highlevel(), b'Hello World!')
class ByteArrayWriterTest(BytesWriterTest): class ByteArrayWriterTest(BytesWriterTest):
result_type = bytearray result_type = bytearray
@ -374,5 +388,6 @@ class ByteArrayWriterTest(BytesWriterTest):
def create_writer(self, alloc=0, string=b''): def create_writer(self, alloc=0, string=b''):
return _testcapi.PyBytesWriter(alloc, string, 1) return _testcapi.PyBytesWriter(alloc, string, 1)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -5,6 +5,7 @@ Implement :pep:`782`, the :c:type:`PyBytesWriter` API. Add functions:
* :c:func:`PyBytesWriter_FinishWithPointer` * :c:func:`PyBytesWriter_FinishWithPointer`
* :c:func:`PyBytesWriter_FinishWithSize` * :c:func:`PyBytesWriter_FinishWithSize`
* :c:func:`PyBytesWriter_Finish` * :c:func:`PyBytesWriter_Finish`
* :c:func:`PyBytesWriter_Format`
* :c:func:`PyBytesWriter_GetData` * :c:func:`PyBytesWriter_GetData`
* :c:func:`PyBytesWriter_GetSize` * :c:func:`PyBytesWriter_GetSize`
* :c:func:`PyBytesWriter_GrowAndUpdatePointer` * :c:func:`PyBytesWriter_GrowAndUpdatePointer`

View 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* static PyObject*
writer_resize(PyObject *self_raw, PyObject *args) writer_resize(PyObject *self_raw, PyObject *args)
{ {
@ -241,6 +262,7 @@ writer_finish_with_size(PyObject *self_raw, PyObject *args)
static PyMethodDef writer_methods[] = { static PyMethodDef writer_methods[] = {
{"write_bytes", _PyCFunction_CAST(writer_write_bytes), METH_VARARGS}, {"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}, {"resize", _PyCFunction_CAST(writer_resize), METH_VARARGS},
{"get_size", _PyCFunction_CAST(writer_get_size), METH_NOARGS}, {"get_size", _PyCFunction_CAST(writer_get_size), METH_NOARGS},
{"finish", _PyCFunction_CAST(writer_finish), 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[] = { static PyMethodDef test_methods[] = {
{"bytes_resize", bytes_resize, METH_VARARGS}, {"bytes_resize", bytes_resize, METH_VARARGS},
{"bytes_join", bytes_join, METH_VARARGS}, {"bytes_join", bytes_join, METH_VARARGS},
{"byteswriter_abc", byteswriter_abc, METH_NOARGS}, {"byteswriter_abc", byteswriter_abc, METH_NOARGS},
{"byteswriter_resize", byteswriter_resize, METH_NOARGS}, {"byteswriter_resize", byteswriter_resize, METH_NOARGS},
{"byteswriter_highlevel", byteswriter_highlevel, METH_NOARGS},
{NULL}, {NULL},
}; };

View file

@ -196,10 +196,11 @@ PyBytes_FromString(const char *str)
return (PyObject *) op; 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 *f;
const char *p; const char *p;
Py_ssize_t prec; Py_ssize_t prec;
@ -213,21 +214,20 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
Longest 64-bit pointer representation: Longest 64-bit pointer representation:
"0xffffffffffffffff\0" (19 bytes). */ "0xffffffffffffffff\0" (19 bytes). */
char buffer[21]; char buffer[21];
_PyBytesWriter writer;
_PyBytesWriter_Init(&writer); char *s = (char*)PyBytesWriter_GetData(writer) + writer_pos;
s = _PyBytesWriter_Alloc(&writer, strlen(format)); #define WRITE_BYTES_LEN(str, len_expr) \
if (s == NULL)
return NULL;
writer.overallocate = 1;
#define WRITE_BYTES(str) \
do { \ do { \
s = _PyBytesWriter_WriteBytes(&writer, s, (str), strlen(str)); \ size_t len = (len_expr); \
if (s == NULL) \ s = PyBytesWriter_GrowAndUpdatePointer(writer, len, s); \
if (s == NULL) { \
goto error; \ goto error; \
} \
memcpy(s, (str), len); \
s += len; \
} while (0) } while (0)
#define WRITE_BYTES(str) WRITE_BYTES_LEN(str, strlen(str))
for (f = format; *f; f++) { for (f = format; *f; f++) {
if (*f != '%') { if (*f != '%') {
@ -268,10 +268,6 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
++f; ++f;
} }
/* subtract bytes preallocated for the format string
(ex: 2 for "%s") */
writer.min_size -= (f - p + 1);
switch (*f) { switch (*f) {
case 'c': case 'c':
{ {
@ -282,7 +278,6 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
"expects an integer in range [0; 255]"); "expects an integer in range [0; 255]");
goto error; goto error;
} }
writer.min_size++;
*s++ = (unsigned char)c; *s++ = (unsigned char)c;
break; break;
} }
@ -341,9 +336,7 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
i++; i++;
} }
} }
s = _PyBytesWriter_WriteBytes(&writer, s, p, i); WRITE_BYTES_LEN(p, i);
if (s == NULL)
goto error;
break; break;
} }
@ -362,31 +355,45 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
break; break;
case '%': case '%':
writer.min_size++;
*s++ = '%'; *s++ = '%';
break; break;
default: 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 */ /* invalid format string: copy unformatted string and exit */
WRITE_BYTES(p); WRITE_BYTES(p);
return _PyBytesWriter_Finish(&writer, s); return s;
} }
} }
#undef WRITE_BYTES #undef WRITE_BYTES
#undef WRITE_BYTES_LEN
return _PyBytesWriter_Finish(&writer, s); return s;
error: error:
_PyBytesWriter_Dealloc(&writer);
return NULL; 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 * PyObject *
PyBytes_FromFormat(const char *format, ...) PyBytes_FromFormat(const char *format, ...)
{ {
@ -399,6 +406,7 @@ PyBytes_FromFormat(const char *format, ...)
return ret; return ret;
} }
/* Helpers for formatstring */ /* Helpers for formatstring */
Py_LOCAL_INLINE(PyObject *) Py_LOCAL_INLINE(PyObject *)
@ -4048,3 +4056,21 @@ PyBytesWriter_WriteBytes(PyBytesWriter *writer,
memcpy(buf + pos, bytes, size); memcpy(buf + pos, bytes, size);
return 0; 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);
}