Skip to content

Commit c3fca5d

Browse files
authored
gh-129813, PEP 782: Add PyBytesWriter_Format() (#138824)
Modify PyBytes_FromFormatV() to use the public PyBytesWriter API rather than the _PyBytesWriter private API.
1 parent 419441a commit c3fca5d

File tree

7 files changed

+130
-30
lines changed

7 files changed

+130
-30
lines changed

Doc/c-api/bytes.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,15 @@ High-level API
307307
On success, return ``0``.
308308
On error, set an exception and return ``-1``.
309309
310+
.. c:function:: int PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
311+
312+
Similar to :c:func:`PyBytes_FromFormat`, but write the output directly at
313+
the writer end. Grow the writer internal buffer on demand. Then add the
314+
written size to the writer size.
315+
316+
On success, return ``0``.
317+
On error, set an exception and return ``-1``.
318+
310319
311320
Getters
312321
^^^^^^^

Doc/whatsnew/3.15.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,7 @@ New features
714714
* :c:func:`PyBytesWriter_FinishWithPointer`
715715
* :c:func:`PyBytesWriter_FinishWithSize`
716716
* :c:func:`PyBytesWriter_Finish`
717+
* :c:func:`PyBytesWriter_Format`
717718
* :c:func:`PyBytesWriter_GetData`
718719
* :c:func:`PyBytesWriter_GetSize`
719720
* :c:func:`PyBytesWriter_GrowAndUpdatePointer`

Include/cpython/bytesobject.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ PyAPI_FUNC(int) PyBytesWriter_WriteBytes(
6868
PyBytesWriter *writer,
6969
const void *bytes,
7070
Py_ssize_t size);
71+
PyAPI_FUNC(int) PyBytesWriter_Format(
72+
PyBytesWriter *writer,
73+
const char *format,
74+
...);
7175

7276
PyAPI_FUNC(int) PyBytesWriter_Resize(
7377
PyBytesWriter *writer,

Lib/test/test_capi/test_bytes.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,18 +361,33 @@ def test_resize(self):
361361
writer.resize(len(b'number=123456'), b'456')
362362
self.assertEqual(writer.finish(), self.result_type(b'number=123456'))
363363

364+
def test_format_i(self):
365+
# Test PyBytesWriter_Format()
366+
writer = self.create_writer()
367+
writer.format_i(b'x=%i', 123456)
368+
self.assertEqual(writer.finish(), self.result_type(b'x=123456'))
369+
370+
writer = self.create_writer()
371+
writer.format_i(b'x=%i, ', 123)
372+
writer.format_i(b'y=%i', 456)
373+
self.assertEqual(writer.finish(), self.result_type(b'x=123, y=456'))
374+
364375
def test_example_abc(self):
365376
self.assertEqual(_testcapi.byteswriter_abc(), b'abc')
366377

367378
def test_example_resize(self):
368379
self.assertEqual(_testcapi.byteswriter_resize(), b'Hello World')
369380

381+
def test_example_highlevel(self):
382+
self.assertEqual(_testcapi.byteswriter_highlevel(), b'Hello World!')
383+
370384

371385
class ByteArrayWriterTest(BytesWriterTest):
372386
result_type = bytearray
373387

374388
def create_writer(self, alloc=0, string=b''):
375389
return _testcapi.PyBytesWriter(alloc, string, 1)
376390

391+
377392
if __name__ == "__main__":
378393
unittest.main()

Misc/NEWS.d/next/C_API/2025-09-12-13-05-20.gh-issue-129813.dJZpME.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Implement :pep:`782`, the :c:type:`PyBytesWriter` API. Add functions:
55
* :c:func:`PyBytesWriter_FinishWithPointer`
66
* :c:func:`PyBytesWriter_FinishWithSize`
77
* :c:func:`PyBytesWriter_Finish`
8+
* :c:func:`PyBytesWriter_Format`
89
* :c:func:`PyBytesWriter_GetData`
910
* :c:func:`PyBytesWriter_GetSize`
1011
* :c:func:`PyBytesWriter_GrowAndUpdatePointer`

Modules/_testcapi/bytes.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,27 @@ writer_write_bytes(PyObject *self_raw, PyObject *args)
163163
}
164164

165165

166+
static PyObject*
167+
writer_format_i(PyObject *self_raw, PyObject *args)
168+
{
169+
WriterObject *self = (WriterObject *)self_raw;
170+
if (writer_check(self) < 0) {
171+
return NULL;
172+
}
173+
174+
char *format;
175+
int value;
176+
if (!PyArg_ParseTuple(args, "yi", &format, &value)) {
177+
return NULL;
178+
}
179+
180+
if (PyBytesWriter_Format(self->writer, format, value) < 0) {
181+
return NULL;
182+
}
183+
Py_RETURN_NONE;
184+
}
185+
186+
166187
static PyObject*
167188
writer_resize(PyObject *self_raw, PyObject *args)
168189
{
@@ -241,6 +262,7 @@ writer_finish_with_size(PyObject *self_raw, PyObject *args)
241262

242263
static PyMethodDef writer_methods[] = {
243264
{"write_bytes", _PyCFunction_CAST(writer_write_bytes), METH_VARARGS},
265+
{"format_i", _PyCFunction_CAST(writer_format_i), METH_VARARGS},
244266
{"resize", _PyCFunction_CAST(writer_resize), METH_VARARGS},
245267
{"get_size", _PyCFunction_CAST(writer_get_size), METH_NOARGS},
246268
{"finish", _PyCFunction_CAST(writer_finish), METH_NOARGS},
@@ -309,11 +331,33 @@ byteswriter_resize(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
309331
}
310332

311333

334+
static PyObject *
335+
byteswriter_highlevel(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
336+
{
337+
PyBytesWriter *writer = PyBytesWriter_Create(0);
338+
if (writer == NULL) {
339+
goto error;
340+
}
341+
if (PyBytesWriter_WriteBytes(writer, "Hello", -1) < 0) {
342+
goto error;
343+
}
344+
if (PyBytesWriter_Format(writer, " %s!", "World") < 0) {
345+
goto error;
346+
}
347+
return PyBytesWriter_Finish(writer);
348+
349+
error:
350+
PyBytesWriter_Discard(writer);
351+
return NULL;
352+
}
353+
354+
312355
static PyMethodDef test_methods[] = {
313356
{"bytes_resize", bytes_resize, METH_VARARGS},
314357
{"bytes_join", bytes_join, METH_VARARGS},
315358
{"byteswriter_abc", byteswriter_abc, METH_NOARGS},
316359
{"byteswriter_resize", byteswriter_resize, METH_NOARGS},
360+
{"byteswriter_highlevel", byteswriter_highlevel, METH_NOARGS},
317361
{NULL},
318362
};
319363

Objects/bytesobject.c

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,11 @@ PyBytes_FromString(const char *str)
196196
return (PyObject *) op;
197197
}
198198

199-
PyObject *
200-
PyBytes_FromFormatV(const char *format, va_list vargs)
199+
200+
static char*
201+
bytes_fromformat(PyBytesWriter *writer, Py_ssize_t writer_pos,
202+
const char *format, va_list vargs)
201203
{
202-
char *s;
203204
const char *f;
204205
const char *p;
205206
Py_ssize_t prec;
@@ -213,21 +214,20 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
213214
Longest 64-bit pointer representation:
214215
"0xffffffffffffffff\0" (19 bytes). */
215216
char buffer[21];
216-
_PyBytesWriter writer;
217217

218-
_PyBytesWriter_Init(&writer);
218+
char *s = (char*)PyBytesWriter_GetData(writer) + writer_pos;
219219

220-
s = _PyBytesWriter_Alloc(&writer, strlen(format));
221-
if (s == NULL)
222-
return NULL;
223-
writer.overallocate = 1;
224-
225-
#define WRITE_BYTES(str) \
220+
#define WRITE_BYTES_LEN(str, len_expr) \
226221
do { \
227-
s = _PyBytesWriter_WriteBytes(&writer, s, (str), strlen(str)); \
228-
if (s == NULL) \
222+
size_t len = (len_expr); \
223+
s = PyBytesWriter_GrowAndUpdatePointer(writer, len, s); \
224+
if (s == NULL) { \
229225
goto error; \
226+
} \
227+
memcpy(s, (str), len); \
228+
s += len; \
230229
} while (0)
230+
#define WRITE_BYTES(str) WRITE_BYTES_LEN(str, strlen(str))
231231

232232
for (f = format; *f; f++) {
233233
if (*f != '%') {
@@ -268,10 +268,6 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
268268
++f;
269269
}
270270

271-
/* subtract bytes preallocated for the format string
272-
(ex: 2 for "%s") */
273-
writer.min_size -= (f - p + 1);
274-
275271
switch (*f) {
276272
case 'c':
277273
{
@@ -282,7 +278,6 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
282278
"expects an integer in range [0; 255]");
283279
goto error;
284280
}
285-
writer.min_size++;
286281
*s++ = (unsigned char)c;
287282
break;
288283
}
@@ -341,9 +336,7 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
341336
i++;
342337
}
343338
}
344-
s = _PyBytesWriter_WriteBytes(&writer, s, p, i);
345-
if (s == NULL)
346-
goto error;
339+
WRITE_BYTES_LEN(p, i);
347340
break;
348341
}
349342

@@ -362,31 +355,45 @@ PyBytes_FromFormatV(const char *format, va_list vargs)
362355
break;
363356

364357
case '%':
365-
writer.min_size++;
366358
*s++ = '%';
367359
break;
368360

369361
default:
370-
if (*f == 0) {
371-
/* fix min_size if we reached the end of the format string */
372-
writer.min_size++;
373-
}
374-
375362
/* invalid format string: copy unformatted string and exit */
376363
WRITE_BYTES(p);
377-
return _PyBytesWriter_Finish(&writer, s);
364+
return s;
378365
}
379366
}
380367

