Skip to content
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
9 changes: 9 additions & 0 deletions Doc/c-api/bytes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^^^
Expand Down
1 change: 1 addition & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
4 changes: 4 additions & 0 deletions Include/cpython/bytesobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_capi/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,18 +361,33 @@ def test_resize(self):
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

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


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
44 changes: 44 additions & 0 deletions Modules/_testcapi/bytes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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},
};

Expand Down
86 changes: 56 additions & 30 deletions Objects/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 != '%') {
Expand Down Expand Up @@ -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':
{
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}

Expand All @@ -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, ...)
{
Expand All @@ -399,6 +406,7 @@ PyBytes_FromFormat(const char *format, ...)
return ret;
}


/* Helpers for formatstring */

Py_LOCAL_INLINE(PyObject *)
Expand Down Expand Up @@ -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);
}
Loading