Skip to content

gh-121710: Add PyBytesWriter API #121726

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
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
49 changes: 49 additions & 0 deletions Doc/c-api/bytes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,52 @@ called with a non-bytes parameter.
reallocation fails, the original bytes object at *\*bytes* is deallocated,
*\*bytes* is set to ``NULL``, :exc:`MemoryError` is set, and ``-1`` is
returned.

PyBytesWriter
^^^^^^^^^^^^^

The :c:type:`PyBytesWriter` API can be used to create a Python :class:`bytes`
object.

.. versionadded:: 3.14

.. c:type:: PyBytesWriter

A bytes writer instance.

The instance must be destroyed by :c:func:`PyBytesWriter_Finish` on
success, or :c:func:`PyBytesWriter_Discard` on error.

.. c:function:: PyBytesWriter* PyBytesWriter_Create(Py_ssize_t size, char **str)

Create a bytes writer instance.
Preallocate *size* bytes.

On success, set *\*str* and return a new writer.
On error, set an exception and return ``NULL``.

.. c:function:: PyObject* PyBytesWriter_Finish(PyBytesWriter *writer, char *str)

Return the final Python :class:`bytes` object and destroy the writer
instance.

On success, return a bytes object.
On error, set an exception and return ``NULL``.

.. c:function:: void PyBytesWriter_Discard(PyBytesWriter *writer)

Discard the internal bytes buffer and destroy the writer instance.

.. c:function:: int PyBytesWriter_Prepare(PyBytesWriter *writer, char **str, Py_ssize_t size)

Allocate *size* bytes to prepare writing *size* bytes into *writer*.

On success, update *\*str* and return ``0``.
On error, set an exception and return ``-1``.

.. c:function:: int PyBytesWriter_WriteBytes(PyBytesWriter *writer, char **str, const void *bytes, Py_ssize_t size)

Write a the bytes string *bytes* of *size* bytes into *writer*.

On success, update *\*str* and return ``0``.
On error, set an exception and return ``-1``.
1 change: 1 addition & 0 deletions Doc/c-api/unicode.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,7 @@ object.
.. c:function:: PyUnicodeWriter* PyUnicodeWriter_Create(Py_ssize_t length)

Create a Unicode writer instance.
Preallocate *length* characters.

Set an exception and return ``NULL`` on error.

Expand Down
11 changes: 11 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,17 @@ New Features

(Contributed by Victor Stinner in :gh:`119182`.)

* Add a new :c:type:`PyBytesWriter` API to create a Python :class:`bytes`
object:

* :c:func:`PyBytesWriter_Create`;
* :c:func:`PyBytesWriter_Finish`;
* :c:func:`PyBytesWriter_Discard`;
* :c:func:`PyBytesWriter_Prepare`;
* :c:func:`PyBytesWriter_WriteBytes`.

(Contributed by Victor Stinner in :gh:`121710`.)

Porting to Python 3.14
----------------------

