Skip to content

Commit 8749b1c

Browse files
committed
pythongh-129813: Add PyBytesWriter C API (version 2)
Add functions: * PyBytesWriter_Create() * PyBytesWriter_Discard() * PyBytesWriter_Finish() * PyBytesWriter_Alloc() * PyBytesWriter_Extend() * PyBytesWriter_Truncate() * PyBytesWriter_WriteBytes()
1 parent 486d537 commit 8749b1c

File tree

10 files changed

+683
-86
lines changed

10 files changed

+683
-86
lines changed

Include/cpython/bytesobject.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,31 @@ _PyBytes_Join(PyObject *sep, PyObject *iterable)
4040
{
4141
return PyBytes_Join(sep, iterable);
4242
}
43+
44+
45+
// --- PyBytesWriter API -----------------------------------------------------
46+
47+
typedef struct PyBytesWriter PyBytesWriter;
48+
49+
PyAPI_FUNC(PyBytesWriter *) PyBytesWriter_Create(
50+
Py_ssize_t prealloc);
51+
PyAPI_FUNC(void) PyBytesWriter_Discard(
52+
PyBytesWriter *writer);
53+
PyAPI_FUNC(PyObject*) PyBytesWriter_Finish(
54+
PyBytesWriter *writer);
55+
56+
PyAPI_FUNC(void*) PyBytesWriter_Alloc(
57+
PyBytesWriter *writer,
58+
Py_ssize_t alloc);
59+
PyAPI_FUNC(void*) PyBytesWriter_Extend(
60+
PyBytesWriter *writer,
61+
void *buf,
62+
Py_ssize_t extend);
63+
PyAPI_FUNC(int) PyBytesWriter_Truncate(
64+
PyBytesWriter *writer,
65+
void *buf);
66+
67+
PyAPI_FUNC(int) PyBytesWriter_WriteBytes(
68+
PyBytesWriter *writer,
69+
const void *bytes,
70+
Py_ssize_t size);

