Skip to content

Commit 7862808

Browse files
committed
gh-121710: Add PyBytesWriter API
1 parent c0af6d4 commit 7862808

File tree

7 files changed

+335
-0
lines changed

7 files changed

+335
-0
lines changed

Doc/c-api/bytes.rst

+49
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,52 @@ called with a non-bytes parameter.
201201
reallocation fails, the original bytes object at *\*bytes* is deallocated,
202202
*\*bytes* is set to ``NULL``, :exc:`MemoryError` is set, and ``-1`` is
203203
returned.
204+
205+
PyBytesWriter
206+
^^^^^^^^^^^^^
207+
208+
The :c:type:`PyBytesWriter` API can be used to create a Python :class:`bytes`
209+
object.
210+
211+
.. versionadded:: 3.14
212+
213+
.. c:type:: PyBytesWriter
214+
215+
A bytes writer instance.
216+
217+
The instance must be destroyed by :c:func:`PyBytesWriter_Finish` on
218+
success, or :c:func:`PyBytesWriter_Discard` on error.
219+
220+
.. c:function:: PyBytesWriter* PyBytesWriter_Create(Py_ssize_t size, char **str)
221+
222+
Create a bytes writer instance.
223+
Preallocate *size* bytes.
224+
225+
On success, set *\*str* and return a new writer.
226+
On error, set an exception and return ``NULL``.
227+
228+
.. c:function:: PyObject* PyBytesWriter_Finish(PyBytesWriter *writer, char *str)
229+
230+
Return the final Python :class:`bytes` object and destroy the writer
231+
instance.
232+
233+
On success, return a bytes object.
234+
On error, set an exception and return ``NULL``.
235+
236+
.. c:function:: void PyBytesWriter_Discard(PyBytesWriter *writer)
237+
238+
Discard the internal bytes buffer and destroy the writer instance.
239+
240+
.. c:function:: int PyBytesWriter_Prepare(PyBytesWriter *writer, char **str, Py_ssize_t size)
241+
242+
Allocate *size* bytes to prepare writing *size* bytes into *writer*.
243+
244+
On success, update *\*str* and return ``0``.
245+
On error, set an exception and return ``-1``.
246+
247+
.. c:function:: int PyBytesWriter_WriteBytes(PyBytesWriter *writer, char **str, const void *bytes, Py_ssize_t size)
248+
249+
Write a the bytes string *bytes* of *size* bytes into *writer*.
250+
251+
On success, update *\*str* and return ``0``.
252+
On error, set an exception and return ``-1``.

Doc/c-api/unicode.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1521,6 +1521,7 @@ object.
15211521
.. c:function:: PyUnicodeWriter* PyUnicodeWriter_Create(Py_ssize_t length)
15221522
15231523
Create a Unicode writer instance.
1524+
Preallocate *length* characters.
15241525
15251526
Set an exception and return ``NULL`` on error.
15261527

Doc/whatsnew/3.14.rst

+11
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,17 @@ New Features
357357

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

360+
* Add a new :c:type:`PyBytesWriter` API to create a Python :class:`bytes`
361+
object:
362+
363+
* :c:func:`PyBytesWriter_Create`;
364+
* :c:func:`PyBytesWriter_Finish`;
365+
* :c:func:`PyBytesWriter_Discard`;
366+
* :c:func:`PyBytesWriter_Prepare`;
367+
* :c:func:`PyBytesWriter_WriteBytes`.
368+
369+
(Contributed by Victor Stinner in :gh:`121710`.)
370+
360371
Porting to Python 3.14
361372
----------------------
362373

Include/cpython/bytesobject.h

+23
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,26 @@ static inline Py_ssize_t PyBytes_GET_SIZE(PyObject *op) {
3131
return Py_SIZE(self);
3232
}
3333
#define PyBytes_GET_SIZE(self) PyBytes_GET_SIZE(_PyObject_CAST(self))
34+
35+
36+
/* --- PyBytesWriter ------------------------------------------------------ */
37+
38+
typedef struct PyBytesWriter PyBytesWriter;
39+
40+
PyAPI_FUNC(PyBytesWriter*) PyBytesWriter_Create(
41+
Py_ssize_t size,
42+
char **str);
43+
PyAPI_FUNC(PyObject *) PyBytesWriter_Finish(
44+
PyBytesWriter *writer,
45+
char *str);
46+
PyAPI_FUNC(void) PyBytesWriter_Discard(PyBytesWriter *writer);
47+
48+
PyAPI_FUNC(int) PyBytesWriter_Prepare(
49+
PyBytesWriter *writer,
50+
char **str,
51+
Py_ssize_t size);
52+
PyAPI_FUNC(int) PyBytesWriter_WriteBytes(
53+
PyBytesWriter *writer,
54+
char **str,
55+
const void *bytes,
56+
Py_ssize_t size);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Add a new :c:type:`PyBytesWriter` API to create a Python :class:`bytes`
2+
object:
3+
4+
* :c:func:`PyBytesWriter_Create`;
5+
* :c:func:`PyBytesWriter_Finish`;
6+
* :c:func:`PyBytesWriter_Discard`;
7+
* :c:func:`PyBytesWriter_Prepare`;
8+
* :c:func:`PyBytesWriter_WriteBytes`.
9+
10+
Patch by Victor Stinner.

Modules/_testcapi/bytes.c

+132
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,140 @@ bytes_resize(PyObject *Py_UNUSED(module), PyObject *args)
3737
}
3838

3939

40+
static int
41+
bytes_equal(PyObject *obj, const char *str)
42+
{
43+
return (PyBytes_Size(obj) == (Py_ssize_t)strlen(str)
44+
&& strcmp(PyBytes_AsString(obj), str) == 0);
45+
}
46+
47+
48+
/* Test PyBytesWriter API */
49+
static PyObject *
50+
test_byteswriter(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
51+
{
52+
char *str;
53+
PyBytesWriter *writer = PyBytesWriter_Create(3, &str);
54+
if (writer == NULL) {
55+
return NULL;
56+
}
57+
58+
if (PyBytesWriter_WriteBytes(writer, &str, "abc", 3) < 0) {
59+
goto error;
60+
}
61+
62+
// write empty string
63+
if (PyBytesWriter_WriteBytes(writer, &str, "", 0) < 0) {
64+
goto error;
65+
}
66+
67+
PyObject *obj = PyBytesWriter_Finish(writer, str);
68+
if (obj == NULL) {
69+
return NULL;
70+
}
71+
72+
assert(bytes_equal(obj, "abc"));
73+
Py_DECREF(obj);
74+
75+
Py_RETURN_NONE;
76+
77+
error:
78+
PyBytesWriter_Discard(writer);
79+
return NULL;
80+
}
81+
82+
83+
/* Test PyBytesWriter_Discard() */
84+
static PyObject *
85+
test_byteswriter_discard(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
86+
{
87+
char *str;
88+
PyBytesWriter *writer = PyBytesWriter_Create(3, &str);
89+
if (writer == NULL) {
90+
return NULL;
91+
}
92+
assert(PyBytesWriter_WriteBytes(writer, &str, "abc", 3) == 0);
93+
94+
PyBytesWriter_Discard(writer);
95+
Py_RETURN_NONE;
96+
}
97+
98+
99+
/* Test PyBytesWriter_WriteBytes() */
100+
static PyObject *
101+
test_byteswriter_writebytes(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
102+
{
103+
char *str;
104+
PyBytesWriter *writer = PyBytesWriter_Create(0, &str);
105+
if (writer == NULL) {
106+
return NULL;
107+
}
108+
109+
if (PyBytesWriter_WriteBytes(writer, &str, "abc", 3) < 0) {
110+
goto error;
111+
}
112+
if (PyBytesWriter_WriteBytes(writer, &str, "def", 3) < 0) {
113+
goto error;
114+
}
115+
116+
PyObject *obj = PyBytesWriter_Finish(writer, str);
117+
if (obj == NULL) {
118+
return NULL;
119+
}
120+
121+
assert(bytes_equal(obj, "abcdef"));
122+
Py_DECREF(obj);
123+
124+
Py_RETURN_NONE;
125+
126+
error:
127+
PyBytesWriter_Discard(writer);
128+
return NULL;
129+
}
130+
131+
132+
/* Test PyBytesWriter_Prepare() */
133+
static PyObject *
134+
test_byteswriter_prepare(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
135+
{
136+
char *str;
137+
PyBytesWriter *writer = PyBytesWriter_Create(0, &str);
138+
if (writer == NULL) {
139+
return NULL;
140+
}
141+
142+
// test error on purpose (negative size)
143+
assert(PyBytesWriter_Prepare(writer, &str, -3) < 0);
144+
assert(PyErr_ExceptionMatches(PyExc_ValueError));
145+
PyErr_Clear();
146+
147+
if (PyBytesWriter_Prepare(writer, &str, 3) < 0) {
148+
PyBytesWriter_Discard(writer);
149+
return NULL;
150+
}
151+
152+
// Write "abc"
153+
memcpy(str, "abc", 3);
154+
str += 3;
155+
156+
PyObject *obj = PyBytesWriter_Finish(writer, str);
157+
if (obj == NULL) {
158+
return NULL;
159+
}
160+
161+
assert(bytes_equal(obj, "abc"));
162+
Py_DECREF(obj);
163+
164+
Py_RETURN_NONE;
165+
}
166+
167+
40168
static PyMethodDef test_methods[] = {
41169
{"bytes_resize", bytes_resize, METH_VARARGS},
170+
{"test_byteswriter", test_byteswriter, METH_NOARGS},
171+
{"test_byteswriter_discard", test_byteswriter_discard, METH_NOARGS},
172+
{"test_byteswriter_writebytes", test_byteswriter_writebytes, METH_NOARGS},
173+
{"test_byteswriter_prepare", test_byteswriter_prepare, METH_NOARGS},
42174
{NULL},
43175
};
44176

0 commit comments

Comments
 (0)