Skip to content

Commit ab72af8

Browse files
authored
PEP 782: Add PyBytesWriter C API (#139)
1 parent 90c06a4 commit ab72af8

File tree

4 files changed

+383
-0
lines changed

4 files changed

+383
-0
lines changed

docs/api.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,22 @@ Python 3.15
4545

4646
See `PySys_GetOptionalAttrString() documentation <https://docs.python.org/dev/c-api/sys.html#c.PySys_GetOptionalAttrString>`__.
4747

48+
.. c:function:: PyBytesWriter* PyBytesWriter_Create(Py_ssize_t size)
49+
.. c:function:: void* PyBytesWriter_GetData(PyBytesWriter *writer)
50+
.. c:function:: Py_ssize_t PyBytesWriter_GetSize(PyBytesWriter *writer)
51+
.. c:function:: PyObject* PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
52+
.. c:function:: PyObject* PyBytesWriter_Finish(PyBytesWriter *writer)
53+
.. c:function:: PyObject* PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
54+
.. c:function:: void PyBytesWriter_Discard(PyBytesWriter *writer)
55+
.. c:function:: int PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)
56+
.. c:function:: int PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t size)
57+
.. c:function:: void* PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer, Py_ssize_t size, void *buf)
58+
.. c:function:: int PyBytesWriter_WriteBytes(PyBytesWriter *writer, const void *bytes, Py_ssize_t size)
59+
.. c:function:: int PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
60+
61+
See `PyBytesWriter documentation <https://docs.python.org/dev/c-api/bytes.html#pybyteswriter>`__.
62+
63+
4864
Python 3.14
4965
-----------
5066

docs/changelog.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
Changelog
22
=========
33

4+
* 2025-11-18: Add PEP 782 functions:
5+
6+
* ``PyBytesWriter_Create()``
7+
* ``PyBytesWriter_Discard()``
8+
* ``PyBytesWriter_Finish()``
9+
* ``PyBytesWriter_FinishWithPointer()``
10+
* ``PyBytesWriter_FinishWithSize()``
11+
* ``PyBytesWriter_Format()``
12+
* ``PyBytesWriter_GetData()``
13+
* ``PyBytesWriter_GetSize()``
14+
* ``PyBytesWriter_Grow()``
15+
* ``PyBytesWriter_GrowAndUpdatePointer()``
16+
* ``PyBytesWriter_Resize()``
17+
* ``PyBytesWriter_WriteBytes()``
18+
419
* 2025-09-01: Add ``PyUnstable_Object_IsUniquelyReferenced()`` function.
520
* 2025-06-09: Add ``PyUnicodeWriter_WriteASCII()`` function.
621
* 2025-06-03: Add functions:

pythoncapi_compat.h

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2295,6 +2295,260 @@ PySys_GetOptionalAttr(PyObject *name, PyObject **value)
22952295
#endif // PY_VERSION_HEX < 0x030F00A1
22962296

22972297

2298+
#if PY_VERSION_HEX < 0x030F00A1
2299+
typedef struct PyBytesWriter {
2300+
char small_buffer[256];
2301+
PyObject *obj;
2302+
Py_ssize_t size;
2303+
} PyBytesWriter;
2304+
2305+
static inline Py_ssize_t
2306+
_PyBytesWriter_GetAllocated(PyBytesWriter *writer)
2307+
{
2308+
if (writer->obj == NULL) {
2309+
return sizeof(writer->small_buffer);
2310+
}
2311+
else {
2312+
return PyBytes_GET_SIZE(writer->obj);
2313+
}
2314+
}
2315+
2316+
2317+
static inline int
2318+
_PyBytesWriter_Resize_impl(PyBytesWriter *writer, Py_ssize_t size,
2319+
int resize)
2320+
{
2321+
int overallocate = resize;
2322+
assert(size >= 0);
2323+
2324+
if (size <= _PyBytesWriter_GetAllocated(writer)) {
2325+
return 0;
2326+
}
2327+
2328+
if (overallocate) {
2329+
#ifdef MS_WINDOWS
2330+
/* On Windows, overallocate by 50% is the best factor */
2331+
if (size <= (PY_SSIZE_T_MAX - size / 2)) {
2332+
size += size / 2;
2333+
}
2334+
#else
2335+
/* On Linux, overallocate by 25% is the best factor */
2336+
if (size <= (PY_SSIZE_T_MAX - size / 4)) {
2337+
size += size / 4;
2338+
}
2339+
#endif
2340+
}
2341+
2342+
if (writer->obj != NULL) {
2343+
if (_PyBytes_Resize(&writer->obj, size)) {
2344+
return -1;
2345+
}
2346+
assert(writer->obj != NULL);
2347+
}
2348+
else {
2349+
writer->obj = PyBytes_FromStringAndSize(NULL, size);
2350+
if (writer->obj == NULL) {
2351+
return -1;
2352+
}
2353+
2354+
if (resize) {
2355+
assert((size_t)size > sizeof(writer->small_buffer));
2356+
memcpy(PyBytes_AS_STRING(writer->obj),
2357+
writer->small_buffer,
2358+
sizeof(writer->small_buffer));
2359+
}
2360+
}
2361+
return 0;
2362+
}
2363+
2364+
static inline void*
2365+
PyBytesWriter_GetData(PyBytesWriter *writer)
2366+
{
2367+
if (writer->obj == NULL) {
2368+
return writer->small_buffer;
2369+
}
2370+
else {
2371+
return PyBytes_AS_STRING(writer->obj);
2372+
}
2373+
}
2374+
2375+
static inline Py_ssize_t
2376+
PyBytesWriter_GetSize(PyBytesWriter *writer)
2377+
{
2378+
return writer->size;
2379+
}
2380+
2381+
static inline void
2382+
PyBytesWriter_Discard(PyBytesWriter *writer)
2383+
{
2384+
if (writer == NULL) {
2385+
return;
2386+
}
2387+
2388+
Py_XDECREF(writer->obj);
2389+
PyMem_Free(writer);
2390+
}
2391+
2392+
static inline PyBytesWriter*
2393+
PyBytesWriter_Create(Py_ssize_t size)
2394+
{
2395+
if (size < 0) {
2396+
PyErr_SetString(PyExc_ValueError, "size must be >= 0");
2397+
return NULL;
2398+
}
2399+
2400+
PyBytesWriter *writer = (PyBytesWriter*)PyMem_Malloc(sizeof(PyBytesWriter));
2401+
if (writer == NULL) {
2402+
PyErr_NoMemory();
2403+
return NULL;
2404+
}
2405+
2406+
writer->obj = NULL;
2407+
writer->size = 0;
2408+
2409+
if (size >= 1) {
2410+
if (_PyBytesWriter_Resize_impl(writer, size, 0) < 0) {
2411+
PyBytesWriter_Discard(writer);
2412+
return NULL;
2413+
}
2414+
writer->size = size;
2415+
}
2416+
return writer;
2417+
}
2418+
2419+
static inline PyObject*
2420+
PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
2421+
{
2422+
PyObject *result;
2423+
if (size == 0) {
2424+
result = PyBytes_FromStringAndSize("", 0);
2425+
}
2426+
else if (writer->obj != NULL) {
2427+
if (size != PyBytes_GET_SIZE(writer->obj)) {
2428+
if (_PyBytes_Resize(&writer->obj, size)) {
2429+
goto error;
2430+
}
2431+
}
2432+
result = writer->obj;
2433+
writer->obj = NULL;
2434+
}
2435+
else {
2436+
result = PyBytes_FromStringAndSize(writer->small_buffer, size);
2437+
}
2438+
PyBytesWriter_Discard(writer);
2439+
return result;
2440+
2441+
error:
2442+
PyBytesWriter_Discard(writer);
2443+
return NULL;
2444+
}
2445+
2446+
static inline PyObject*
2447+
PyBytesWriter_Finish(PyBytesWriter *writer)
2448+
{
2449+
return PyBytesWriter_FinishWithSize(writer, writer->size);
2450+
}
2451+
2452+
static inline PyObject*
2453+
PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
2454+
{
2455+
Py_ssize_t size = (char*)buf - (char*)PyBytesWriter_GetData(writer);
2456+
if (size < 0 || size > _PyBytesWriter_GetAllocated(writer)) {
2457+
PyBytesWriter_Discard(writer);
2458+
PyErr_SetString(PyExc_ValueError, "invalid end pointer");
2459+
return NULL;
2460+
}
2461+
2462+
return PyBytesWriter_FinishWithSize(writer, size);
2463+
}
2464+
2465+
static inline int
2466+
PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)
2467+
{
2468+
if (size < 0) {
2469+
PyErr_SetString(PyExc_ValueError, "size must be >= 0");
2470+
return -1;
2471+
}
2472+
if (_PyBytesWriter_Resize_impl(writer, size, 1) < 0) {
2473+
return -1;
2474+
}
2475+
writer->size = size;
2476+
return 0;
2477+
}
2478+
2479+
static inline int
2480+
PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t size)
2481+
{
2482+
if (size < 0 && writer->size + size < 0) {
2483+
PyErr_SetString(PyExc_ValueError, "invalid size");
2484+
return -1;
2485+
}
2486+
if (size > PY_SSIZE_T_MAX - writer->size) {
2487+
PyErr_NoMemory();
2488+
return -1;
2489+
}
2490+
size = writer->size + size;
2491+
2492+
if (_PyBytesWriter_Resize_impl(writer, size, 1) < 0) {
2493+
return -1;
2494+
}
2495+
writer->size = size;
2496+
return 0;
2497+
}
2498+
2499+
static inline void*
2500+
PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer,
2501+
Py_ssize_t size, void *buf)
2502+
{
2503+
Py_ssize_t pos = (char*)buf - (char*)PyBytesWriter_GetData(writer);
2504+
if (PyBytesWriter_Grow(writer, size) < 0) {
2505+
return NULL;
2506+
}
2507+
return (char*)PyBytesWriter_GetData(writer) + pos;
2508+
}
2509+
2510+
static inline int
2511+
PyBytesWriter_WriteBytes(PyBytesWriter *writer,
2512+
const void *bytes, Py_ssize_t size)
2513+
{
2514+
if (size < 0) {
2515+
size_t len = strlen((const char*)bytes);
2516+
if (len > (size_t)PY_SSIZE_T_MAX) {
2517+
PyErr_NoMemory();
2518+
return -1;
2519+
}
2520+
size = (Py_ssize_t)len;
2521+
}
2522+
2523+
Py_ssize_t pos = writer->size;
2524+
if (PyBytesWriter_Grow(writer, size) < 0) {
2525+
return -1;
2526+
}
2527+
char *buf = (char*)PyBytesWriter_GetData(writer);
2528+
memcpy(buf + pos, bytes, (size_t)size);
2529+
return 0;
2530+
}
2531+
2532+
static inline int
2533+
PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
2534+
{
2535+
va_list vargs;
2536+
va_start(vargs, format);
2537+
PyObject *str = PyBytes_FromFormatV(format, vargs);
2538+
va_end(vargs);
2539+
2540+
if (str == NULL) {
2541+
return -1;
2542+
}
2543+
int res = PyBytesWriter_WriteBytes(writer,
2544+
PyBytes_AS_STRING(str),
2545+
PyBytes_GET_SIZE(str));
2546+
Py_DECREF(str);
2547+
return res;
2548+
}
2549+
#endif // PY_VERSION_HEX < 0x030F00A1
2550+
2551+
22982552
#ifdef __cplusplus
22992553
}
23002554
#endif

0 commit comments

Comments
 (0)