Skip to content

Commit

Permalink
pythongh-121710: Add PyBytesWriter API
Browse files Browse the repository at this point in the history
  • Loading branch information
vstinner committed Jul 13, 2024
1 parent c0af6d4 commit 7862808
Show file tree
Hide file tree
Showing 7 changed files with 335 additions and 0 deletions.
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 @@ -1521,6 +1521,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 @@ -357,6 +357,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
23 changes: 23 additions & 0 deletions Include/cpython/bytesobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,26 @@ static inline Py_ssize_t PyBytes_GET_SIZE(PyObject *op) {
return Py_SIZE(self);
}
#define PyBytes_GET_SIZE(self) PyBytes_GET_SIZE(_PyObject_CAST(self))


/* --- 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);
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

0 comments on commit 7862808

Please sign in to comment.