381368
#undef WRITE_BYTES
369+
#undef WRITE_BYTES_LEN
382370

383-
return _PyBytesWriter_Finish(&writer, s);
371+
return s;
384372

385373
error:
386-
_PyBytesWriter_Dealloc(&writer);
387374
return NULL;
388375
}
389376

377+
378+
PyObject *
379+
PyBytes_FromFormatV(const char *format, va_list vargs)
380+
{
381+
Py_ssize_t alloc = strlen(format);
382+
PyBytesWriter *writer = PyBytesWriter_Create(alloc);
383+
if (writer == NULL) {
384+
return NULL;
385+
}
386+
387+
char *s = bytes_fromformat(writer, 0, format, vargs);
388+
if (s == NULL) {
389+
PyBytesWriter_Discard(writer);
390+
return NULL;
391+
}
392+
393+
return PyBytesWriter_FinishWithPointer(writer, s);
394+
}
395+
396+
390397
PyObject *
391398
PyBytes_FromFormat(const char *format, ...)
392399
{
@@ -399,6 +406,7 @@ PyBytes_FromFormat(const char *format, ...)
399406
return ret;
400407
}
401408

409+
402410
/* Helpers for formatstring */
403411

404412
Py_LOCAL_INLINE(PyObject *)
@@ -4048,3 +4056,21 @@ PyBytesWriter_WriteBytes(PyBytesWriter *writer,
40484056
memcpy(buf + pos, bytes, size);
40494057
return 0;
40504058
}
4059+
4060+
4061+
int
4062+
PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
4063+
{
4064+
Py_ssize_t pos = writer->size;
4065+
if (PyBytesWriter_Grow(writer, strlen(format)) < 0) {
4066+
return -1;
4067+
}
4068+
4069+
va_list vargs;
4070+
va_start(vargs, format);
4071+
char *buf = bytes_fromformat(writer, pos, format, vargs);
4072+
va_end(vargs);
4073+
4074+
Py_ssize_t size = buf - byteswriter_data(writer);
4075+
return PyBytesWriter_Resize(writer, size);
4076+
}

0 commit comments

Comments
 (0)