Include/internal/pycore_freelist_state.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ extern "C" {
2424
# define Py_futureiters_MAXFREELIST 255
2525
# define Py_object_stack_chunks_MAXFREELIST 4
2626
# define Py_unicode_writers_MAXFREELIST 1
27+
# define Py_bytes_writers_MAXFREELIST 1
2728
# define Py_pymethodobjects_MAXFREELIST 20
2829

2930
// A generic freelist of either PyObjects or other data structures.
@@ -53,6 +54,7 @@ struct _Py_freelists {
5354
struct _Py_freelist futureiters;
5455
struct _Py_freelist object_stack_chunks;
5556
struct _Py_freelist unicode_writers;
57+
struct _Py_freelist bytes_writers;
5658
struct _Py_freelist pymethodobjects;
5759
};
5860

Lib/test/test_capi/test_bytes.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,5 +291,70 @@ def test_join(self):
291291
bytes_join(b'', NULL)
292292

293293

294+
class PyBytesWriterTest(unittest.TestCase):
295+
def create_writer(self, prealloc):
296+
return _testcapi.PyBytesWriter(prealloc)
297+
298+
def test_empty(self):
299+
# Test PyBytesWriter_Create()
300+
writer = self.create_writer(0)
301+
self.assertEqual(writer.finish(), b'')
302+
303+
def test_write_bytes(self):
304+
# Test PyBytesWriter_WriteBytes()
305+
306+
writer = self.create_writer(0)
307+
writer.write_bytes(b'Hello World!', -1)
308+
self.assertEqual(writer.finish(), b'Hello World!')
309+
310+
writer = self.create_writer(0)
311+
writer.write_bytes(b'Hello ', -1)
312+
writer.write_bytes(b'World! <truncated>', 6)
313+
self.assertEqual(writer.finish(), b'Hello World!')
314+
315+
def test_extend(self):
316+
# Test PyBytesWriter_Extend() and PyBytesWriter_SetSizeFromBuf()
317+
318+
writer = self.create_writer(0)
319+
writer.extend(13, b'number=123456')
320+
writer.extend(0, b'')
321+
self.assertEqual(writer.finish(), b'number=123456')
322+
323+
writer = self.create_writer(0)
324+
writer.extend(0, b'')
325+
writer.extend(13, b'number=123456')
326+
self.assertEqual(writer.finish(), b'number=123456')
327+
328+
writer = self.create_writer(0)
329+
writer.extend(10, b'number=')
330+
writer.extend(10, b'123456')
331+
self.assertEqual(writer.finish(), b'number=123456')
332+
333+
writer = self.create_writer(0)
334+
writer.extend(7, b'number=')
335+
writer.extend(0, b'')
336+
writer.extend(6, b'123456')
337+
self.assertEqual(writer.finish(), b'number=123456')
338+
339+
writer = self.create_writer(0)
340+
writer.extend(6, b'number')
341+
writer.extend(1, b'=')
342+
writer.extend(3, b'123')
343+
writer.extend(3, b'456')
344+
self.assertEqual(writer.finish(), b'number=123456')
345+
346+
def test_hello_world(self):
347+
self.assertEqual(_testcapi.byteswriter_hello_world(),
348+
b'Hello World')
349+
350+
def test_alloc(self):
351+
self.assertEqual(_testcapi.byteswriter_alloc(),
352+
b'abc')
353+
354+
def test_extend(self):
355+
self.assertEqual(_testcapi.byteswriter_extend(),
356+
b'Hello World')
357+
358+
294359
if __name__ == "__main__":
295360
unittest.main()

Modules/_pickle.c

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2615,29 +2615,25 @@ save_picklebuffer(PickleState *st, PicklerObject *self, PyObject *obj)
26152615
static PyObject *
26162616
raw_unicode_escape(PyObject *obj)
26172617
{
2618-
char *p;
2619-
Py_ssize_t i, size;
2620-
const void *data;
2621-
int kind;
2622-
_PyBytesWriter writer;
2618+
Py_ssize_t size = PyUnicode_GET_LENGTH(obj);
2619+
const void *data = PyUnicode_DATA(obj);
2620+
int kind = PyUnicode_KIND(obj);
26232621

2624-
_PyBytesWriter_Init(&writer);
2625-
2626-
size = PyUnicode_GET_LENGTH(obj);
2627-
data = PyUnicode_DATA(obj);
2628-
kind = PyUnicode_KIND(obj);
2629-
2630-
p = _PyBytesWriter_Alloc(&writer, size);
2631-
if (p == NULL)
2622+
PyBytesWriter *writer = PyBytesWriter_Create(size);
2623+
if (writer == NULL) {
2624+
return NULL;
2625+
}
2626+
char *p = PyBytesWriter_Alloc(writer, size);
2627+
if (p == NULL) {
26322628
goto error;
2633-
writer.overallocate = 1;
2629+
}
26342630

2635-
for (i=0; i < size; i++) {
2631+
for (Py_ssize_t i=0; i < size; i++) {
26362632
Py_UCS4 ch = PyUnicode_READ(kind, data, i);
26372633
/* Map 32-bit characters to '\Uxxxxxxxx' */
26382634
if (ch >= 0x10000) {
26392635
/* -1: subtract 1 preallocated byte */
2640-
p = _PyBytesWriter_Prepare(&writer, p, 10-1);
2636+
p = PyBytesWriter_Extend(writer, p, 10-1);
26412637
if (p == NULL)
26422638
goto error;
26432639

@@ -2658,7 +2654,7 @@ raw_unicode_escape(PyObject *obj)
26582654
ch == 0x1a)
26592655
{
26602656
/* -1: subtract 1 preallocated byte */
2661-
p = _PyBytesWriter_Prepare(&writer, p, 6-1);
2657+
p = PyBytesWriter_Extend(writer, p, 6-1);
26622658
if (p == NULL)
26632659
goto error;
26642660

@@ -2674,10 +2670,13 @@ raw_unicode_escape(PyObject *obj)
26742670
*p++ = (char) ch;
26752671
}
26762672

2677-
return _PyBytesWriter_Finish(&writer, p);
2673+
if (PyBytesWriter_Truncate(writer, p) < 0) {
2674+
goto error;
2675+
}
2676+
return PyBytesWriter_Finish(writer);
26782677

26792678
error:
2680-
_PyBytesWriter_Dealloc(&writer);
2679+
PyBytesWriter_Discard(writer);
26812680
return NULL;
26822681
}
26832682

Modules/_struct.c

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2272,7 +2272,6 @@ strings.");
22722272
static PyObject *
22732273
s_pack(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
22742274
{
2275-
char *buf;
22762275
PyStructObject *soself;
22772276
_structmodulestate *state = get_struct_state_structinst(self);
22782277

@@ -2288,21 +2287,23 @@ s_pack(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
22882287
}
22892288

22902289
/* Allocate a new string */
2291-
_PyBytesWriter writer;
2292-
_PyBytesWriter_Init(&writer);
2293-
buf = _PyBytesWriter_Alloc(&writer, soself->s_size);
2290+
PyBytesWriter *writer = PyBytesWriter_Create(soself->s_size);
2291+
if (writer == NULL) {
2292+
return NULL;
2293+
}
2294+
char *buf = PyBytesWriter_Alloc(writer, soself->s_size);
22942295
if (buf == NULL) {
2295-
_PyBytesWriter_Dealloc(&writer);
2296+
PyBytesWriter_Discard(writer);
22962297
return NULL;
22972298
}
22982299

22992300
/* Call the guts */
23002301
if ( s_pack_internal(soself, args, 0, buf, state) != 0 ) {
2301-
_PyBytesWriter_Dealloc(&writer);
2302+
PyBytesWriter_Discard(writer);
23022303
return NULL;
23032304
}
23042305

2305-
return _PyBytesWriter_Finish(&writer, buf + soself->s_size);
2306+
return PyBytesWriter_Finish(writer);
23062307
}
23072308

23082309
PyDoc_STRVAR(s_pack_into__doc__,

0 commit comments

Comments
 (0)