From 39e2663e6acc0b68d5dd75bdaad0af33152552ae Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 6 Aug 2024 15:29:31 +0200 Subject: [PATCH 01/35] Add static inline to PyUnicodeWriter_WriteStr (#104) Add static inline to PyUnicodeWriter_WriteStr() PyUnicodeWriter_WriteRepr(). --- pythoncapi_compat.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index d45828f..4ff5c74 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1392,7 +1392,7 @@ PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch) return _PyUnicodeWriter_WriteChar((_PyUnicodeWriter*)writer, ch); } -int +static inline int PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) { PyObject *str = PyObject_Str(obj); @@ -1405,7 +1405,7 @@ PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) return res; } -int +static inline int PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) { PyObject *str = PyObject_Repr(obj); From 2d18aecd7b2f549d38a13e27b682ea4966f37bd8 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Mon, 26 Aug 2024 03:10:10 -0600 Subject: [PATCH 02/35] Add critical section API (#106) --- pythoncapi_compat.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 4ff5c74..c0feaa2 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1338,6 +1338,13 @@ PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, } #endif +#if PY_VERSION_HEX < 0x030D00B3 +# define Py_BEGIN_CRITICAL_SECTION(op) { +# define Py_END_CRITICAL_SECTION() } +# define Py_BEGIN_CRITICAL_SECTION2(a, b) { +# define Py_END_CRITICAL_SECTION2() } +#endif + #if PY_VERSION_HEX < 0x030E0000 && PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) typedef struct PyUnicodeWriter PyUnicodeWriter; From d20d7f815bb5504c806d4eb1fd43192f074fe268 Mon Sep 17 00:00:00 2001 From: Peter Hawkins Date: Mon, 23 Sep 2024 12:09:13 -0400 Subject: [PATCH 03/35] Fix incorrect use of assignment in place of an equality test. (#108) --- tests/test_pythoncapi_compat_cext.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index f813548..5cf0f45 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1662,7 +1662,7 @@ check_get_constant(PyObject* (*get_constant)(unsigned int), int borrowed) // Py_CONSTANT_FALSE obj = get_constant(Py_CONSTANT_FALSE); - assert(obj = Py_False); + assert(obj == Py_False); CLEAR(obj); // Py_CONSTANT_TRUE From bb0934e4f6cf5d2c5cae85ebc4784e906551a777 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 8 Oct 2024 16:36:28 +0200 Subject: [PATCH 04/35] Document PyUnicodeWriter API (#109) --- docs/api.rst | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index a75053e..9063cc5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -33,6 +33,51 @@ Python 3.14 See `PyLong_GetSign() documentation `__. +.. c:function:: PyUnicodeWriter* PyUnicodeWriter_Create(Py_ssize_t length) + + See `PyUnicodeWriter_Create() documentation `__. + +.. c:function:: PyObject* PyUnicodeWriter_Finish(PyUnicodeWriter *writer) + + See `PyUnicodeWriter_Finish() documentation `__. + +.. c:function:: void PyUnicodeWriter_Discard(PyUnicodeWriter *writer) + + See `PyUnicodeWriter_Discard() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch) + + See `PyUnicodeWriter_WriteChar() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer, const char *str, Py_ssize_t size) + + See `PyUnicodeWriter_WriteUTF8() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer, const wchar_t *str, Py_ssize_t size) + + See `PyUnicodeWriter_WriteWideChar() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) + + See `PyUnicodeWriter_WriteStr() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) + + See `PyUnicodeWriter_WriteRepr() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteSubstring(PyUnicodeWriter *writer, PyObject *str, Py_ssize_t start, Py_ssize_t end) + + See `PyUnicodeWriter_WriteSubstring() documentation `__. + +.. c:function:: int PyUnicodeWriter_Format(PyUnicodeWriter *writer, const char *format, ...) + + See `PyUnicodeWriter_Format() documentation `__. + +Not supported: + +* ``PyUnicodeWriter_DecodeUTF8Stateful()`` + + Python 3.13 ----------- From abc0f29fb9b245efa3d12ba1e6b35c104f1784f1 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 9 Oct 2024 10:47:33 +0200 Subject: [PATCH 05/35] Add PyUnicode_Equal() function (#110) --- docs/api.rst | 4 ++++ docs/changelog.rst | 1 + pythoncapi_compat.h | 32 +++++++++++++++++++++++++++++ tests/test_pythoncapi_compat_cext.c | 7 +++++++ 4 files changed, 44 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 9063cc5..72d78e9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -33,6 +33,10 @@ Python 3.14 See `PyLong_GetSign() documentation `__. +.. c:function:: int PyUnicode_Equal(PyObject *str1, PyObject *str2) + + See `PyUnicode_Equal() documentation `__. + .. c:function:: PyUnicodeWriter* PyUnicodeWriter_Create(Py_ssize_t length) See `PyUnicodeWriter_Create() documentation `__. diff --git a/docs/changelog.rst b/docs/changelog.rst index 1e7ba2a..5416a3d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* 2024-10-09: Add ``PyUnicode_Equal()`` function. * 2024-07-18: Add functions: * ``PyUnicodeWriter_Create()`` diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index c0feaa2..014d4cc 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1514,6 +1514,38 @@ static inline int PyLong_GetSign(PyObject *obj, int *sign) #endif +// gh-124502 added PyUnicode_Equal() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyUnicode_Equal(PyObject *str1, PyObject *str2) +{ + if (!PyUnicode_Check(str1)) { + PyErr_Format(PyExc_TypeError, + "first argument must be str, not %s", + Py_TYPE(str1)->tp_name); + return -1; + } + if (!PyUnicode_Check(str2)) { + PyErr_Format(PyExc_TypeError, + "second argument must be str, not %s", + Py_TYPE(str2)->tp_name); + return -1; + } + +#if PY_VERSION_HEX >= 0x030d0000 && !defined(PYPY_VERSION) + extern int _PyUnicode_Equal(PyObject *str1, PyObject *str2); + + return _PyUnicode_Equal(str1, str2); +#elif PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); +#elif PY_VERSION_HEX >= 0x03090000 && defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); +#else + return (PyUnicode_Compare(str1, str2) == 0); +#endif +} +#endif + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 5cf0f45..a2b07ed 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1531,6 +1531,13 @@ test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(PyErr_ExceptionMatches(PyExc_MemoryError)); PyErr_Clear(); + // Test PyUnicode_Equal() + assert(PyUnicode_Equal(abc, abc) == 1); + assert(PyUnicode_Equal(abc, abc0def) == 0); + assert(PyUnicode_Equal(abc, Py_True) == -1); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + Py_DECREF(abc); Py_DECREF(abc0def); Py_RETURN_NONE; From fba497767a2773426b52f75b979cf1de76041e7e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 9 Oct 2024 11:14:25 +0200 Subject: [PATCH 06/35] Add PyBytes_Join() function (#111) --- docs/api.rst | 4 ++++ docs/changelog.rst | 6 +++++- pythoncapi_compat.h | 9 +++++++++ tests/test_pythoncapi_compat_cext.c | 31 +++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 72d78e9..d817bc3 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -33,6 +33,10 @@ Python 3.14 See `PyLong_GetSign() documentation `__. +.. c:function:: PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable) + + See `PyBytes_Join() documentation `__. + .. c:function:: int PyUnicode_Equal(PyObject *str1, PyObject *str2) See `PyUnicode_Equal() documentation `__. diff --git a/docs/changelog.rst b/docs/changelog.rst index 5416a3d..18121ab 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,7 +1,11 @@ Changelog ========= -* 2024-10-09: Add ``PyUnicode_Equal()`` function. +* 2024-10-09: Add functions: + + * ``PyBytes_Join()`` + * ``PyUnicode_Equal()`` + * 2024-07-18: Add functions: * ``PyUnicodeWriter_Create()`` diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 014d4cc..02df115 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1546,6 +1546,15 @@ static inline int PyUnicode_Equal(PyObject *str1, PyObject *str2) #endif +// gh-121645 added PyBytes_Join() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable) +{ + return _PyBytes_Join(sep, iterable); +} +#endif + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index a2b07ed..e5f7d2f 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1881,6 +1881,36 @@ test_unicodewriter_format(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) #endif +static PyObject * +test_bytes(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + PyObject *abc = PyBytes_FromString("a b c"); + if (abc == NULL) { + return NULL; + } + PyObject *list = PyObject_CallMethod(abc, "split", NULL); + Py_DECREF(abc); + if (list == NULL) { + return NULL; + } + PyObject *sep = PyBytes_FromString("-"); + if (sep == NULL) { + Py_DECREF(list); + return NULL; + } + + PyObject *join = PyBytes_Join(sep, list); + assert(join != NULL); + assert(PyBytes_Check(join)); + assert(memcmp(PyBytes_AS_STRING(join), "a-b-c", 5) == 0); + Py_DECREF(join); + + Py_DECREF(list); + Py_DECREF(sep); + Py_RETURN_NONE; +} + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, @@ -1924,6 +1954,7 @@ static struct PyMethodDef methods[] = { {"test_unicodewriter_widechar", test_unicodewriter_widechar, METH_NOARGS, _Py_NULL}, {"test_unicodewriter_format", test_unicodewriter_format, METH_NOARGS, _Py_NULL}, #endif + {"test_bytes", test_bytes, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} }; From 3f1c06d12110a9f7ab9f83224282f0cb63fca429 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 9 Oct 2024 11:40:37 +0200 Subject: [PATCH 07/35] Add Py_HashBuffer() function (#112) --- docs/api.rst | 33 ++++++++++++++++++++++++++++- docs/changelog.rst | 1 + pythoncapi_compat.h | 21 ++++++++++++++++++ tests/test_pythoncapi_compat_cext.c | 15 +++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index d817bc3..d79eb87 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -10,7 +10,7 @@ Supported Python versions: * Python 3.6 - 3.14 * PyPy 2.7 and PyPy 3.6 - 3.10 -Python 2.7 and Python 3.4 are no longer officially supported since GitHub +Python 2.7 and Python 3.5 are no longer officially supported since GitHub Actions doesn't support them anymore: only best effort support is provided. C++03 and C++11 are supported on Python 3.6 and newer. @@ -37,6 +37,10 @@ Python 3.14 See `PyBytes_Join() documentation `__. +.. c:function:: Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len) + + See `Py_HashBuffer() documentation `__. + .. c:function:: int PyUnicode_Equal(PyObject *str1, PyObject *str2) See `PyUnicode_Equal() documentation `__. @@ -83,7 +87,34 @@ Python 3.14 Not supported: +* ``PyConfig_Get()`` +* ``PyConfig_GetInt()`` +* ``PyConfig_Names()`` +* ``PyConfig_Set()`` +* ``PyInitConfig_AddModule()`` +* ``PyInitConfig_Create()`` +* ``PyInitConfig_Free()`` +* ``PyInitConfig_FreeStrList()`` +* ``PyInitConfig_GetError()`` +* ``PyInitConfig_GetExitCode()`` +* ``PyInitConfig_GetInt()`` +* ``PyInitConfig_GetStr()`` +* ``PyInitConfig_GetStrList()`` +* ``PyInitConfig_HasOption()`` +* ``PyInitConfig_SetInt()`` +* ``PyInitConfig_SetStr()`` +* ``PyInitConfig_SetStrList()`` +* ``PyLong_AsInt32()`` +* ``PyLong_AsInt64()`` +* ``PyLong_AsUInt32()`` +* ``PyLong_AsUInt64()`` +* ``PyLong_FromInt32()`` +* ``PyLong_FromInt64()`` +* ``PyLong_FromUInt32()`` +* ``PyLong_FromUInt64()`` +* ``PyType_GetBaseByToken()`` * ``PyUnicodeWriter_DecodeUTF8Stateful()`` +* ``Py_InitializeFromInitConfig()`` Python 3.13 diff --git a/docs/changelog.rst b/docs/changelog.rst index 18121ab..157a08b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,7 @@ Changelog * ``PyBytes_Join()`` * ``PyUnicode_Equal()`` + * ``Py_HashBuffer()`` * 2024-07-18: Add functions: diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 02df115..dfd65a2 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1555,6 +1555,27 @@ static inline PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable) #endif +#if PY_VERSION_HEX < 0x030E00A0 +static inline Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len) +{ +#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) + extern Py_hash_t _Py_HashBytes(const void *src, Py_ssize_t len); + + return _Py_HashBytes(ptr, len); +#else + Py_hash_t hash; + PyObject *bytes = PyBytes_FromStringAndSize((const char*)ptr, len); + if (bytes == NULL) { + return -1; + } + hash = PyObject_Hash(bytes); + Py_DECREF(bytes); + return hash; +#endif +} +#endif + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index e5f7d2f..2d8db5c 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1618,6 +1618,20 @@ test_hash(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(imag != 0); #endif + // Test Py_HashBuffer() + { + PyObject *abc = PyBytes_FromString("abc"); + if (abc == NULL) { + return NULL; + } + Py_hash_t hash = Py_HashBuffer(PyBytes_AS_STRING(abc), + PyBytes_GET_SIZE(abc)); + Py_hash_t hash2 = PyObject_Hash(abc); + assert(hash == hash2); + + Py_DECREF(abc); + } + Py_RETURN_NONE; } @@ -1884,6 +1898,7 @@ test_unicodewriter_format(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) static PyObject * test_bytes(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) { + // Test PyBytes_Join() PyObject *abc = PyBytes_FromString("a b c"); if (abc == NULL) { return NULL; From 38e2d327a31bc2a599190528ba83341d4e048cf0 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 9 Oct 2024 12:00:16 +0200 Subject: [PATCH 08/35] Add PyIter_NextItem() function (#113) --- docs/api.rst | 4 +++ docs/changelog.rst | 1 + pythoncapi_compat.h | 37 ++++++++++++++++++++--- tests/test_pythoncapi_compat_cext.c | 46 +++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 4 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index d79eb87..ceb232f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -33,6 +33,10 @@ Python 3.14 See `PyLong_GetSign() documentation `__. +.. c:function:: PyObject* PyIter_NextItem(PyObject *sep, PyObject *iterable) + + See `PyIter_NextItem() documentation `__. + .. c:function:: PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable) See `PyBytes_Join() documentation `__. diff --git a/docs/changelog.rst b/docs/changelog.rst index 157a08b..856e5a9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,7 @@ Changelog * 2024-10-09: Add functions: * ``PyBytes_Join()`` + * ``PyIter_NextItem()`` * ``PyUnicode_Equal()`` * ``Py_HashBuffer()`` diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index dfd65a2..db1e8d2 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1519,14 +1519,12 @@ static inline int PyLong_GetSign(PyObject *obj, int *sign) static inline int PyUnicode_Equal(PyObject *str1, PyObject *str2) { if (!PyUnicode_Check(str1)) { - PyErr_Format(PyExc_TypeError, - "first argument must be str, not %s", + PyErr_Format(PyExc_TypeError, "first argument must be str, not %s", Py_TYPE(str1)->tp_name); return -1; } if (!PyUnicode_Check(str2)) { - PyErr_Format(PyExc_TypeError, - "second argument must be str, not %s", + PyErr_Format(PyExc_TypeError, "second argument must be str, not %s", Py_TYPE(str2)->tp_name); return -1; } @@ -1576,6 +1574,37 @@ static inline Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len) #endif +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyIter_NextItem(PyObject *iter, PyObject **item) +{ + iternextfunc tp_iternext; + + assert(iter != NULL); + assert(item != NULL); + + tp_iternext = Py_TYPE(iter)->tp_iternext; + if (tp_iternext == NULL) { + *item = NULL; + PyErr_Format(PyExc_TypeError, "expected an iterator, got '%s'", + Py_TYPE(iter)->tp_name); + return -1; + } + + if ((*item = tp_iternext(iter))) { + return 1; + } + if (!PyErr_Occurred()) { + return 0; + } + if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + PyErr_Clear(); + return 0; + } + return -1; +} +#endif + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 2d8db5c..6ef1873 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1437,12 +1437,14 @@ static int heapmanaged_traverse(PyObject *self, visitproc visit, void *arg) { Py_VISIT(Py_TYPE(self)); + // Test PyObject_VisitManagedDict() return PyObject_VisitManagedDict(self, visit, arg); } static int heapmanaged_clear(PyObject *self) { + // Test PyObject_ClearManagedDict() PyObject_ClearManagedDict(self); return 0; } @@ -1475,6 +1477,7 @@ static PyType_Spec HeapCTypeWithManagedDict_spec = { static PyObject * test_managed_dict(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) { + // Test PyObject_VisitManagedDict() and PyObject_ClearManagedDict() PyObject *type = PyType_FromSpec(&HeapCTypeWithManagedDict_spec); if (type == NULL) { return NULL; @@ -1926,6 +1929,48 @@ test_bytes(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } +static PyObject * +test_iter(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Test PyIter_NextItem() + PyObject *tuple = Py_BuildValue("(i)", 123); + if (tuple == NULL) { + return NULL; + } + PyObject *iter = PyObject_GetIter(tuple); + Py_DECREF(tuple); + if (iter == NULL) { + return NULL; + } + + // first item + PyObject *item = UNINITIALIZED_OBJ; + assert(PyIter_NextItem(iter, &item) == 1); + { + PyObject *expected = PyLong_FromLong(123); + assert(PyObject_RichCompareBool(item, expected, Py_EQ) == 1); + assert(expected != NULL); + Py_DECREF(expected); + } + + // StopIteration + item = UNINITIALIZED_OBJ; + assert(PyIter_NextItem(iter, &item) == 0); + assert(item == NULL); + assert(!PyErr_Occurred()); + + // non-iterable object + item = UNINITIALIZED_OBJ; + assert(PyIter_NextItem(Py_None, &item) == -1); + assert(item == NULL); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + Py_DECREF(iter); + Py_RETURN_NONE; +} + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, @@ -1970,6 +2015,7 @@ static struct PyMethodDef methods[] = { {"test_unicodewriter_format", test_unicodewriter_format, METH_NOARGS, _Py_NULL}, #endif {"test_bytes", test_bytes, METH_NOARGS, _Py_NULL}, + {"test_iter", test_iter, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} }; From 669c882624bcde615710747103dd912ccedc9ae5 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 9 Oct 2024 12:41:02 +0200 Subject: [PATCH 09/35] Add PyLong_FromUInt64() and PyLong_AsUInt64() (#114) --- docs/api.rst | 41 +++++++++++--- docs/changelog.rst | 8 +++ pythoncapi_compat.h | 85 +++++++++++++++++++++++++++++ tests/test_pythoncapi_compat_cext.c | 42 ++++++++++++++ 4 files changed, 168 insertions(+), 8 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index ceb232f..b94df42 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -89,6 +89,39 @@ Python 3.14 See `PyUnicodeWriter_Format() documentation `__. +.. c:function:: int PyLong_AsInt32(PyObject *obj, int32_t *pvalue) + + See `PyLong_AsInt32() documentation `__. + +.. c:function:: int PyLong_AsInt64(PyObject *obj, int64_t *pvalue) + + See `PyLong_AsInt64() documentation `__. + +.. c:function:: int PyLong_AsUInt32(PyObject *obj, uint32_t *pvalue) + + See `PyLong_AsUInt32() documentation `__. + +.. c:function:: int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) + + See `PyLong_AsUInt64() documentation `__. + +.. c:function:: PyObject* PyLong_FromInt32(int32_t value) + + See `PyLong_FromInt32() documentation `__. + +.. c:function:: PyObject* PyLong_FromInt64(int64_t value) + + See `PyLong_FromInt64() documentation `__. + +.. c:function:: PyObject* PyLong_FromUInt32(uint32_t value) + + See `PyLong_FromUInt32() documentation `__. + +.. c:function:: PyObject* PyLong_FromUInt64(uint64_t value) + + See `PyLong_FromUInt64() documentation `__. + + Not supported: * ``PyConfig_Get()`` @@ -108,14 +141,6 @@ Not supported: * ``PyInitConfig_SetInt()`` * ``PyInitConfig_SetStr()`` * ``PyInitConfig_SetStrList()`` -* ``PyLong_AsInt32()`` -* ``PyLong_AsInt64()`` -* ``PyLong_AsUInt32()`` -* ``PyLong_AsUInt64()`` -* ``PyLong_FromInt32()`` -* ``PyLong_FromInt64()`` -* ``PyLong_FromUInt32()`` -* ``PyLong_FromUInt64()`` * ``PyType_GetBaseByToken()`` * ``PyUnicodeWriter_DecodeUTF8Stateful()`` * ``Py_InitializeFromInitConfig()`` diff --git a/docs/changelog.rst b/docs/changelog.rst index 856e5a9..a2d188c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,14 @@ Changelog * ``PyBytes_Join()`` * ``PyIter_NextItem()`` + * ``PyLong_AsInt32()`` + * ``PyLong_AsInt64()`` + * ``PyLong_AsUInt32()`` + * ``PyLong_AsUInt64()`` + * ``PyLong_FromInt32()`` + * ``PyLong_FromInt64()`` + * ``PyLong_FromUInt32()`` + * ``PyLong_FromUInt64()`` * ``PyUnicode_Equal()`` * ``Py_HashBuffer()`` diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index db1e8d2..34a84c9 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -45,6 +45,13 @@ extern "C" { # define _PyObject_CAST(op) _Py_CAST(PyObject*, op) #endif +#ifndef Py_BUILD_ASSERT +# define Py_BUILD_ASSERT(cond) \ + do { \ + (void)sizeof(char [1 - 2 * !(cond)]); \ + } while(0) +#endif + // bpo-42262 added Py_NewRef() to Python 3.10.0a3 #if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_NewRef) @@ -1605,6 +1612,84 @@ static inline int PyIter_NextItem(PyObject *iter, PyObject **item) #endif +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject* PyLong_FromInt32(int32_t value) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + return PyLong_FromLong(value); +} + +static inline PyObject* PyLong_FromInt64(int64_t value) +{ + Py_BUILD_ASSERT(sizeof(long long) >= 8); + return PyLong_FromLongLong(value); +} + +static inline PyObject* PyLong_FromUInt32(uint32_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long) >= 4); + return PyLong_FromUnsignedLong(value); +} + +static inline PyObject* PyLong_FromUInt64(uint64_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long long) >= 8); + return PyLong_FromUnsignedLongLong(value); +} + +static inline int PyLong_AsInt32(PyObject *obj, int32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(int) == 4); + int value = PyLong_AsInt(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int32_t)value; + return 0; +} + +static inline int PyLong_AsInt64(PyObject *obj, int64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + long long value = PyLong_AsLongLong(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int64_t)value; + return 0; +} + +static inline int PyLong_AsUInt32(PyObject *obj, uint32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + unsigned long value = PyLong_AsUnsignedLong(obj); + if (value == (unsigned long)-1 && PyErr_Occurred()) { + return -1; + } +#if SIZEOF_LONG > 4 + if ((unsigned long)UINT32_MAX < value) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C uint32_t"); + return -1; + } +#endif + *pvalue = (uint32_t)value; + return 0; +} + +static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + unsigned long long value = PyLong_AsUnsignedLongLong(obj); + if (value == (unsigned long long)-1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (uint64_t)value; + return 0; +} +#endif + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 6ef1873..ecbed45 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1971,6 +1971,47 @@ test_iter(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } +static PyObject * +test_long_stdint(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + PyObject *obj; + + // Test PyLong_FromInt32() and PyLong_AsInt32() + obj = PyLong_FromInt32(INT32_C(-0x12345678)); + assert(obj != NULL); + int32_t i32; + assert(PyLong_AsInt32(obj, &i32) == 0); + assert(i32 == INT32_C(-0x12345678)); + Py_DECREF(obj); + + // Test PyLong_FromUInt32() and PyLong_AsUInt32() + obj = PyLong_FromUInt32(UINT32_C(0xDEADBEEF)); + assert(obj != NULL); + uint32_t u32; + assert(PyLong_AsUInt32(obj, &u32) == 0); + assert(u32 == UINT32_C(0xDEADBEEF)); + Py_DECREF(obj); + + // Test PyLong_FromInt64() and PyLong_AsInt64() + obj = PyLong_FromInt64(INT64_C(-0x12345678DEADBEEF)); + assert(obj != NULL); + int64_t i64; + assert(PyLong_AsInt64(obj, &i64) == 0); + assert(i64 == INT64_C(-0x12345678DEADBEEF)); + Py_DECREF(obj); + + // Test PyLong_FromUInt64() and PyLong_AsUInt64() + obj = PyLong_FromUInt64(UINT64_C(0xDEADBEEF12345678)); + assert(obj != NULL); + uint64_t u64; + assert(PyLong_AsUInt64(obj, &u64) == 0); + assert(u64 == UINT64_C(0xDEADBEEF12345678)); + Py_DECREF(obj); + + Py_RETURN_NONE; +} + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, @@ -2016,6 +2057,7 @@ static struct PyMethodDef methods[] = { #endif {"test_bytes", test_bytes, METH_NOARGS, _Py_NULL}, {"test_iter", test_iter, METH_NOARGS, _Py_NULL}, + {"test_long_stdint", test_long_stdint, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} }; From 74a7723fc16308f4cc3005b8b1056e47dd6ce8f0 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 14 Oct 2024 18:59:32 +0200 Subject: [PATCH 10/35] Fix GitHub Actions for latest Ubuntu (#116) --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6f43aa5..b749c60 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,6 @@ jobs: python: # Python versions (CPython): # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json - - "3.7" - "3.8" - "3.9" - "3.10" @@ -64,6 +63,8 @@ jobs: # GHA python-versions. - os: ubuntu-20.04 python: 3.6 + - os: ubuntu-22.04 + python: 3.7 steps: # https://github.com/actions/checkout From 6d8c17a2d547eca24b7b3bb17865588385efde4b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:40:27 +0200 Subject: [PATCH 11/35] Fix 'redefinition; different linkage' errors with cp313-win (#115) --- pythoncapi_compat.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 34a84c9..acaadf3 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1537,7 +1537,7 @@ static inline int PyUnicode_Equal(PyObject *str1, PyObject *str2) } #if PY_VERSION_HEX >= 0x030d0000 && !defined(PYPY_VERSION) - extern int _PyUnicode_Equal(PyObject *str1, PyObject *str2); + PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *str1, PyObject *str2); return _PyUnicode_Equal(str1, str2); #elif PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) @@ -1564,7 +1564,7 @@ static inline PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable) static inline Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len) { #if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) - extern Py_hash_t _Py_HashBytes(const void *src, Py_ssize_t len); + PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void *src, Py_ssize_t len); return _Py_HashBytes(ptr, len); #else From ec07618fff0b13c78eb4d5bb4f058b46659a0214 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 15 Oct 2024 14:08:12 +0200 Subject: [PATCH 12/35] Treat warnings as errors on Windows (MSVC) (#117) * Replace "CPP" with "CXX". * Test also C++14 on Windows. * GitHub Actions: upgrade Windows latest Python to 3.13. --- .github/workflows/build.yml | 2 +- tests/setup.py | 77 +++++++++++++++-------------- tests/test_pythoncapi_compat.py | 17 +++++-- tests/test_pythoncapi_compat_cext.c | 8 +-- 4 files changed, 59 insertions(+), 45 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b749c60..bdd3672 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,7 +53,7 @@ jobs: - os: windows-latest python: 3.6 - os: windows-latest - python: 3.12 + python: 3.13 # macOS: test only new Python - os: macos-latest diff --git a/tests/setup.py b/tests/setup.py index 2e10c15..825241e 100755 --- a/tests/setup.py +++ b/tests/setup.py @@ -13,38 +13,42 @@ # C++ is only supported on Python 3.6 and newer -TEST_CPP = (sys.version_info >= (3, 6)) +TEST_CXX = (sys.version_info >= (3, 6)) SRC_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), '..')) # Windows uses MSVC compiler MSVC = (os.name == "nt") -# C compiler flags for GCC and clang COMMON_FLAGS = [ - # Treat warnings as error - '-Werror', - # Enable all warnings - '-Wall', '-Wextra', - # Extra warnings - '-Wconversion', - # /usr/lib64/pypy3.7/include/pyport.h:68:20: error: redefinition of typedef - # 'Py_hash_t' is a C11 feature - "-Wno-typedef-redefinition", + '-I' + SRC_DIR, ] -CFLAGS = COMMON_FLAGS + [ - # Use C99 for pythoncapi_compat.c which initializes PyModuleDef with a - # mixture of designated and non-designated initializers - '-std=c99', -] -CPPFLAGS = list(COMMON_FLAGS) -# FIXME: _Py_CAST() emits C++ compilers on Python 3.12. -# See: https://github.com/python/cpython/issues/94731 -if 0: - CPPFLAGS.extend(( - '-Wold-style-cast', - '-Wzero-as-null-pointer-constant', +if not MSVC: + # C compiler flags for GCC and clang + COMMON_FLAGS.extend(( + # Treat warnings as error + '-Werror', + # Enable all warnings + '-Wall', '-Wextra', + # Extra warnings + '-Wconversion', + # /usr/lib64/pypy3.7/include/pyport.h:68:20: error: redefinition of typedef + # 'Py_hash_t' is a C11 feature + "-Wno-typedef-redefinition", + )) + CFLAGS = COMMON_FLAGS + [ + # Use C99 for pythoncapi_compat.c which initializes PyModuleDef with a + # mixture of designated and non-designated initializers + '-std=c99', + ] +else: + # C compiler flags for MSVC + COMMON_FLAGS.extend(( + # Treat all compiler warnings as compiler errors + '/WX', )) + CFLAGS = list(COMMON_FLAGS) +CXXFLAGS = list(COMMON_FLAGS) def main(): @@ -66,34 +70,31 @@ def main(): # CC env var overrides sysconfig CC variable in setuptools os.environ['CC'] = cmd - cflags = ['-I' + SRC_DIR] - cppflags = list(cflags) - if not MSVC: - cflags.extend(CFLAGS) - cppflags.extend(CPPFLAGS) - # C extension c_ext = Extension( 'test_pythoncapi_compat_cext', sources=['test_pythoncapi_compat_cext.c'], - extra_compile_args=cflags) + extra_compile_args=CFLAGS) extensions = [c_ext] - if TEST_CPP: + if TEST_CXX: # C++ extension # MSVC has /std flag but doesn't support /std:c++11 if not MSVC: versions = [ - ('test_pythoncapi_compat_cpp03ext', '-std=c++03'), - ('test_pythoncapi_compat_cpp11ext', '-std=c++11'), + ('test_pythoncapi_compat_cpp03ext', ['-std=c++03']), + ('test_pythoncapi_compat_cpp11ext', ['-std=c++11']), ] else: - versions = [('test_pythoncapi_compat_cppext', None)] - for name, flag in versions: - flags = list(cppflags) - if flag is not None: - flags.append(flag) + versions = [ + ('test_pythoncapi_compat_cppext', None), + ('test_pythoncapi_compat_cpp14ext', ['/std:c++14', '/Zc:__cplusplus']), + ] + for name, std_flags in versions: + flags = list(CXXFLAGS) + if std_flags is not None: + flags.extend(std_flags) cpp_ext = Extension( name, sources=['test_pythoncapi_compat_cppext.cpp'], diff --git a/tests/test_pythoncapi_compat.py b/tests/test_pythoncapi_compat.py index 17ade5a..8480415 100644 --- a/tests/test_pythoncapi_compat.py +++ b/tests/test_pythoncapi_compat.py @@ -24,12 +24,23 @@ from utils import run_command, command_stdout +# Windows uses MSVC compiler +MSVC = (os.name == "nt") + TESTS = [ ("test_pythoncapi_compat_cext", "C"), - ("test_pythoncapi_compat_cppext", "C++"), - ("test_pythoncapi_compat_cpp03ext", "C++03"), - ("test_pythoncapi_compat_cpp11ext", "C++11"), ] +if not MSVC: + TESTS.extend(( + ("test_pythoncapi_compat_cpp03ext", "C++03"), + ("test_pythoncapi_compat_cpp11ext", "C++11"), + )) +else: + TESTS.extend(( + ("test_pythoncapi_compat_cppext", "C++"), + ("test_pythoncapi_compat_cpp14ext", "C++14"), + )) + VERBOSE = False diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index ecbed45..419a5dc 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -15,12 +15,14 @@ # define PYTHON3 1 #endif -#if defined(_MSC_VER) && defined(__cplusplus) -# define MODULE_NAME test_pythoncapi_compat_cppext +#if defined(__cplusplus) && __cplusplus >= 201402 +# define MODULE_NAME test_pythoncapi_compat_cpp14ext #elif defined(__cplusplus) && __cplusplus >= 201103 # define MODULE_NAME test_pythoncapi_compat_cpp11ext -#elif defined(__cplusplus) +#elif defined(__cplusplus) && !defined(_MSC_VER) # define MODULE_NAME test_pythoncapi_compat_cpp03ext +#elif defined(__cplusplus) +# define MODULE_NAME test_pythoncapi_compat_cppext #else # define MODULE_NAME test_pythoncapi_compat_cext #endif From 0041177c4f348c8952b4c8980b2c90856e61c7c7 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 17 Oct 2024 11:50:17 +0200 Subject: [PATCH 13/35] Update link to latest version (#118) --- docs/api.rst | 2 +- pythoncapi_compat.h | 2 +- upgrade_pythoncapi.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index b94df42..68429cc 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -23,7 +23,7 @@ Some functions related to frame objects and ``PyThreadState`` are not available on PyPy. Latest version of the header file: -`pythoncapi_compat.h `_. +`pythoncapi_compat.h `_. Python 3.14 diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index acaadf3..ba1850e 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -7,7 +7,7 @@ // https://github.com/python/pythoncapi_compat // // Latest version: -// https://raw.githubusercontent.com/python/pythoncapi_compat/master/pythoncapi_compat.h +// https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h // // SPDX-License-Identifier: 0BSD diff --git a/upgrade_pythoncapi.py b/upgrade_pythoncapi.py index ea6e722..6640cec 100755 --- a/upgrade_pythoncapi.py +++ b/upgrade_pythoncapi.py @@ -10,7 +10,7 @@ PYTHONCAPI_COMPAT_URL = ('https://raw.githubusercontent.com/python/' - 'pythoncapi_compat/master/pythoncapi_compat.h') + 'pythoncapi-compat/main/pythoncapi_compat.h') PYTHONCAPI_COMPAT_H = 'pythoncapi_compat.h' INCLUDE_PYTHONCAPI_COMPAT = f'#include "{PYTHONCAPI_COMPAT_H}"' INCLUDE_PYTHONCAPI_COMPAT2 = f'#include <{PYTHONCAPI_COMPAT_H}>' From 77abeec5330ee0c97ab128066207e1b6f232bb56 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 12 Nov 2024 18:56:51 +0300 Subject: [PATCH 14/35] Add PyLong_IsPositive/Negative/Zero() functions (#119) Co-authored-by: Victor Stinner --- docs/api.rst | 12 ++++++++++++ docs/changelog.rst | 6 ++++++ pythoncapi_compat.h | 30 +++++++++++++++++++++++++++++ tests/test_pythoncapi_compat_cext.c | 5 +++++ 4 files changed, 53 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 68429cc..7c743f8 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -29,6 +29,18 @@ Latest version of the header file: Python 3.14 ----------- +.. c:function:: int PyLong_IsPositive(PyObject *obj) + + See `PyLong_IsPositive() documentation `__. + +.. c:function:: int PyLong_IsNegative(PyObject *obj) + + See `PyLong_IsNegative() documentation `__. + +.. c:function:: int PyLong_IsZero(PyObject *obj) + + See `PyLong_IsZero() documentation `__. + .. c:function:: int PyLong_GetSign(PyObject *obj, int *sign) See `PyLong_GetSign() documentation `__. diff --git a/docs/changelog.rst b/docs/changelog.rst index a2d188c..3a616f7 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,12 @@ Changelog ========= +* 2024-11-12: Add functions: + + * ``PyLong_IsPositive()`` + * ``PyLong_IsNegative()`` + * ``PyLong_IsZero()`` + * 2024-10-09: Add functions: * ``PyBytes_Join()`` diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index ba1850e..c51dd6b 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1520,6 +1520,36 @@ static inline int PyLong_GetSign(PyObject *obj, int *sign) } #endif +// gh-126061 added PyLong_IsPositive/Negative/Zero() to Python in 3.14.0a2 +#if PY_VERSION_HEX < 0x030E00A2 +static inline int PyLong_IsPositive(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == 1; +} + +static inline int PyLong_IsNegative(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == -1; +} + +static inline int PyLong_IsZero(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == 0; +} +#endif + // gh-124502 added PyUnicode_Equal() to Python 3.14.0a0 #if PY_VERSION_HEX < 0x030E00A0 diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 419a5dc..b8df8a6 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1421,6 +1421,11 @@ test_long_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(PyLong_GetSign(obj, &sign) == 0); assert(sign == 1); + // test PyLong_IsPositive(), PyLong_IsNegative() and PyLong_IsZero() + assert(PyLong_IsPositive(obj) == 1); + assert(PyLong_IsNegative(obj) == 0); + assert(PyLong_IsZero(obj) == 0); + Py_RETURN_NONE; } From 03e441d5e0bf005b481cb61821d58e400cc1c293 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 15 Nov 2024 11:49:39 +0300 Subject: [PATCH 15/35] Relax PYPY_VERSION_NUM requirements for hash macros (#122) Those added in https://github.com/pypy/pypy/commit/5661dff36f1120e458374954ca2353b87b951089 which is available in PyPy 7.3.8+. --- pythoncapi_compat.h | 4 ++-- tests/test_pythoncapi_compat_cext.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index c51dd6b..7c76858 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1204,11 +1204,11 @@ static inline int PyTime_PerfCounter(PyTime_t *result) #endif // gh-111389 added hash constants to Python 3.13.0a5. These constants were -// added first as private macros to Python 3.4.0b1 and PyPy 7.3.9. +// added first as private macros to Python 3.4.0b1 and PyPy 7.3.8. #if (!defined(PyHASH_BITS) \ && ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \ || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \ - && PYPY_VERSION_NUM >= 0x07090000))) + && PYPY_VERSION_NUM >= 0x07030800))) # define PyHASH_BITS _PyHASH_BITS # define PyHASH_MODULUS _PyHASH_MODULUS # define PyHASH_INF _PyHASH_INF diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index b8df8a6..c413689 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1616,7 +1616,7 @@ test_hash(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) #if ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \ || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \ - && PYPY_VERSION_NUM >= 0x07090000)) + && PYPY_VERSION_NUM >= 0x07030800)) // Just check that constants are available size_t bits = PyHASH_BITS; assert(bits >= 8); From 0f1d42a10a3f594ad48894912396df31b2c2d55d Mon Sep 17 00:00:00 2001 From: Matti Picus Date: Tue, 19 Nov 2024 10:09:30 +0200 Subject: [PATCH 16/35] prepare for pypy3.11 release (#123) --- pythoncapi_compat.h | 6 +++--- runtests.py | 1 - tests/test_pythoncapi_compat_cext.c | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 7c76858..2218b1b 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -287,7 +287,7 @@ PyFrame_GetVarString(PyFrameObject *frame, const char *name) // bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5 -#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) +#if PY_VERSION_HEX < 0x030900A5 || (defined(PYPY_VERSION) && PY_VERSION_HEX < 0x030B0000) static inline PyInterpreterState * PyThreadState_GetInterpreter(PyThreadState *tstate) { @@ -918,7 +918,7 @@ static inline int PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg) { PyObject **dict = _PyObject_GetDictPtr(obj); - if (*dict == NULL) { + if (dict == NULL || *dict == NULL) { return -1; } Py_VISIT(*dict); @@ -929,7 +929,7 @@ static inline void PyObject_ClearManagedDict(PyObject *obj) { PyObject **dict = _PyObject_GetDictPtr(obj); - if (*dict == NULL) { + if (dict == NULL || *dict == NULL) { return; } Py_CLEAR(*dict); diff --git a/runtests.py b/runtests.py index 5064550..2e92dba 100755 --- a/runtests.py +++ b/runtests.py @@ -13,7 +13,6 @@ import argparse import os.path import shutil -import subprocess import sys try: from shutil import which diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index c413689..4f369f7 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -758,7 +758,7 @@ test_import(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) static void gc_collect(void) { -#if defined(PYPY_VERSION) +#if defined(PYPY_VERSION) && PY_VERSION_HEX < 0x030B0000 PyObject *mod = PyImport_ImportModule("gc"); assert(mod != _Py_NULL); @@ -1432,8 +1432,8 @@ test_long_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) // --- HeapCTypeWithManagedDict -------------------------------------------- -// Py_TPFLAGS_MANAGED_DICT was added to Python 3.11.0a3 -#if PY_VERSION_HEX >= 0x030B00A3 +// Py_TPFLAGS_MANAGED_DICT was added to Python 3.11.0a3 but is not implemented on PyPy +#if PY_VERSION_HEX >= 0x030B00A3 && ! defined(PYPY_VERSION) # define TEST_MANAGED_DICT typedef struct { From 900c130f9cdc6ec3ae19a76410c76d39bdc3b958 Mon Sep 17 00:00:00 2001 From: Dan Yeaw Date: Fri, 29 Nov 2024 04:36:26 -0500 Subject: [PATCH 17/35] Add PyGObject as a user (#124) --- docs/users.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/users.rst b/docs/users.rst index b90466f..abf4307 100644 --- a/docs/users.rst +++ b/docs/users.rst @@ -45,7 +45,10 @@ Examples of projects using pythoncapi_compat.h `__) * `PyTorch `_ (`pythoncapi_compat.h copy `__) - +* `PyGObject `_ + (`commit `__, + `pythoncapi-compat Meson subproject + `__) Projects not using pythoncapi_compat.h ====================================== From 61709bfa512f66842fbc70bac5fb3279d0bdba7b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 13 Dec 2024 17:47:15 +0300 Subject: [PATCH 18/35] Add PyLong Import/Export API (#121) PyPy is not supported, as well as Python 2. In the later case it's possible, but hardly worth code complications: most real-word potential consumers (e.g. Sage, gmpy2 or python-flint) support only Python 3. Co-authored-by: Victor Stinner --- docs/api.rst | 36 ++++++ docs/changelog.rst | 12 ++ pythoncapi_compat.h | 179 ++++++++++++++++++++++++++++ tests/test_pythoncapi_compat_cext.c | 44 +++++++ 4 files changed, 271 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 7c743f8..07b303a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -29,6 +29,42 @@ Latest version of the header file: Python 3.14 ----------- +.. c:struct:: PyLongLayout + + See `PyLongLayout documentation `__. + +.. c:function:: const PyLongLayout* PyLong_GetNativeLayout(void) + + See `PyLong_GetNativeLayout() documentation `__. + +.. c:struct:: PyLongExport + + See `PyLongExport documentation `__. + +.. c:function:: int PyLong_Export(PyObject *obj, PyLongExport *export_long) + + See `PyLong_Export() documentation `__. + +.. c:function:: void PyLong_FreeExport(PyLongExport *export_long) + + See `PyLong_FreeExport() documentation `__. + +.. c:struct:: PyLongWriter + + See `PyLongWriter documentation `__. + +.. c:function:: PyLongWriter* PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits) + + See `PyLongWriter_Create() documentation `__. + +.. c:function:: PyObject* PyLongWriter_Finish(PyLongWriter *writer) + + See `PyLongWriter_Finish() documentation `__. + +.. c:function:: void PyLongWriter_Discard(PyLongWriter *writer) + + See `PyLongWriter_Discard() documentation `__. + .. c:function:: int PyLong_IsPositive(PyObject *obj) See `PyLong_IsPositive() documentation `__. diff --git a/docs/changelog.rst b/docs/changelog.rst index 3a616f7..ec263a3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,18 @@ Changelog ========= +* 2024-12-13: Add functions and structs: + + * ``PyLongLayout`` + * ``PyLong_GetNativeLayout()`` + * ``PyLongExport`` + * ``PyLong_Export()`` + * ``PyLong_FreeExport()`` + * ``PyLongWriter`` + * ``PyLongWriter_Create()`` + * ``PyLongWriter_Finish()`` + * ``PyLongWriter_Discard()`` + * 2024-11-12: Add functions: * ``PyLong_IsPositive()`` diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 2218b1b..5e22e7d 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1720,6 +1720,185 @@ static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) #endif +// gh-102471 added import and export API for integers to 3.14.0a2. +#if PY_VERSION_HEX < 0x030E00A2 && PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +// Helpers to access PyLongObject internals. +static inline void +_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) +{ +#if PY_VERSION_HEX >= 0x030C0000 + op->long_value.lv_tag = (uintptr_t)(1 - sign) | ((uintptr_t)(size) << 3); +#elif PY_VERSION_HEX >= 0x030900A4 + Py_SET_SIZE(op, sign * size); +#else + Py_SIZE(op) = sign * size; +#endif +} + +static inline Py_ssize_t +_PyLong_DigitCount(const PyLongObject *op) +{ +#if PY_VERSION_HEX >= 0x030C0000 + return (Py_ssize_t)(op->long_value.lv_tag >> 3); +#else + return _PyLong_Sign((PyObject*)op) < 0 ? -Py_SIZE(op) : Py_SIZE(op); +#endif +} + +static inline digit* +_PyLong_GetDigits(const PyLongObject *op) +{ +#if PY_VERSION_HEX >= 0x030C0000 + return (digit*)(op->long_value.ob_digit); +#else + return (digit*)(op->ob_digit); +#endif +} + +typedef struct PyLongLayout { + uint8_t bits_per_digit; + uint8_t digit_size; + int8_t digits_order; + int8_t digit_endianness; +} PyLongLayout; + +typedef struct PyLongExport { + int64_t value; + uint8_t negative; + Py_ssize_t ndigits; + const void *digits; + Py_uintptr_t _reserved; +} PyLongExport; + +typedef struct PyLongWriter PyLongWriter; + +static inline const PyLongLayout* +PyLong_GetNativeLayout(void) +{ + static const PyLongLayout PyLong_LAYOUT = { + PyLong_SHIFT, + sizeof(digit), + -1, // least significant first + PY_LITTLE_ENDIAN ? -1 : 1, + }; + + return &PyLong_LAYOUT; +} + +static inline int +PyLong_Export(PyObject *obj, PyLongExport *export_long) +{ + if (!PyLong_Check(obj)) { + memset(export_long, 0, sizeof(*export_long)); + PyErr_Format(PyExc_TypeError, "expected int, got %s", + Py_TYPE(obj)->tp_name); + return -1; + } + + // Fast-path: try to convert to a int64_t + PyLongObject *self = (PyLongObject*)obj; + int overflow; +#if SIZEOF_LONG == 8 + long value = PyLong_AsLongAndOverflow(obj, &overflow); +#else + // Windows has 32-bit long, so use 64-bit long long instead + long long value = PyLong_AsLongLongAndOverflow(obj, &overflow); +#endif + Py_BUILD_ASSERT(sizeof(value) == sizeof(int64_t)); + // the function cannot fail since obj is a PyLongObject + assert(!(value == -1 && PyErr_Occurred())); + + if (!overflow) { + export_long->value = value; + export_long->negative = 0; + export_long->ndigits = 0; + export_long->digits = 0; + export_long->_reserved = 0; + } + else { + export_long->value = 0; + export_long->negative = _PyLong_Sign(obj) < 0; + export_long->ndigits = _PyLong_DigitCount(self); + if (export_long->ndigits == 0) { + export_long->ndigits = 1; + } + export_long->digits = _PyLong_GetDigits(self); + export_long->_reserved = (Py_uintptr_t)Py_NewRef(obj); + } + return 0; +} + +static inline void +PyLong_FreeExport(PyLongExport *export_long) +{ + PyObject *obj = (PyObject*)export_long->_reserved; + + if (obj) { + export_long->_reserved = 0; + Py_DECREF(obj); + } +} + +static inline PyLongWriter* +PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits) +{ + if (ndigits <= 0) { + PyErr_SetString(PyExc_ValueError, "ndigits must be positive"); + return NULL; + } + assert(digits != NULL); + + PyLongObject *obj = _PyLong_New(ndigits); + if (obj == NULL) { + return NULL; + } + _PyLong_SetSignAndDigitCount(obj, negative?-1:1, ndigits); + + *digits = _PyLong_GetDigits(obj); + return (PyLongWriter*)obj; +} + +static inline void +PyLongWriter_Discard(PyLongWriter *writer) +{ + PyLongObject *obj = (PyLongObject *)writer; + + assert(Py_REFCNT(obj) == 1); + Py_DECREF(obj); +} + +static inline PyObject* +PyLongWriter_Finish(PyLongWriter *writer) +{ + PyObject *obj = (PyObject *)writer; + PyLongObject *self = (PyLongObject*)obj; + Py_ssize_t j = _PyLong_DigitCount(self); + Py_ssize_t i = j; + int sign = _PyLong_Sign(obj); + + assert(Py_REFCNT(obj) == 1); + + // Normalize and get singleton if possible + while (i > 0 && _PyLong_GetDigits(self)[i-1] == 0) { + --i; + } + if (i != j) { + if (i == 0) { + sign = 0; + } + _PyLong_SetSignAndDigitCount(self, sign, i); + } + if (i <= 1) { + long val = sign * (long)(_PyLong_GetDigits(self)[0]); + Py_DECREF(obj); + return PyLong_FromLong(val); + } + + return obj; +} +#endif + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 4f369f7..28663d2 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1426,6 +1426,50 @@ test_long_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(PyLong_IsNegative(obj) == 0); assert(PyLong_IsZero(obj) == 0); +#if defined(PYTHON3) && !defined(PYPY_VERSION) + // test import/export API + digit *digits; + PyLongWriter *writer; + static PyLongExport long_export; + + writer = PyLongWriter_Create(1, 1, (void**)&digits); + PyLongWriter_Discard(writer); + + writer = PyLongWriter_Create(1, 1, (void**)&digits); + digits[0] = 123; + obj = PyLongWriter_Finish(writer); + + check_int(obj, -123); + PyLong_Export(obj, &long_export); + assert(long_export.value == -123); + assert(long_export.digits == NULL); + PyLong_FreeExport(&long_export); + Py_DECREF(obj); + + writer = PyLongWriter_Create(0, 5, (void**)&digits); + digits[0] = 1; + digits[1] = 0; + digits[2] = 0; + digits[3] = 0; + digits[4] = 1; + obj = PyLongWriter_Finish(writer); + + PyLong_Export(obj, &long_export); + assert(long_export.value == 0); + digits = (digit*)long_export.digits; + assert(digits[0] == 1); + assert(digits[1] == 0); + assert(digits[2] == 0); + assert(digits[3] == 0); + assert(digits[4] == 1); + PyLong_FreeExport(&long_export); + Py_DECREF(obj); + + const PyLongLayout *layout = PyLong_GetNativeLayout(); + assert(layout->digits_order == -1); + assert(layout->digit_size == sizeof(digit)); +#endif // defined(PYTHON3) && !defined(PYPY_VERSION) + Py_RETURN_NONE; } From 7eb512b67cf3b4449c72bdfba04af24cb1503514 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 16 Dec 2024 14:41:38 +0100 Subject: [PATCH 19/35] Add structmember.h constants (#126) --- docs/changelog.rst | 26 +++++++++++++++++++++ pythoncapi_compat.h | 34 ++++++++++++++++++++++++++++ tests/test_pythoncapi_compat_cext.c | 35 +++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index ec263a3..3500bd5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,32 @@ Changelog ========= +* 2024-12-16: Add ``structmember.h`` constants: + + * ``Py_T_BOOL`` + * ``Py_T_BYTE`` + * ``Py_T_CHAR`` + * ``Py_T_DOUBLE`` + * ``Py_T_FLOAT`` + * ``Py_T_INT`` + * ``Py_T_LONGLONG`` + * ``Py_T_LONG`` + * ``Py_T_OBJECT_EX`` + * ``Py_T_PYSSIZET`` + * ``Py_T_SHORT`` + * ``Py_T_STRING_INPLACE`` + * ``Py_T_STRING`` + * ``Py_T_UBYTE`` + * ``Py_T_UINT`` + * ``Py_T_ULONGLONG`` + * ``Py_T_ULONG`` + * ``Py_T_USHORT`` + * ``_Py_T_NONE`` + * ``_Py_T_OBJECT`` + * ``Py_AUDIT_READ`` + * ``Py_READONLY`` + * ``_Py_WRITE_RESTRICTED`` + * 2024-12-13: Add functions and structs: * ``PyLongLayout`` diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 5e22e7d..cee282d 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -24,6 +24,9 @@ extern "C" { #if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION) # include "frameobject.h" // PyFrameObject, PyFrame_GetBack() #endif +#if PY_VERSION_HEX < 0x030C00A3 +# include // T_SHORT, READONLY +#endif #ifndef _Py_CAST @@ -1899,6 +1902,37 @@ PyLongWriter_Finish(PyLongWriter *writer) #endif +#if PY_VERSION_HEX < 0x030C00A3 +# define Py_T_SHORT T_SHORT +# define Py_T_INT T_INT +# define Py_T_LONG T_LONG +# define Py_T_FLOAT T_FLOAT +# define Py_T_DOUBLE T_DOUBLE +# define Py_T_STRING T_STRING +# define _Py_T_OBJECT T_OBJECT +# define Py_T_CHAR T_CHAR +# define Py_T_BYTE T_BYTE +# define Py_T_UBYTE T_UBYTE +# define Py_T_USHORT T_USHORT +# define Py_T_UINT T_UINT +# define Py_T_ULONG T_ULONG +# define Py_T_STRING_INPLACE T_STRING_INPLACE +# define Py_T_BOOL T_BOOL +# define Py_T_OBJECT_EX T_OBJECT_EX +# define Py_T_LONGLONG T_LONGLONG +# define Py_T_ULONGLONG T_ULONGLONG +# define Py_T_PYSSIZET T_PYSSIZET + +# if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +# define _Py_T_NONE T_NONE +# endif + +# define Py_READONLY READONLY +# define Py_AUDIT_READ READ_RESTRICTED +# define _Py_WRITE_RESTRICTED PY_WRITE_RESTRICTED +#endif + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 28663d2..aff19e0 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -2063,6 +2063,40 @@ test_long_stdint(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } +static PyObject * +test_structmember(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + assert(Py_T_SHORT >= 0); + assert(Py_T_INT >= 0); + assert(Py_T_LONG >= 0); + assert(Py_T_FLOAT >= 0); + assert(Py_T_DOUBLE >= 0); + assert(Py_T_STRING >= 0); + assert(_Py_T_OBJECT >= 0); + assert(Py_T_CHAR >= 0); + assert(Py_T_BYTE >= 0); + assert(Py_T_UBYTE >= 0); + assert(Py_T_USHORT >= 0); + assert(Py_T_UINT >= 0); + assert(Py_T_ULONG >= 0); + assert(Py_T_STRING_INPLACE >= 0); + assert(Py_T_BOOL >= 0); + assert(Py_T_OBJECT_EX >= 0); + assert(Py_T_LONGLONG >= 0); + assert(Py_T_ULONGLONG >= 0); + assert(Py_T_PYSSIZET >= 0); +#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) + assert(_Py_T_NONE >= 0); +#endif + + assert(Py_READONLY >= 0); + assert(Py_AUDIT_READ >= 0); + assert(_Py_WRITE_RESTRICTED >= 0); + + Py_RETURN_NONE; +} + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, @@ -2109,6 +2143,7 @@ static struct PyMethodDef methods[] = { {"test_bytes", test_bytes, METH_NOARGS, _Py_NULL}, {"test_iter", test_iter, METH_NOARGS, _Py_NULL}, {"test_long_stdint", test_long_stdint, METH_NOARGS, _Py_NULL}, + {"test_structmember", test_structmember, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} }; From 79404e9b5bab9d40d71f18562e20df03fc69e7cb Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 6 Jan 2025 14:39:36 +0100 Subject: [PATCH 20/35] Add Py_fopen() and Py_fclose() (#127) --- docs/api.rst | 8 ++++++ docs/changelog.rst | 1 + pythoncapi_compat.h | 41 +++++++++++++++++++++++++++++ tests/test_pythoncapi_compat_cext.c | 16 +++++++++++ 4 files changed, 66 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 07b303a..a85a61d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -169,6 +169,14 @@ Python 3.14 See `PyLong_FromUInt64() documentation `__. +.. c:function:: FILE* Py_fopen(PyObject *path, const char *mode) + + See `Py_fopen() documentation `__. + +.. c:function:: int Py_fclose(FILE *file) + + See `Py_fclose() documentation `__. + Not supported: diff --git a/docs/changelog.rst b/docs/changelog.rst index 3500bd5..04a63b2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* 2025-01-06: Add ``Py_fopen()`` and ``Py_fclose()`` functions. * 2024-12-16: Add ``structmember.h`` constants: * ``Py_T_BOOL`` diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index cee282d..0ea8e80 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1933,6 +1933,47 @@ PyLongWriter_Finish(PyLongWriter *writer) #endif +// gh-127350 added Py_fopen() and Py_fclose() to Python 3.14a4 +#if PY_VERSION_HEX < 0x030E00A4 +static inline FILE* Py_fopen(PyObject *path, const char *mode) +{ +#if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION) + extern FILE* _Py_fopen_obj(PyObject *path, const char *mode); + return _Py_fopen_obj(path, mode); +#else + FILE *f; + PyObject *bytes; +#if PY_VERSION_HEX >= 0x03000000 + if (!PyUnicode_FSConverter(path, &bytes)) { + return NULL; + } +#else + if (!PyString_Check(path)) { + PyErr_SetString(PyExc_TypeError, "except str"); + return NULL; + } + bytes = Py_NewRef(path); +#endif + const char *path_bytes = PyBytes_AS_STRING(bytes); + + f = fopen(path_bytes, mode); + Py_DECREF(bytes); + + if (f == NULL) { + PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path); + return NULL; + } + return f; +#endif +} + +int Py_fclose(FILE *file) +{ + return fclose(file); +} +#endif + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index aff19e0..f1557bc 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -2097,6 +2097,21 @@ test_structmember(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } +static PyObject * +test_file(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + const char *filename = __FILE__; + PyObject *path = create_string(filename); + + FILE *fp = Py_fopen(path, "rb"); + Py_DECREF(path); + assert(fp != NULL); + Py_fclose(fp); + + Py_RETURN_NONE; +} + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, @@ -2144,6 +2159,7 @@ static struct PyMethodDef methods[] = { {"test_iter", test_iter, METH_NOARGS, _Py_NULL}, {"test_long_stdint", test_long_stdint, METH_NOARGS, _Py_NULL}, {"test_structmember", test_structmember, METH_NOARGS, _Py_NULL}, + {"test_file", test_file, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} }; From b1b2071331f0d88d7661ca9c0ad19b81282071bc Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 19 Jan 2025 22:42:27 +0100 Subject: [PATCH 21/35] Optimize PyWeakref_GetRef() (#129) --- pythoncapi_compat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 0ea8e80..c533b82 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -583,7 +583,7 @@ static inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) return 0; } *pobj = Py_NewRef(obj); - return (*pobj != NULL); + return 1; } #endif From 8a52253179b91d0345b1d68ffeb518fce577b8f3 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 19 Jan 2025 22:46:25 +0100 Subject: [PATCH 22/35] Add PyConfig_Get() (#128) --- docs/api.rst | 10 +- docs/changelog.rst | 1 + pythoncapi_compat.h | 224 ++++++++++++++++++++++++++++ tests/test_pythoncapi_compat_cext.c | 63 ++++++++ 4 files changed, 296 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index a85a61d..f2dd262 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -177,11 +177,17 @@ Python 3.14 See `Py_fclose() documentation `__. +.. c:function:: PyObject* PyConfig_Get(const char *name) + + See `PyConfig_Get() documentation `__. + +.. c:function:: int PyConfig_GetInt(const char *name, int *value) + + See `PyConfig_GetInt() documentation `__. + Not supported: -* ``PyConfig_Get()`` -* ``PyConfig_GetInt()`` * ``PyConfig_Names()`` * ``PyConfig_Set()`` * ``PyInitConfig_AddModule()`` diff --git a/docs/changelog.rst b/docs/changelog.rst index 04a63b2..74e046b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* 2025-01-19: Add ``PyConfig_Get()`` functions. * 2025-01-06: Add ``Py_fopen()`` and ``Py_fclose()`` functions. * 2024-12-16: Add ``structmember.h`` constants: diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index c533b82..8c1822d 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -19,6 +19,7 @@ extern "C" { #endif #include +#include // offsetof() // Python 3.11.0b4 added PyFrame_Back() to Python.h #if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION) @@ -1974,6 +1975,229 @@ int Py_fclose(FILE *file) #endif +#if 0x03090000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030E0000 && !defined(PYPY_VERSION) +static inline PyObject* +PyConfig_Get(const char *name) +{ + typedef enum { + _PyConfig_MEMBER_INT, + _PyConfig_MEMBER_UINT, + _PyConfig_MEMBER_ULONG, + _PyConfig_MEMBER_BOOL, + _PyConfig_MEMBER_WSTR, + _PyConfig_MEMBER_WSTR_OPT, + _PyConfig_MEMBER_WSTR_LIST, + } PyConfigMemberType; + + typedef struct { + const char *name; + size_t offset; + PyConfigMemberType type; + const char *sys_attr; + } PyConfigSpec; + +#define PYTHONCAPI_COMPAT_SPEC(MEMBER, TYPE, sys_attr) \ + {#MEMBER, offsetof(PyConfig, MEMBER), \ + _PyConfig_MEMBER_##TYPE, sys_attr} + + static const PyConfigSpec config_spec[] = { + PYTHONCAPI_COMPAT_SPEC(argv, WSTR_LIST, "argv"), + PYTHONCAPI_COMPAT_SPEC(base_exec_prefix, WSTR_OPT, "base_exec_prefix"), + PYTHONCAPI_COMPAT_SPEC(base_executable, WSTR_OPT, "_base_executable"), + PYTHONCAPI_COMPAT_SPEC(base_prefix, WSTR_OPT, "base_prefix"), + PYTHONCAPI_COMPAT_SPEC(bytes_warning, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(exec_prefix, WSTR_OPT, "exec_prefix"), + PYTHONCAPI_COMPAT_SPEC(executable, WSTR_OPT, "executable"), + PYTHONCAPI_COMPAT_SPEC(inspect, BOOL, _Py_NULL), +#if 0x030C0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(int_max_str_digits, UINT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(interactive, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(module_search_paths, WSTR_LIST, "path"), + PYTHONCAPI_COMPAT_SPEC(optimization_level, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(parser_debug, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(platlibdir, WSTR, "platlibdir"), + PYTHONCAPI_COMPAT_SPEC(prefix, WSTR_OPT, "prefix"), + PYTHONCAPI_COMPAT_SPEC(pycache_prefix, WSTR_OPT, "pycache_prefix"), + PYTHONCAPI_COMPAT_SPEC(quiet, BOOL, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(stdlib_dir, WSTR_OPT, "_stdlib_dir"), +#endif + PYTHONCAPI_COMPAT_SPEC(use_environment, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(verbose, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(warnoptions, WSTR_LIST, "warnoptions"), + PYTHONCAPI_COMPAT_SPEC(write_bytecode, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(xoptions, WSTR_LIST, "_xoptions"), + PYTHONCAPI_COMPAT_SPEC(buffered_stdio, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(check_hash_pycs_mode, WSTR, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(code_debug_ranges, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(configure_c_stdio, BOOL, _Py_NULL), +#if 0x030D0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(cpu_count, INT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(dev_mode, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(dump_refs, BOOL, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(dump_refs_file, WSTR_OPT, _Py_NULL), +#endif +#ifdef Py_GIL_DISABLED + PYTHONCAPI_COMPAT_SPEC(enable_gil, INT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(faulthandler, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(filesystem_encoding, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(filesystem_errors, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(hash_seed, ULONG, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(home, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(import_time, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(install_signal_handlers, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(isolated, BOOL, _Py_NULL), +#ifdef MS_WINDOWS + PYTHONCAPI_COMPAT_SPEC(legacy_windows_stdio, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(malloc_stats, BOOL, _Py_NULL), +#if 0x030A0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(orig_argv, WSTR_LIST, "orig_argv"), +#endif + PYTHONCAPI_COMPAT_SPEC(parse_argv, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(pathconfig_warnings, BOOL, _Py_NULL), +#if 0x030C0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(perf_profiling, UINT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(program_name, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_command, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_filename, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_module, WSTR_OPT, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(safe_path, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(show_ref_count, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(site_import, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(skip_source_first_line, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(stdio_encoding, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(stdio_errors, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(tracemalloc, UINT, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(use_frozen_modules, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(use_hash_seed, BOOL, _Py_NULL), +#if 0x030D0000 <= PY_VERSION_HEX && defined(__APPLE__) + PYTHONCAPI_COMPAT_SPEC(use_system_logger, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(user_site_directory, BOOL, _Py_NULL), +#if 0x030A0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(warn_default_encoding, BOOL, _Py_NULL), +#endif + }; + +#undef PYTHONCAPI_COMPAT_SPEC + + const PyConfigSpec *spec; + int found = 0; + for (size_t i=0; i < Py_ARRAY_LENGTH(config_spec); i++) { + spec = &config_spec[i]; + if (strcmp(spec->name, name) == 0) { + found = 1; + break; + } + } + if (found) { + if (spec->sys_attr != NULL) { + PyObject *value = PySys_GetObject(spec->sys_attr); + if (value == NULL) { + PyErr_Format(PyExc_RuntimeError, "lost sys.%s", spec->sys_attr); + return NULL; + } + return Py_NewRef(value); + } + + extern const PyConfig* _Py_GetConfig(void); + const PyConfig *config = _Py_GetConfig(); + void *member = (char *)config + spec->offset; + switch (spec->type) { + case _PyConfig_MEMBER_INT: + case _PyConfig_MEMBER_UINT: + { + int value = *(int *)member; + return PyLong_FromLong(value); + } + case _PyConfig_MEMBER_BOOL: + { + int value = *(int *)member; + return PyBool_FromLong(value != 0); + } + case _PyConfig_MEMBER_ULONG: + { + unsigned long value = *(unsigned long *)member; + return PyLong_FromUnsignedLong(value); + } + case _PyConfig_MEMBER_WSTR: + case _PyConfig_MEMBER_WSTR_OPT: + { + wchar_t *wstr = *(wchar_t **)member; + if (wstr != NULL) { + return PyUnicode_FromWideChar(wstr, -1); + } + else { + return Py_NewRef(Py_None); + } + } + case _PyConfig_MEMBER_WSTR_LIST: + { + const PyWideStringList *list = (const PyWideStringList *)member; + PyObject *tuple = PyTuple_New(list->length); + if (tuple == NULL) { + return NULL; + } + + for (Py_ssize_t i = 0; i < list->length; i++) { + PyObject *item = PyUnicode_FromWideChar(list->items[i], -1); + if (item == NULL) { + Py_DECREF(tuple); + return NULL; + } + PyTuple_SET_ITEM(tuple, i, item); + } + return tuple; + } + default: + Py_UNREACHABLE(); + } + } + + PyErr_Format(PyExc_ValueError, "unknown config option name: %s", name); + return NULL; +} + +static inline int +PyConfig_GetInt(const char *name, int *value) +{ + PyObject *obj = PyConfig_Get(name); + if (obj == NULL) { + return -1; + } + + if (!PyLong_Check(obj)) { + Py_DECREF(obj); + PyErr_Format(PyExc_TypeError, "config option %s is not an int", name); + return -1; + } + + int as_int = PyLong_AsInt(obj); + Py_DECREF(obj); + if (as_int == -1 && PyErr_Occurred()) { + PyErr_Format(PyExc_OverflowError, + "config option %s value does not fit into a C int", name); + return -1; + } + + *value = as_int; + return 0; +} +#endif // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION) + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index f1557bc..bae0ee3 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -2112,6 +2112,66 @@ test_file(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } +#if 0x03090000 <= PY_VERSION_HEX && !defined(PYPY_VERSION) +static PyObject * +test_config(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Test PyConfig_Get() + PyObject *sys = PyImport_ImportModule("sys"); + if (sys == _Py_NULL) { + return _Py_NULL; + } + + PyObject *obj = PyConfig_Get("argv"); + PyObject *sys_attr = PyObject_GetAttrString(sys, "argv"); + assert(obj == sys_attr); + Py_DECREF(obj); + Py_DECREF(sys_attr); + + obj = PyConfig_Get("module_search_paths"); + sys_attr = PyObject_GetAttrString(sys, "path"); + assert(obj == sys_attr); + Py_DECREF(obj); + Py_DECREF(sys_attr); + + obj = PyConfig_Get("xoptions"); + sys_attr = PyObject_GetAttrString(sys, "_xoptions"); + assert(obj == sys_attr); + Py_DECREF(obj); + Py_DECREF(sys_attr); + + obj = PyConfig_Get("use_environment"); + assert(PyBool_Check(obj)); + Py_DECREF(obj); + + obj = PyConfig_Get("verbose"); + assert(PyLong_Check(obj)); + Py_DECREF(obj); + + assert(PyConfig_Get("nonexistent") == NULL); + assert(PyErr_ExceptionMatches(PyExc_ValueError)); + PyErr_Clear(); + + // Test PyConfig_GetInt() + int value = -3; + + assert(PyConfig_GetInt("verbose", &value) == 0); + assert(value >= 0); + + assert(PyConfig_GetInt("argv", &value) == -1); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + assert(PyConfig_GetInt("nonexistent", &value) == -1); + assert(PyErr_ExceptionMatches(PyExc_ValueError)); + PyErr_Clear(); + + Py_DECREF(sys); + Py_RETURN_NONE; +} +#endif + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, @@ -2160,6 +2220,9 @@ static struct PyMethodDef methods[] = { {"test_long_stdint", test_long_stdint, METH_NOARGS, _Py_NULL}, {"test_structmember", test_structmember, METH_NOARGS, _Py_NULL}, {"test_file", test_file, METH_NOARGS, _Py_NULL}, +#if 0x03090000 <= PY_VERSION_HEX && !defined(PYPY_VERSION) + {"test_config", test_config, METH_NOARGS, _Py_NULL}, +#endif {_Py_NULL, _Py_NULL, 0, _Py_NULL} }; From 03043e3bb23e81094f17cecd1eb58a0a893f05a1 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 26 Jan 2025 12:45:01 +0100 Subject: [PATCH 23/35] GHA: Test more Python versions on macOS/Windows (#132) Remove PyConfig.use_system_logger: it will only be available on Python 3.13.2 which is not released yet. --- .github/workflows/build.yml | 37 ++++++++++++++++++++++++++++++------- pythoncapi_compat.h | 3 --- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bdd3672..dd726af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,22 +49,45 @@ jobs: - "pypy3.7-v7.3.2" include: - # Windows: test old and new Python + # Windows - os: windows-latest - python: 3.6 + python: "3.6" - os: windows-latest - python: 3.13 + python: "3.7" + - os: windows-latest + python: "3.8" + - os: windows-latest + python: "3.9" + - os: windows-latest + python: "3.10" + - os: windows-latest + python: "3.11" + - os: windows-latest + python: "3.12" + - os: windows-latest + python: "3.13" - # macOS: test only new Python + # macOS + # Python 3.8 is the oldest version available on macOS/arm64. + - os: macos-latest + python: "3.8" + - os: macos-latest + python: "3.9" + - os: macos-latest + python: "3.10" + - os: macos-latest + python: "3.11" + - os: macos-latest + python: "3.12" - os: macos-latest - python: 3.12 + python: "3.13" # Ubuntu: test deadsnakes Python versions which are not supported by # GHA python-versions. - os: ubuntu-20.04 - python: 3.6 + python: "3.6" - os: ubuntu-22.04 - python: 3.7 + python: "3.7" steps: # https://github.com/actions/checkout diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 8c1822d..52f36cc 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -2082,9 +2082,6 @@ PyConfig_Get(const char *name) PYTHONCAPI_COMPAT_SPEC(use_frozen_modules, BOOL, _Py_NULL), #endif PYTHONCAPI_COMPAT_SPEC(use_hash_seed, BOOL, _Py_NULL), -#if 0x030D0000 <= PY_VERSION_HEX && defined(__APPLE__) - PYTHONCAPI_COMPAT_SPEC(use_system_logger, BOOL, _Py_NULL), -#endif PYTHONCAPI_COMPAT_SPEC(user_site_directory, BOOL, _Py_NULL), #if 0x030A0000 <= PY_VERSION_HEX PYTHONCAPI_COMPAT_SPEC(warn_default_encoding, BOOL, _Py_NULL), From d24c2016f1d418a954369adac029fc30058ddbe4 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 26 Jan 2025 12:50:10 +0100 Subject: [PATCH 24/35] Fix multiple definitions for Py_fclose (#130) --- pythoncapi_compat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 52f36cc..8cf63f5 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1968,7 +1968,7 @@ static inline FILE* Py_fopen(PyObject *path, const char *mode) #endif } -int Py_fclose(FILE *file) +static inline int Py_fclose(FILE *file) { return fclose(file); } From d341dac942a419a6d5ef17b06eced51e0c9921fd Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 26 Jan 2025 12:54:05 +0100 Subject: [PATCH 25/35] GHA: Test Python 3.14 (#133) --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dd726af..3164e9e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,9 +26,10 @@ jobs: - "3.10" - "3.11" - "3.12" - # CPython 3.13 final is scheduled for October 2024: - # https://peps.python.org/pep-0719/ - "3.13" + # CPython 3.14 final is scheduled for October 2025: + # https://peps.python.org/pep-0719/ + - "3.14" # PyPy versions: # - https://github.com/actions/setup-python/blob/main/docs/advanced-usage.md From 4c5370162377493e25da47e32f84a3df5b397804 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 26 Jan 2025 13:08:13 +0100 Subject: [PATCH 26/35] Avoid Py_ARRAY_LENGTH() (#134) Replace Py_ARRAY_LENGTH(array) with sizeof(array)/sizeof(array[0]). Py_ARRAY_LENGTH() fails with C++ on Python 3.9 on macOS. --- pythoncapi_compat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 8cf63f5..4d28846 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -2092,7 +2092,7 @@ PyConfig_Get(const char *name) const PyConfigSpec *spec; int found = 0; - for (size_t i=0; i < Py_ARRAY_LENGTH(config_spec); i++) { + for (size_t i=0; i < sizeof(config_spec) / sizeof(config_spec[0]); i++) { spec = &config_spec[i]; if (strcmp(spec->name, name) == 0) { found = 1; From 81eefa76fa1483f68cf489940715d42fadeb484e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 26 Jan 2025 13:16:23 +0100 Subject: [PATCH 27/35] test_config: get the last PyConfig member (#135) --- tests/test_pythoncapi_compat_cext.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index bae0ee3..9d324a4 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -2148,6 +2148,15 @@ test_config(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(PyLong_Check(obj)); Py_DECREF(obj); + // Get the last member +#if 0x030A0000 <= PY_VERSION_HEX + obj = PyConfig_Get("warn_default_encoding"); +#else + obj = PyConfig_Get("user_site_directory"); +#endif + assert(PyLong_Check(obj)); + Py_DECREF(obj); + assert(PyConfig_Get("nonexistent") == NULL); assert(PyErr_ExceptionMatches(PyExc_ValueError)); PyErr_Clear(); From c84545f0e1e21757d4901f75c47333d25a3fcff0 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 28 Jan 2025 14:18:38 +0100 Subject: [PATCH 28/35] Closes #136: Replace extern with PyAPI_FUNC() (#137) --- pythoncapi_compat.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 4d28846..e534c1c 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1939,7 +1939,8 @@ PyLongWriter_Finish(PyLongWriter *writer) static inline FILE* Py_fopen(PyObject *path, const char *mode) { #if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION) - extern FILE* _Py_fopen_obj(PyObject *path, const char *mode); + PyAPI_FUNC(FILE*) _Py_fopen_obj(PyObject *path, const char *mode); + return _Py_fopen_obj(path, mode); #else FILE *f; @@ -2109,7 +2110,8 @@ PyConfig_Get(const char *name) return Py_NewRef(value); } - extern const PyConfig* _Py_GetConfig(void); + PyAPI_FUNC(const PyConfig*) _Py_GetConfig(void); + const PyConfig *config = _Py_GetConfig(); void *member = (char *)config + spec->offset; switch (spec->type) { From 632d1aa0c4be6c67498d6b97630ddd7d7eb0f90a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 18 Feb 2025 11:05:49 +0100 Subject: [PATCH 29/35] Don't redefine _Py_NULL macro if already defined (#138) Original mypy fix by Michael R. Crusoe. --- pythoncapi_compat.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index e534c1c..4b179e4 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -37,11 +37,13 @@ extern "C" { // Static inline functions should use _Py_NULL rather than using directly NULL // to prevent C++ compiler warnings. On C23 and newer and on C++11 and newer, // _Py_NULL is defined as nullptr. -#if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \ - || (defined(__cplusplus) && __cplusplus >= 201103) -# define _Py_NULL nullptr -#else -# define _Py_NULL NULL +#ifndef _Py_NULL +# if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \ + || (defined(__cplusplus) && __cplusplus >= 201103) +# define _Py_NULL nullptr +# else +# define _Py_NULL NULL +# endif #endif // Cast argument to PyObject* type. From 3082742a3b0fdd795281b06525534f9e56f9e407 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 31 Mar 2025 20:20:44 +0200 Subject: [PATCH 30/35] Test PyPy 3.11 (#140) --- .github/workflows/build.yml | 1 + runtests.py | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3164e9e..ae2bf42 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,6 +42,7 @@ jobs: - "pypy3.8" - "pypy3.9" - "pypy3.10" + - "pypy3.11" # Old PyPy versions # See https://foss.heptapod.net/pypy/pypy/-/issues/3991 diff --git a/runtests.py b/runtests.py index 2e92dba..c858516 100755 --- a/runtests.py +++ b/runtests.py @@ -52,6 +52,7 @@ "pypy3.8", "pypy3.9", "pypy3.10", + "pypy3.11", ) From 0a8b2c56331a31d7f7096faaa1c1c26467b51c15 Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Mon, 14 Apr 2025 18:52:50 +0200 Subject: [PATCH 31/35] upgrade_pythoncapi: try to preserve newlines when patching (#141) In case we use \n on Windows or \r\n on Linux we don't want patching those files change every line due to newlines being adjusted to the platform defaults. Instead pass 'newline=""' to all open() calls to preserve newlines. And when adding new lines use the first type of newline found in the file. The test changes make the patching code reuseable and adds a second test with multiple different newlines in the input. --- tests/test_upgrade_pythoncapi.py | 77 +++++++++++++++++++------------- upgrade_pythoncapi.py | 12 +++-- 2 files changed, 55 insertions(+), 34 deletions(-) diff --git a/tests/test_upgrade_pythoncapi.py b/tests/test_upgrade_pythoncapi.py index bf6f648..48bad12 100644 --- a/tests/test_upgrade_pythoncapi.py +++ b/tests/test_upgrade_pythoncapi.py @@ -43,33 +43,12 @@ def reformat(source): class Tests(unittest.TestCase): maxDiff = 80 * 30 - def _test_patch_file(self, tmp_dir): - # test Patcher.patcher() - source = """ - PyTypeObject* - test_type(PyObject *obj, PyTypeObject *type) - { - Py_TYPE(obj) = type; - return Py_TYPE(obj); - } - """ - expected = """ - #include "pythoncapi_compat.h" - - PyTypeObject* - test_type(PyObject *obj, PyTypeObject *type) - { - Py_SET_TYPE(obj, type); - return Py_TYPE(obj); - } - """ - source = reformat(source) - expected = reformat(expected) - + def _patch_file(self, source, tmp_dir=None): + # test Patcher.patcher() filename = tempfile.mktemp(suffix='.c', dir=tmp_dir) old_filename = filename + ".old" try: - with open(filename, "w", encoding="utf-8") as fp: + with open(filename, "w", encoding="utf-8", newline="") as fp: fp.write(source) old_stderr = sys.stderr @@ -93,10 +72,10 @@ def _test_patch_file(self, tmp_dir): sys.stderr = old_stderr sys.argv = old_argv - with open(filename, encoding="utf-8") as fp: + with open(filename, encoding="utf-8", newline="") as fp: new_contents = fp.read() - with open(old_filename, encoding="utf-8") as fp: + with open(old_filename, encoding="utf-8", newline="") as fp: old_contents = fp.read() finally: try: @@ -108,15 +87,53 @@ def _test_patch_file(self, tmp_dir): except FileNotFoundError: pass - self.assertEqual(new_contents, expected) self.assertEqual(old_contents, source) + return new_contents def test_patch_file(self): - self._test_patch_file(None) + source = """ + PyTypeObject* + test_type(PyObject *obj, PyTypeObject *type) + { + Py_TYPE(obj) = type; + return Py_TYPE(obj); + } + """ + expected = """ + #include "pythoncapi_compat.h" + + PyTypeObject* + test_type(PyObject *obj, PyTypeObject *type) + { + Py_SET_TYPE(obj, type); + return Py_TYPE(obj); + } + """ + source = reformat(source) + expected = reformat(expected) + + new_contents = self._patch_file(source) + self.assertEqual(new_contents, expected) - def test_patch_directory(self): with tempfile.TemporaryDirectory() as tmp_dir: - self._test_patch_file(tmp_dir) + new_contents = self._patch_file(source, tmp_dir) + self.assertEqual(new_contents, expected) + + def test_patch_file_preserve_newlines(self): + source = """ + Py_ssize_t get_size(PyVarObject *obj)\r\n\ + \n\ + { return obj->ob_size; }\r\ + """ + expected = """ + Py_ssize_t get_size(PyVarObject *obj)\r\n\ + \n\ + { return Py_SIZE(obj); }\r\ + """ + source = reformat(source) + expected = reformat(expected) + new_contents = self._patch_file(source) + self.assertEqual(new_contents, expected) def check_replace(self, source, expected, **kwargs): source = reformat(source) diff --git a/upgrade_pythoncapi.py b/upgrade_pythoncapi.py index 6640cec..68b5c53 100755 --- a/upgrade_pythoncapi.py +++ b/upgrade_pythoncapi.py @@ -554,13 +554,17 @@ def _get_operations(self, parser): return operations def add_line(self, content, line): - line = line + '\n' + # Use the first matching newline + match = re.search(r'(?:\r\n|\n|\r)', content) + newline = match.group(0) if match else '\n' + + line = line + newline # FIXME: tolerate trailing spaces if line not in content: # FIXME: add macro after the first header comment # FIXME: add macro after includes # FIXME: add macro after: #define PY_SSIZE_T_CLEAN - return line + '\n' + content + return line + newline + content else: return content @@ -601,7 +605,7 @@ def patch_file(self, filename): encoding = "utf-8" errors = "surrogateescape" - with open(filename, encoding=encoding, errors=errors) as fp: + with open(filename, encoding=encoding, errors=errors, newline="") as fp: old_contents = fp.read() new_contents, operations = self._patch(old_contents) @@ -620,7 +624,7 @@ def patch_file(self, filename): # If old_filename already exists, replace it os.replace(filename, old_filename) - with open(filename, "w", encoding=encoding, errors=errors) as fp: + with open(filename, "w", encoding=encoding, errors=errors, newline="") as fp: fp.write(new_contents) self.applied_operations |= set(operations) From ecf3cd40f004fe0f63cca6f80ac4eae330c4605b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 3 Jun 2025 16:55:52 +0200 Subject: [PATCH 32/35] GHA: Get rid of Ubuntu 20.04 (#144) GHA no longer supports Ubuntu 20.04. Remove also Python 3.8 on macOS. --- .github/workflows/build.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae2bf42..72d91db 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -70,9 +70,7 @@ jobs: python: "3.13" # macOS - # Python 3.8 is the oldest version available on macOS/arm64. - - os: macos-latest - python: "3.8" + # Python 3.9 is the oldest version available on macOS/arm64. - os: macos-latest python: "3.9" - os: macos-latest @@ -86,8 +84,6 @@ jobs: # Ubuntu: test deadsnakes Python versions which are not supported by # GHA python-versions. - - os: ubuntu-20.04 - python: "3.6" - os: ubuntu-22.04 python: "3.7" From fde4d3457d7c1e6f7d09afeae5afd2218fbb2cae Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 3 Jun 2025 16:59:23 +0200 Subject: [PATCH 33/35] Add PySys_GetAttr() function (#143) --- docs/api.rst | 19 ++++++++ docs/changelog.rst | 7 +++ pythoncapi_compat.h | 65 ++++++++++++++++++++++++++ tests/test_pythoncapi_compat_cext.c | 72 +++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index f2dd262..609edbd 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -26,6 +26,25 @@ Latest version of the header file: `pythoncapi_compat.h `_. +Python 3.15 +----------- + +.. c:function:: PyObject* PySys_GetAttr(const char *name) + + See `PySys_GetAttr() documentation `__. + +.. c:function:: PyObject* PySys_GetAttrString(const char *name) + + See `PySys_GetAttrString() documentation `__. + +.. c:function:: PyObject* PySys_GetOptionalAttr(const char *name) + + See `PySys_GetOptionalAttr() documentation `__. + +.. c:function:: PyObject* PySys_GetOptionalAttrString(const char *name) + + See `PySys_GetOptionalAttrString() documentation `__. + Python 3.14 ----------- diff --git a/docs/changelog.rst b/docs/changelog.rst index 74e046b..bb981c4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,13 @@ Changelog ========= +* 2025-06-03: Add functions: + + * ``PySys_GetAttr()`` + * ``PySys_GetAttrString()`` + * ``PySys_GetOptionalAttr()`` + * ``PySys_GetOptionalAttrString()`` + * 2025-01-19: Add ``PyConfig_Get()`` functions. * 2025-01-06: Add ``Py_fopen()`` and ``Py_fclose()`` functions. * 2024-12-16: Add ``structmember.h`` constants: diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 4b179e4..eb84307 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -2199,6 +2199,71 @@ PyConfig_GetInt(const char *name, int *value) #endif // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION) +#if PY_VERSION_HEX < 0x030F0000 +static inline PyObject* +PySys_GetAttrString(const char *name) +{ +#if PY_VERSION_HEX >= 0x03000000 + PyObject *value = Py_XNewRef(PySys_GetObject(name)); +#else + PyObject *value = Py_XNewRef(PySys_GetObject((char*)name)); +#endif + if (value != NULL) { + return value; + } + if (!PyErr_Occurred()) { + PyErr_Format(PyExc_RuntimeError, "lost sys.%s", name); + } + return NULL; +} + +static inline PyObject* +PySys_GetAttr(PyObject *name) +{ +#if PY_VERSION_HEX >= 0x03000000 + const char *name_str = PyUnicode_AsUTF8(name); +#else + const char *name_str = PyString_AsString(name); +#endif + if (name_str == NULL) { + return NULL; + } + + return PySys_GetAttrString(name_str); +} + +static inline int +PySys_GetOptionalAttrString(const char *name, PyObject **value) +{ +#if PY_VERSION_HEX >= 0x03000000 + *value = Py_XNewRef(PySys_GetObject(name)); +#else + *value = Py_XNewRef(PySys_GetObject((char*)name)); +#endif + if (*value != NULL) { + return 1; + } + return 0; +} + +static inline int +PySys_GetOptionalAttr(PyObject *name, PyObject **value) +{ +#if PY_VERSION_HEX >= 0x03000000 + const char *name_str = PyUnicode_AsUTF8(name); +#else + const char *name_str = PyString_AsString(name); +#endif + if (name_str == NULL) { + *value = NULL; + return -1; + } + + return PySys_GetOptionalAttrString(name_str, value); +} +#endif // PY_VERSION_HEX < 0x030F00A1 + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 9d324a4..82cf387 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -2181,6 +2181,77 @@ test_config(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) #endif +static PyObject * +test_sys(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + const char *stdout_str = "stdout"; + PyObject *stdout_obj = create_string(stdout_str); +#if PYTHON3 + PyObject *sys_stdout = PySys_GetObject(stdout_str); // borrowed ref +#else + PyObject *sys_stdout = PySys_GetObject((char*)stdout_str); // borrowed ref +#endif + const char *nonexistent_str = "nonexistent"; + PyObject *nonexistent_obj = create_string(nonexistent_str); + PyObject *error_obj = PyLong_FromLong(1); + PyObject *value; + + // get sys.stdout + value = PySys_GetAttr(stdout_obj); + assert(value == sys_stdout); + Py_DECREF(value); + + value = PySys_GetAttrString(stdout_str); + assert(value == sys_stdout); + Py_DECREF(value); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttr(stdout_obj, &value) == 1); + assert(value == sys_stdout); + Py_DECREF(value); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttrString(stdout_str, &value) == 1); + assert(value == sys_stdout); + Py_DECREF(value); + + // non existent attribute + value = PySys_GetAttr(nonexistent_obj); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_RuntimeError)); + PyErr_Clear(); + + value = PySys_GetAttrString(nonexistent_str); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_RuntimeError)); + PyErr_Clear(); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttr(nonexistent_obj, &value) == 0); + assert(value == NULL); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttrString(nonexistent_str, &value) == 0); + assert(value == NULL); + + // invalid attribute type + value = PySys_GetAttr(error_obj); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttr(error_obj, &value) == -1); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + Py_DECREF(stdout_obj); + Py_DECREF(nonexistent_obj); + Py_RETURN_NONE; +} + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, @@ -2232,6 +2303,7 @@ static struct PyMethodDef methods[] = { #if 0x03090000 <= PY_VERSION_HEX && !defined(PYPY_VERSION) {"test_config", test_config, METH_NOARGS, _Py_NULL}, #endif + {"test_sys", test_sys, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} }; From ffae0ffa2a3f906fd471e2d672731205f7e5febd Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 9 Jun 2025 14:50:50 +0200 Subject: [PATCH 34/35] Add PyUnicodeWriter_WriteASCII() (#145) --- docs/api.rst | 4 ++++ docs/changelog.rst | 1 + pythoncapi_compat.h | 12 ++++++++++++ tests/test_pythoncapi_compat_cext.c | 7 ++++++- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 609edbd..6efae15 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -136,6 +136,10 @@ Python 3.14 See `PyUnicodeWriter_WriteUTF8() documentation `__. +.. c:function:: int PyUnicodeWriter_WriteASCII(PyUnicodeWriter *writer, const char *str, Py_ssize_t size) + + See `PyUnicodeWriter_WriteASCII() documentation `__. + .. c:function:: int PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer, const wchar_t *str, Py_ssize_t size) See `PyUnicodeWriter_WriteWideChar() documentation `__. diff --git a/docs/changelog.rst b/docs/changelog.rst index bb981c4..b8e3a86 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* 2025-06-09: Add ``PyUnicodeWriter_WriteASCII()`` function. * 2025-06-03: Add functions: * ``PySys_GetAttr()`` diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index eb84307..20646af 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1456,6 +1456,18 @@ PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer, return res; } +static inline int +PyUnicodeWriter_WriteASCII(PyUnicodeWriter *writer, + const char *str, Py_ssize_t size) +{ + if (size < 0) { + size = (Py_ssize_t)strlen(str); + } + + return _PyUnicodeWriter_WriteASCIIString((_PyUnicodeWriter*)writer, + str, size); +} + static inline int PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer, const wchar_t *str, Py_ssize_t size) diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 82cf387..6e76316 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1847,6 +1847,11 @@ test_unicodewriter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) goto error; } + // test PyUnicodeWriter_WriteASCII() + if (PyUnicodeWriter_WriteASCII(writer, " non-ASCII", -1) < 0) { + goto error; + } + // test PyUnicodeWriter_WriteUTF8() if (PyUnicodeWriter_WriteUTF8(writer, " valu\xC3\xA9", -1) < 0) { goto error; @@ -1870,7 +1875,7 @@ test_unicodewriter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) if (result == NULL) { return NULL; } - assert(PyUnicode_EqualToUTF8(result, "var=long valu\xC3\xA9 'repr'")); + assert(PyUnicode_EqualToUTF8(result, "var=long non-ASCII valu\xC3\xA9 'repr'")); Py_DECREF(result); } From b541b98df1e3e5aabb5def27422a75c876f5a88a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 24 Jun 2025 11:40:12 +0200 Subject: [PATCH 35/35] Avoid %T format in error message (#146) The %T format was added to Python 3.13 (PEP 737). --- pythoncapi_compat.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 20646af..3320f68 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1491,7 +1491,8 @@ PyUnicodeWriter_WriteSubstring(PyUnicodeWriter *writer, PyObject *str, Py_ssize_t start, Py_ssize_t end) { if (!PyUnicode_Check(str)) { - PyErr_Format(PyExc_TypeError, "expect str, not %T", str); + PyErr_Format(PyExc_TypeError, "expect str, not %s", + Py_TYPE(str)->tp_name); return -1; } if (start < 0 || start > end) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy