Skip to content
Merged
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
16 changes: 16 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ Python 3.15

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

.. c:function:: PyBytesWriter* PyBytesWriter_Create(Py_ssize_t size)
.. c:function:: void* PyBytesWriter_GetData(PyBytesWriter *writer)
.. c:function:: Py_ssize_t PyBytesWriter_GetSize(PyBytesWriter *writer)
.. c:function:: PyObject* PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
.. c:function:: PyObject* PyBytesWriter_Finish(PyBytesWriter *writer)
.. c:function:: PyObject* PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
.. c:function:: void PyBytesWriter_Discard(PyBytesWriter *writer)
.. c:function:: int PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)
.. c:function:: int PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t size)
.. c:function:: void* PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer, Py_ssize_t size, void *buf)
.. c:function:: int PyBytesWriter_WriteBytes(PyBytesWriter *writer, const void *bytes, Py_ssize_t size)
.. c:function:: int PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)

See `PyBytesWriter documentation <https://docs.python.org/dev/c-api/bytes.html#pybyteswriter>`__.


Python 3.14
-----------

Expand Down
15 changes: 15 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
Changelog
=========

* 2025-11-18: Add PEP 782 functions:

* ``PyBytesWriter_Create()``
* ``PyBytesWriter_Discard()``
* ``PyBytesWriter_Finish()``
* ``PyBytesWriter_FinishWithPointer()``
* ``PyBytesWriter_FinishWithSize()``
* ``PyBytesWriter_Format()``
* ``PyBytesWriter_GetData()``
* ``PyBytesWriter_GetSize()``
* ``PyBytesWriter_Grow()``
* ``PyBytesWriter_GrowAndUpdatePointer()``
* ``PyBytesWriter_Resize()``
* ``PyBytesWriter_WriteBytes()``

* 2025-09-01: Add ``PyUnstable_Object_IsUniquelyReferenced()`` function.
* 2025-06-09: Add ``PyUnicodeWriter_WriteASCII()`` function.
* 2025-06-03: Add functions:
Expand Down
254 changes: 254 additions & 0 deletions pythoncapi_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -2295,6 +2295,260 @@ PySys_GetOptionalAttr(PyObject *name, PyObject **value)
#endif // PY_VERSION_HEX < 0x030F00A1


#if PY_VERSION_HEX < 0x030F00A1
typedef struct PyBytesWriter {
char small_buffer[256];
PyObject *obj;
Py_ssize_t size;
} PyBytesWriter;

static inline Py_ssize_t
_PyBytesWriter_GetAllocated(PyBytesWriter *writer)
{
if (writer->obj == NULL) {
return sizeof(writer->small_buffer);
}
else {
return PyBytes_GET_SIZE(writer->obj);
}
}


static inline int
_PyBytesWriter_Resize_impl(PyBytesWriter *writer, Py_ssize_t size,
int resize)
{
int overallocate = resize;
assert(size >= 0);

if (size <= _PyBytesWriter_GetAllocated(writer)) {
return 0;
}

if (overallocate) {
#ifdef MS_WINDOWS
/* On Windows, overallocate by 50% is the best factor */
if (size <= (PY_SSIZE_T_MAX - size / 2)) {
size += size / 2;
}
#else
/* On Linux, overallocate by 25% is the best factor */
if (size <= (PY_SSIZE_T_MAX - size / 4)) {
size += size / 4;
}
#endif
}

if (writer->obj != NULL) {
if (_PyBytes_Resize(&writer->obj, size)) {
return -1;
}
assert(writer->obj != NULL);
}
else {
writer->obj = PyBytes_FromStringAndSize(NULL, size);
if (writer->obj == NULL) {
return -1;
}

if (resize) {
assert((size_t)size > sizeof(writer->small_buffer));
memcpy(PyBytes_AS_STRING(writer->obj),
writer->small_buffer,
sizeof(writer->small_buffer));
}
}
return 0;
}

static inline void*
PyBytesWriter_GetData(PyBytesWriter *writer)
{
if (writer->obj == NULL) {
return writer->small_buffer;
}
else {
return PyBytes_AS_STRING(writer->obj);
}
}

static inline Py_ssize_t
PyBytesWriter_GetSize(PyBytesWriter *writer)
{
return writer->size;
}

static inline void
PyBytesWriter_Discard(PyBytesWriter *writer)
{
if (writer == NULL) {
return;
}

Py_XDECREF(writer->obj);
PyMem_Free(writer);
}

static inline PyBytesWriter*
PyBytesWriter_Create(Py_ssize_t size)
{
if (size < 0) {
PyErr_SetString(PyExc_ValueError, "size must be >= 0");
return NULL;
}

PyBytesWriter *writer = (PyBytesWriter*)PyMem_Malloc(sizeof(PyBytesWriter));
if (writer == NULL) {
PyErr_NoMemory();
return NULL;
}

writer->obj = NULL;
writer->size = 0;

if (size >= 1) {
if (_PyBytesWriter_Resize_impl(writer, size, 0) < 0) {
PyBytesWriter_Discard(writer);
return NULL;
}
writer->size = size;
}
return writer;
}

static inline PyObject*
PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
{
PyObject *result;
if (size == 0) {
result = PyBytes_FromStringAndSize("", 0);
}
else if (writer->obj != NULL) {
if (size != PyBytes_GET_SIZE(writer->obj)) {
if (_PyBytes_Resize(&writer->obj, size)) {
goto error;
}
}
result = writer->obj;
writer->obj = NULL;
}
else {
result = PyBytes_FromStringAndSize(writer->small_buffer, size);
}
PyBytesWriter_Discard(writer);
return result;

error:
PyBytesWriter_Discard(writer);
return NULL;
}

static inline PyObject*
PyBytesWriter_Finish(PyBytesWriter *writer)
{
return PyBytesWriter_FinishWithSize(writer, writer->size);
}

static inline PyObject*
PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
{
Py_ssize_t size = (char*)buf - (char*)PyBytesWriter_GetData(writer);
if (size < 0 || size > _PyBytesWriter_GetAllocated(writer)) {
PyBytesWriter_Discard(writer);
PyErr_SetString(PyExc_ValueError, "invalid end pointer");
return NULL;
}

return PyBytesWriter_FinishWithSize(writer, size);
}

static inline int
PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)
{
if (size < 0) {
PyErr_SetString(PyExc_ValueError, "size must be >= 0");
return -1;
}
if (_PyBytesWriter_Resize_impl(writer, size, 1) < 0) {
return -1;
}
writer->size = size;
return 0;
}

static inline int
PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t size)
{
if (size < 0 && writer->size + size < 0) {
PyErr_SetString(PyExc_ValueError, "invalid size");
return -1;
}
if (size > PY_SSIZE_T_MAX - writer->size) {
PyErr_NoMemory();
return -1;
}
size = writer->size + size;

if (_PyBytesWriter_Resize_impl(writer, size, 1) < 0) {
return -1;
}
writer->size = size;
return 0;
}

static inline void*
PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer,
Py_ssize_t size, void *buf)
{
Py_ssize_t pos = (char*)buf - (char*)PyBytesWriter_GetData(writer);
if (PyBytesWriter_Grow(writer, size) < 0) {
return NULL;
}
return (char*)PyBytesWriter_GetData(writer) + pos;
}

static inline int
PyBytesWriter_WriteBytes(PyBytesWriter *writer,
const void *bytes, Py_ssize_t size)
{
if (size < 0) {
size_t len = strlen((const char*)bytes);
if (len > (size_t)PY_SSIZE_T_MAX) {
PyErr_NoMemory();
return -1;
}
size = (Py_ssize_t)len;
}

Py_ssize_t pos = writer->size;
if (PyBytesWriter_Grow(writer, size) < 0) {
return -1;
}
char *buf = (char*)PyBytesWriter_GetData(writer);
memcpy(buf + pos, bytes, (size_t)size);
return 0;
}

static inline int
PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
{
va_list vargs;
va_start(vargs, format);
PyObject *str = PyBytes_FromFormatV(format, vargs);
va_end(vargs);

if (str == NULL) {
return -1;
}
int res = PyBytesWriter_WriteBytes(writer,
PyBytes_AS_STRING(str),
PyBytes_GET_SIZE(str));
Py_DECREF(str);
return res;
}
#endif // PY_VERSION_HEX < 0x030F00A1


#ifdef __cplusplus
}
#endif
Expand Down
Loading
Loading