Expand Down
22 changes: 22 additions & 0 deletions Include/cpython/bytesobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,25 @@ static inline Py_ssize_t PyBytes_GET_SIZE(PyObject *op) {
/* _PyBytes_Join(sep, x) is like sep.join(x). sep must be PyBytesObject*,
x must be an iterable object. */
PyAPI_FUNC(PyObject*) _PyBytes_Join(PyObject *sep, PyObject *x);

/* --- PyBytesWriter ------------------------------------------------------ */

typedef struct PyBytesWriter PyBytesWriter;

PyAPI_FUNC(PyBytesWriter*) PyBytesWriter_Create(
Py_ssize_t size,
char **str);
PyAPI_FUNC(PyObject *) PyBytesWriter_Finish(
PyBytesWriter *writer,
char *str);
PyAPI_FUNC(void) PyBytesWriter_Discard(PyBytesWriter *writer);

PyAPI_FUNC(int) PyBytesWriter_Prepare(
PyBytesWriter *writer,
char **str,
Py_ssize_t size);
PyAPI_FUNC(int) PyBytesWriter_WriteBytes(
PyBytesWriter *writer,
char **str,
const void *bytes,
Py_ssize_t size);
19 changes: 2 additions & 17 deletions Include/internal/pycore_bytesobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ typedef struct {
This flag must be zero if use_bytearray is non-zero. */
int overallocate;

/* Stack buffer */
/* Small buffer: smaller than pymalloc 512 bytes threshold */
int use_small_buffer;
char small_buffer[512];
char small_buffer[256];
} _PyBytesWriter;

/* Initialize a bytes writer
Expand Down Expand Up @@ -120,21 +120,6 @@ PyAPI_FUNC(void*) _PyBytesWriter_Prepare(_PyBytesWriter *writer,
void *str,
Py_ssize_t size);

/* Resize the buffer to make it larger.
The new buffer may be larger than size bytes because of overallocation.
Return the updated current pointer inside the buffer.
Raise an exception and return NULL on error.

Note: size must be greater than the number of allocated bytes in the writer.

This function doesn't use the writer minimum size (min_size attribute).

See also _PyBytesWriter_Prepare().
*/
PyAPI_FUNC(void*) _PyBytesWriter_Resize(_PyBytesWriter *writer,
void *str,
Py_ssize_t size);

/* Write bytes.
Raise an exception and return NULL on error. */
PyAPI_FUNC(void*) _PyBytesWriter_WriteBytes(_PyBytesWriter *writer,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Add a new :c:type:`PyBytesWriter` API to create a Python :class:`bytes`
object:

* :c:func:`PyBytesWriter_Create`;
* :c:func:`PyBytesWriter_Finish`;
* :c:func:`PyBytesWriter_Discard`;
* :c:func:`PyBytesWriter_Prepare`;
* :c:func:`PyBytesWriter_WriteBytes`.

Patch by Victor Stinner.
132 changes: 132 additions & 0 deletions Modules/_testcapi/bytes.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,140 @@ bytes_resize(PyObject *Py_UNUSED(module), PyObject *args)
}


static int
bytes_equal(PyObject *obj, const char *str)
{
return (PyBytes_Size(obj) == (Py_ssize_t)strlen(str)
&& strcmp(PyBytes_AsString(obj), str) == 0);
}


/* Test PyBytesWriter API */
static PyObject *
test_byteswriter(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
{
char *str;
PyBytesWriter *writer = PyBytesWriter_Create(3, &str);
if (writer == NULL) {
return NULL;
}

if (PyBytesWriter_WriteBytes(writer, &str, "abc", 3) < 0) {
goto error;
}

// write empty string
if (PyBytesWriter_WriteBytes(writer, &str, "", 0) < 0) {
goto error;
}

PyObject *obj = PyBytesWriter_Finish(writer, str);
if (obj == NULL) {
return NULL;
}

assert(bytes_equal(obj, "abc"));
Py_DECREF(obj);

Py_RETURN_NONE;

error:
PyBytesWriter_Discard(writer);
return NULL;
}


/* Test PyBytesWriter_Discard() */
static PyObject *
test_byteswriter_discard(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
{
char *str;
PyBytesWriter *writer = PyBytesWriter_Create(3, &str);
if (writer == NULL) {
return NULL;
}
assert(PyBytesWriter_WriteBytes(writer, &str, "abc", 3) == 0);

PyBytesWriter_Discard(writer);
Py_RETURN_NONE;
}


/* Test PyBytesWriter_WriteBytes() */
static PyObject *
test_byteswriter_writebytes(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
{
char *str;
PyBytesWriter *writer = PyBytesWriter_Create(0, &str);
if (writer == NULL) {
return NULL;
}

if (PyBytesWriter_WriteBytes(writer, &str, "abc", 3) < 0) {
goto error;
}
if (PyBytesWriter_WriteBytes(writer, &str, "def", 3) < 0) {
goto error;
}

PyObject *obj = PyBytesWriter_Finish(writer, str);
if (obj == NULL) {
return NULL;
}

assert(bytes_equal(obj, "abcdef"));
Py_DECREF(obj);

Py_RETURN_NONE;

error:
PyBytesWriter_Discard(writer);
return NULL;
}


/* Test PyBytesWriter_Prepare() */
static PyObject *
test_byteswriter_prepare(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
{
char *str;
PyBytesWriter *writer = PyBytesWriter_Create(0, &str);
if (writer == NULL) {
return NULL;
}

// test error on purpose (negative size)
assert(PyBytesWriter_Prepare(writer, &str, -3) < 0);
assert(PyErr_ExceptionMatches(PyExc_ValueError));
PyErr_Clear();

if (PyBytesWriter_Prepare(writer, &str, 3) < 0) {
PyBytesWriter_Discard(writer);
return NULL;
}

// Write "abc"
memcpy(str, "abc", 3);
str += 3;

PyObject *obj = PyBytesWriter_Finish(writer, str);
if (obj == NULL) {
return NULL;
}

assert(bytes_equal(obj, "abc"));
Py_DECREF(obj);

Py_RETURN_NONE;
}


static PyMethodDef test_methods[] = {
{"bytes_resize", bytes_resize, METH_VARARGS},
{"test_byteswriter", test_byteswriter, METH_NOARGS},
{"test_byteswriter_discard", test_byteswriter_discard, METH_NOARGS},
{"test_byteswriter_writebytes", test_byteswriter_writebytes, METH_NOARGS},
{"test_byteswriter_prepare", test_byteswriter_prepare, METH_NOARGS},
{NULL},
};

Expand Down
Loading
Loading