diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ab5a2bc..8f8b429 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -21,16 +21,18 @@ jobs:
python:
# Python versions (CPython):
# https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json
- - "3.7"
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- # CPython 3.12 final is scheduled for October 2023:
- # https://peps.python.org/pep-0693/
- "3.12"
- # Python 2.7 was removed from GHA setup-python in June 2023:
- # https://github.com/actions/setup-python/issues/672
+ - "3.13"
+ - "3.13t"
+ - "3.14"
+ - "3.14t"
+ # CPython 3.15 final is scheduled for October 2026:
+ # https://peps.python.org/pep-0790/
+ - "3.15-dev"
# PyPy versions:
# - https://github.com/actions/setup-python/blob/main/docs/advanced-usage.md
@@ -43,6 +45,7 @@ jobs:
- "pypy3.8"
- "pypy3.9"
- "pypy3.10"
+ - "pypy3.11"
# Old PyPy versions
# See https://foss.heptapod.net/pypy/pypy/-/issues/3991
@@ -51,28 +54,60 @@ 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.11
+ 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"
+ - os: windows-latest
+ python: "3.13t"
+ - os: windows-latest
+ python: "3.14"
+ - os: windows-latest
+ python: "3.14t"
- # macOS: test old and new Python
+ # macOS
+ # Python 3.9 is the oldest version available on macOS/arm64.
+ - os: macos-latest
+ python: "3.9"
- os: macos-latest
- python: 3.6
+ python: "3.10"
- os: macos-latest
- python: 3.11
- - os: ubuntu-20.04
- python: 3.5
- - os: ubuntu-20.04
- python: 3.6
+ python: "3.11"
+ - os: macos-latest
+ python: "3.12"
+ - os: macos-latest
+ python: "3.13"
+ - os: macos-latest
+ python: "3.13t"
+ - os: macos-latest
+ python: "3.14"
+ - os: macos-latest
+ python: "3.14t"
+
+ # Ubuntu: test deadsnakes Python versions which are not supported by
+ # GHA python-versions.
+ - os: ubuntu-22.04
+ python: "3.7"
steps:
# https://github.com/actions/checkout
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Setup Python
# https://github.com/actions/setup-python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
allow-prereleases: true
@@ -82,3 +117,21 @@ jobs:
run: python -VV
- name: Run tests
run: python runtests.py --current --verbose
+
+ test_python27:
+ # Get Python 2.7 from Ubuntu 22.04.
+ #
+ # Python 2.7 was removed from GHA setup-python in June 2023:
+ # https://github.com/actions/setup-python/issues/672
+ name: 'Test Python 2.7'
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install Python 2.7
+ run: |
+ sudo apt-get update
+ sudo apt-get -yq install python2.7 python2.7-dev
+ - name: Display the Python version
+ run: python2.7 -VV
+ - name: Run tests
+ run: python2.7 runtests.py --current --verbose
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index de85e78..21e6b81 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -4,9 +4,9 @@
version: 2
build:
- os: ubuntu-22.04
+ os: ubuntu-lts-latest
tools:
- python: "3.11"
+ python: "3"
sphinx:
configuration: docs/conf.py
diff --git a/docs/api.rst b/docs/api.rst
index 9204d9a..2681a4d 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -7,24 +7,263 @@ functions for old Python versions.
Supported Python versions:
-* Python 3.5 - 3.11
-* PyPy 2.7
-* PyPy 3.6 - 3.9
+* Python 3.6 - 3.15
+* 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.
-A C99 subset is required, like ``static inline`` functions: see `PEP 7
-`_. ISO C90 is partially supported
-for Python 2.7.
+A C11 subset (without optional features) is required, like ``static inline``
+functions: see `PEP 7 `_. ISO C90
+is partially supported for Python 2.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.15
+-----------
+
+.. c:function:: PyBytesWriter* PyBytesWriter_Create(Py_ssize_t size)
+.. c:function:: void* PyBytesWriter_GetData(PyBytesWriter *writer)
+.. c:function:: Py_ssize_t PyBytesWriter_GetSize(PyBytesWriter *writer)
+.. c:function:: PyObject* PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
+.. c:function:: PyObject* PyBytesWriter_Finish(PyBytesWriter *writer)
+.. c:function:: PyObject* PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
+.. c:function:: void PyBytesWriter_Discard(PyBytesWriter *writer)
+.. c:function:: int PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)
+.. c:function:: int PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t size)
+.. c:function:: void* PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer, Py_ssize_t size, void *buf)
+.. c:function:: int PyBytesWriter_WriteBytes(PyBytesWriter *writer, const void *bytes, Py_ssize_t size)
+.. c:function:: int PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
+
+ See `PyBytesWriter documentation `__.
+
+.. 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 `__.
+
+.. c:function:: PyObject* PyTuple_FromArray(PyObject *const *array, Py_ssize_t size)
+
+ See `PyTuple_FromArray() documentation `__.
+
+.. c:function:: Py_hash_t PyUnstable_Unicode_GET_CACHED_HASH(PyObject *op)
+
+ See `PyUnstable_Unicode_GET_CACHED_HASH() documentation `__.
+
+ On PyPy, always returns ``-1``.
+
+.. c:function:: int PyUnstable_SetImmortal(PyObject *op)
+
+ See `PyUnstable_SetImmortal() documentation `__.
+
+ Availability: Python 3.13 and newer, not available on PyPy.
+
+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 `__.
+
+.. 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 `__.
+
+.. 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 `__.
+
+.. 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 `__.
+
+.. 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_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 `__.
+
+.. 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 `__.
+
+.. 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 `__.
+
+.. 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 `__.
+
+.. 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 `__.
+
+.. c:function:: int PyUnstable_Object_IsUniquelyReferenced(PyObject *op)
+
+ See `PyUnstable_Object_IsUniquelyReferenced() documentation `__.
+
+Not supported:
+
+* ``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()``
+* ``PyType_GetBaseByToken()``
+* ``PyUnicodeWriter_DecodeUTF8Stateful()``
+* ``PyUnicodeWriter_WriteUCS4()``
+* ``Py_InitializeFromInitConfig()``
Python 3.13
@@ -113,6 +352,83 @@ Python 3.13
Available on Python 3.5.2 and newer.
+.. c:function:: int PyUnicode_EqualToUTF8(PyObject *unicode, const char *str)
+
+ See `PyUnicode_EqualToUTF8() documentation `__.
+
+.. c:function:: int PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t size)
+
+ See `PyUnicode_EqualToUTF8AndSize() documentation `__.
+
+.. c:function:: int PyList_Extend(PyObject *list, PyObject *iterable)
+
+ See `PyList_Extend() documentation `__.
+
+.. c:function:: int PyList_Clear(PyObject *list)
+
+ See `PyList_Clear() documentation `__.
+
+.. c:function:: int PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result)
+
+ See `PyDict_Pop() documentation `__.
+
+.. c:function:: int PyDict_PopString(PyObject *dict, const char *key, PyObject **result)
+
+ See `PyDict_PopString() documentation `__.
+
+.. c:function:: Py_hash_t Py_HashPointer(const void *ptr)
+
+ See `Py_HashPointer() documentation `__.
+
+.. c:type:: PyTime_t
+
+ A timestamp or duration in nanoseconds, represented as a signed 64-bit
+ integer.
+
+.. c:var:: PyTime_t PyTime_MIN
+
+ Minimum value of :c:type:`PyTime_t`.
+
+.. c:var:: PyTime_t PyTime_MAX
+
+ Maximum value of :c:type:`PyTime_t`.
+
+.. c:function:: double PyTime_AsSecondsDouble(PyTime_t t)
+
+ See `PyTime_AsSecondsDouble() documentation `__.
+
+.. c:function:: int PyTime_Monotonic(PyTime_t *result)
+
+ See `PyTime_Monotonic() documentation `__.
+
+.. c:function:: int PyTime_Time(PyTime_t *result)
+
+ See `PyTime_Time() documentation `__.
+
+.. c:function:: int PyTime_PerfCounter(PyTime_t *result)
+
+ See `PyTime_PerfCounter() documentation `__.
+
+.. c:function:: PyObject* PyList_GetItemRef(PyObject *op, Py_ssize_t index)
+
+ See `PyList_GetItemRef() documentation `__.
+
+.. c:function:: int PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, PyObject **result)
+
+ See `PyDict_SetDefaultRef() documentation `__.
+
+
+Not supported:
+
+* ``PyErr_FormatUnraisable()``.
+* ``PyLong_AsNativeBytes()``
+* ``PyLong_FromNativeBytes()``
+* ``PyLong_FromUnsignedNativeBytes()``
+* ``PyObject_GenericHash()``.
+* ``PySys_Audit()``.
+* ``PySys_AuditTuple()``.
+* ``PyType_GetFullyQualifiedName()``
+* ``PyType_GetModuleName()``
Python 3.12
-----------
@@ -129,6 +445,29 @@ Python 3.12
Not available on PyPy.
+.. c:function:: PyObject* Py_GetConstant(unsigned int constant_id)
+
+ See `Py_GetConstant() documentation `__.
+
+.. c:function:: PyObject* Py_GetConstantBorrowed(unsigned int constant_id)
+
+ See `Py_GetConstantBorrowed() documentation `__.
+
+
+Not supported:
+
+* ``PyDict_AddWatcher()``, ``PyDict_Watch()``.
+* ``PyCode_AddWatcher()``, ``PyCode_ClearWatcher()``.
+* ``PyErr_GetRaisedException()``, ``PyErr_SetRaisedException()``.
+* ``_PyErr_ChainExceptions1()``.
+* ``PyErr_DisplayException()``.
+* ``_Py_IsImmortal()``.
+* ``Py_NewInterpreterFromConfig()``.
+* ``PyException_GetArgs()``, ``PyException_SetArgs()``.
+* ``PyEval_SetProfileAllThreads()``, ``PyEval_SetTraceAllThreads()``.
+* ``PyFunction_SetVectorcall()``.
+* ``PyType_FromMetaclass()``: implementation too big to be backported.
+* ``PyVectorcall_Call()``.
Python 3.11
-----------
@@ -229,6 +568,15 @@ Python 3.11
Not available on PyPy
+Not supported:
+
+* ``PyType_GetModuleByDef()``.
+* ``PyType_GetName()``.
+* ``PyType_GetQualName()``.
+* ``Py_Version`` constant.
+* ``PyErr_GetHandledException()``, ``PyErr_SetHandledException()``.
+* ``PyFrame_GetGenerator()``.
+
Python 3.10
-----------
@@ -260,6 +608,17 @@ Python 3.10
See `PyModule_AddObjectRef() documentation `__.
+Not supported:
+
+* ``PyCodec_Unregister()``.
+* ``PyDateTime_DATE_GET_TZINFO()``, ``PyDateTime_TIME_GET_TZINFO()``.
+* ``PyErr_SetInterruptEx()``.
+* ``PyGC_Enable()``, ``PyGC_Disable()`` and ``PyGC_IsEnabled()``.
+* ``PyIter_Send()``.
+* ``PySet_CheckExact()``.
+* ``Py_TPFLAGS_DISALLOW_INSTANTIATION`` constant.
+* ``Py_TPFLAGS_IMMUTABLETYPE`` constant.
+
Python 3.9
----------
@@ -302,6 +661,12 @@ PyObject
See `PY_VECTORCALL_ARGUMENTS_OFFSET documentation `__.
+Not supported:
+
+* ``PyVectorcall_CallMethod()``.
+* ``PyType_FromModuleAndSpec()``
+
+
PyFrameObject
^^^^^^^^^^^^^
@@ -366,6 +731,35 @@ Module helper
See `PyModule_AddType() documentation `__.
+Python 3.8
+----------
+
+Not supported:
+
+* ``PyCode_NewWithPosOnlyArgs()``.
+
+Python 3.7
+----------
+
+Not supported:
+
+* ``PyImport_GetModule()``.
+* ``PyInterpreterState_GetID()``.
+* ``PySlice_Unpack()``, ``PySlice_AdjustIndices()``.
+* ``PyTimeZone_FromOffset()``, ``PyTimeZone_FromOffsetAndName()``.
+* ``Py_RETURN_RICHCOMPARE()``.
+* ``Py_UNREACHABLE`` macro.
+
+Python 3.6
+----------
+
+Not supported:
+
+* ``PyErr_ResourceWarning()``.
+* ``PyErr_SetImportErrorSubclass()``.
+* ``PyOS_FSPath()``.
+* ``Py_FinalizeEx()``.
+
Python 3.5.2
------------
@@ -373,6 +767,15 @@ Python 3.5.2
.. c:macro:: Py_XSETREF(op, op2)
+Not supported:
+
+* ``PyCodec_NameReplaceErrors()``.
+* ``PyErr_FormatV()``.
+* ``PyExc_RecursionError``.
+* ``PyModule_FromDefAndSpec()``, ``PyModule_FromDefAndSpec2()``,
+ and ``PyModule_ExecDef()``.
+* ``PyNumber_MatrixMultiply()`` and ``PyNumber_InPlaceMatrixMultiply()``.
+
Python 3.4
----------
@@ -380,6 +783,24 @@ Python 3.4
See `Py_UNUSED() documentation `__.
+Python 3.2
+----------
+
+Not supported:
+
+* ``Py_VA_COPY``.
+* ``PySys_SetArgvEx()``.
+* ``PyLong_AsLongLongAndOverflow()``.
+* ``PyErr_NewExceptionWithDoc()``.
+
+Python 3.1
+----------
+
+Not supported:
+
+* ``PyOS_string_to_double()``.
+* ``PyCapsule`` API.
+
Borrow variant
--------------
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 9fd3d06..c68d88b 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,154 @@
Changelog
=========
+* 2026-02-12: Add functions:
+
+ * ``PyUnstable_SetImmortal()``
+
+* 2025-10-14: Add functions:
+
+ * ``PyTuple_FromArray()``
+ * ``PyUnstable_Unicode_GET_CACHED_HASH()``
+
+* 2025-09-18: Add PEP 782 functions:
+
+ * ``PyBytesWriter_Create()``
+ * ``PyBytesWriter_Discard()``
+ * ``PyBytesWriter_Finish()``
+ * ``PyBytesWriter_FinishWithPointer()``
+ * ``PyBytesWriter_FinishWithSize()``
+ * ``PyBytesWriter_Format()``
+ * ``PyBytesWriter_GetData()``
+ * ``PyBytesWriter_GetSize()``
+ * ``PyBytesWriter_Grow()``
+ * ``PyBytesWriter_GrowAndUpdatePointer()``
+ * ``PyBytesWriter_Resize()``
+ * ``PyBytesWriter_WriteBytes()``
+
+* 2025-09-01: Add ``PyUnstable_Object_IsUniquelyReferenced()`` function.
+* 2025-06-09: Add ``PyUnicodeWriter_WriteASCII()`` function.
+* 2025-06-03: Add functions:
+
+ * ``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:
+
+ * ``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``
+ * ``PyLong_GetNativeLayout()``
+ * ``PyLongExport``
+ * ``PyLong_Export()``
+ * ``PyLong_FreeExport()``
+ * ``PyLongWriter``
+ * ``PyLongWriter_Create()``
+ * ``PyLongWriter_Finish()``
+ * ``PyLongWriter_Discard()``
+
+* 2024-11-12: Add functions:
+
+ * ``PyLong_IsPositive()``
+ * ``PyLong_IsNegative()``
+ * ``PyLong_IsZero()``
+
+* 2024-10-09: Add functions:
+
+ * ``PyBytes_Join()``
+ * ``PyIter_NextItem()``
+ * ``PyLong_AsInt32()``
+ * ``PyLong_AsInt64()``
+ * ``PyLong_AsUInt32()``
+ * ``PyLong_AsUInt64()``
+ * ``PyLong_FromInt32()``
+ * ``PyLong_FromInt64()``
+ * ``PyLong_FromUInt32()``
+ * ``PyLong_FromUInt64()``
+ * ``PyUnicode_Equal()``
+ * ``Py_HashBuffer()``
+
+* 2024-07-18: Add functions:
+
+ * ``PyUnicodeWriter_Create()``
+ * ``PyUnicodeWriter_Discard()``
+ * ``PyUnicodeWriter_Finish()``
+ * ``PyUnicodeWriter_WriteChar()``
+ * ``PyUnicodeWriter_WriteUTF8()``
+ * ``PyUnicodeWriter_WriteStr()``
+ * ``PyUnicodeWriter_WriteRepr()``
+ * ``PyUnicodeWriter_WriteSubstring()``
+ * ``PyUnicodeWriter_WriteWideChar()``
+ * ``PyUnicodeWriter_Format()``
+
+* 2024-06-03: Add ``PyLong_GetSign()``.
+* 2024-04-23: Drop Python 3.5 support. It cannot be tested anymore (pip fails).
+* 2024-04-02: Add ``PyDict_SetDefaultRef()`` function.
+* 2024-03-29: Add ``PyList_GetItemRef()`` function.
+* 2024-03-21: Add functions:
+
+ * ``Py_GetConstant()``
+ * ``Py_GetConstantBorrowed()``
+
+* 2024-03-09: Add hash constants:
+
+ * ``PyHASH_BITS``
+ * ``PyHASH_IMAG``
+ * ``PyHASH_INF``
+ * ``PyHASH_MODULUS``
+
+* 2024-02-20: Add PyTime API:
+
+ * ``PyTime_t`` type
+ * ``PyTime_MIN`` and ``PyTime_MAX`` constants
+ * ``PyTime_AsSecondsDouble()``
+ * ``PyTime_Monotonic()``
+ * ``PyTime_PerfCounter()``
+ * ``PyTime_Time()``
+
+* 2023-12-15: Add function ``Py_HashPointer()``.
+* 2023-11-14: Add functions:
+
+ * ``PyDict_Pop()``
+ * ``PyDict_PopString()``
+
+* 2023-11-13: Add functions:
+
+ * ``PyList_Extend()``
+ * ``PyList_Clear()``
+
+* 2023-10-04: Add functions:
+
+ * ``PyUnicode_EqualToUTF8()``
+ * ``PyUnicode_EqualToUTF8AndSize()``
+
* 2023-10-03: Add functions:
* ``PyObject_VisitManagedDict()``
diff --git a/docs/users.rst b/docs/users.rst
index b817996..abf4307 100644
--- a/docs/users.rst
+++ b/docs/users.rst
@@ -26,6 +26,9 @@ Examples of projects using pythoncapi_compat.h
* `mypy `_
(mypyc,
`commit `__)
+* `numpy `_:
+ `pythoncapi-compat Git submodule
+ `_
* `pybluez `_
(`commit `__)
* `python-snappy `_
@@ -42,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
======================================
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..7c5f9fc
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,6 @@
+project(
+ 'pythoncapi-compat',
+ 'c'
+)
+
+incdir = include_directories('.')
diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h
index dcd97ff..08b6915 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
@@ -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)
@@ -30,10 +31,12 @@ extern "C" {
# define _Py_CAST(type, expr) ((type)(expr))
#endif
-// On C++11 and newer, _Py_NULL is defined as nullptr on C++11,
-// otherwise it is defined as NULL.
+// 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.
#ifndef _Py_NULL
-# if defined(__cplusplus) && __cplusplus >= 201103
+# if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \
+ || (defined(__cplusplus) && __cplusplus >= 201103)
# define _Py_NULL nullptr
# else
# define _Py_NULL NULL
@@ -45,6 +48,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)
@@ -280,7 +290,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)
{
@@ -573,7 +583,7 @@ static inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj)
return 0;
}
*pobj = Py_NewRef(obj);
- return (*pobj != NULL);
+ return 1;
}
#endif
@@ -853,7 +863,7 @@ PyModule_Add(PyObject *mod, const char *name, PyObject *value)
// gh-108014 added Py_IsFinalizing() to Python 3.13.0a1
// bpo-1856 added _Py_Finalizing to Python 3.2.1b1.
-// Py_IsFinalizing() was added to PyPy 7.3.0.
+// _Py_IsFinalizing() was added to PyPy 7.3.0.
#if (0x030201B1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030D00A1) \
&& (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x7030000)
static inline int Py_IsFinalizing(void)
@@ -911,7 +921,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);
@@ -922,7 +932,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);
@@ -939,6 +949,1764 @@ PyThreadState_GetUnchecked(void)
}
#endif
+// gh-110289 added PyUnicode_EqualToUTF8() and PyUnicode_EqualToUTF8AndSize()
+// to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t str_len)
+{
+ Py_ssize_t len;
+ const void *utf8;
+ PyObject *exc_type, *exc_value, *exc_tb;
+ int res;
+
+ // API cannot report errors so save/restore the exception
+ PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
+
+ // Python 3.3.0a1 added PyUnicode_AsUTF8AndSize()
+#if PY_VERSION_HEX >= 0x030300A1
+ if (PyUnicode_IS_ASCII(unicode)) {
+ utf8 = PyUnicode_DATA(unicode);
+ len = PyUnicode_GET_LENGTH(unicode);
+ }
+ else {
+ utf8 = PyUnicode_AsUTF8AndSize(unicode, &len);
+ if (utf8 == NULL) {
+ // Memory allocation failure. The API cannot report error,
+ // so ignore the exception and return 0.
+ res = 0;
+ goto done;
+ }
+ }
+
+ if (len != str_len) {
+ res = 0;
+ goto done;
+ }
+ res = (memcmp(utf8, str, (size_t)len) == 0);
+#else
+ PyObject *bytes = PyUnicode_AsUTF8String(unicode);
+ if (bytes == NULL) {
+ // Memory allocation failure. The API cannot report error,
+ // so ignore the exception and return 0.
+ res = 0;
+ goto done;
+ }
+
+#if PY_VERSION_HEX >= 0x03000000
+ len = PyBytes_GET_SIZE(bytes);
+ utf8 = PyBytes_AS_STRING(bytes);
+#else
+ len = PyString_GET_SIZE(bytes);
+ utf8 = PyString_AS_STRING(bytes);
+#endif
+ if (len != str_len) {
+ Py_DECREF(bytes);
+ res = 0;
+ goto done;
+ }
+
+ res = (memcmp(utf8, str, (size_t)len) == 0);
+ Py_DECREF(bytes);
+#endif
+
+done:
+ PyErr_Restore(exc_type, exc_value, exc_tb);
+ return res;
+}
+
+static inline int
+PyUnicode_EqualToUTF8(PyObject *unicode, const char *str)
+{
+ return PyUnicode_EqualToUTF8AndSize(unicode, str, (Py_ssize_t)strlen(str));
+}
+#endif
+
+
+// gh-111138 added PyList_Extend() and PyList_Clear() to Python 3.13.0a2
+#if PY_VERSION_HEX < 0x030D00A2
+static inline int
+PyList_Extend(PyObject *list, PyObject *iterable)
+{
+ return PyList_SetSlice(list, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, iterable);
+}
+
+static inline int
+PyList_Clear(PyObject *list)
+{
+ return PyList_SetSlice(list, 0, PY_SSIZE_T_MAX, NULL);
+}
+#endif
+
+// gh-111262 added PyDict_Pop() and PyDict_PopString() to Python 3.13.0a2
+#if PY_VERSION_HEX < 0x030D00A2
+static inline int
+PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result)
+{
+ PyObject *value;
+
+ if (!PyDict_Check(dict)) {
+ PyErr_BadInternalCall();
+ if (result) {
+ *result = NULL;
+ }
+ return -1;
+ }
+
+ // bpo-16991 added _PyDict_Pop() to Python 3.5.0b2.
+ // Python 3.6.0b3 changed _PyDict_Pop() first argument type to PyObject*.
+ // Python 3.13.0a1 removed _PyDict_Pop().
+#if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x030500b2 || PY_VERSION_HEX >= 0x030D0000
+ value = PyObject_CallMethod(dict, "pop", "O", key);
+#elif PY_VERSION_HEX < 0x030600b3
+ value = _PyDict_Pop(_Py_CAST(PyDictObject*, dict), key, NULL);
+#else
+ value = _PyDict_Pop(dict, key, NULL);
+#endif
+ if (value == NULL) {
+ if (result) {
+ *result = NULL;
+ }
+ if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_KeyError)) {
+ return -1;
+ }
+ PyErr_Clear();
+ return 0;
+ }
+ if (result) {
+ *result = value;
+ }
+ else {
+ Py_DECREF(value);
+ }
+ return 1;
+}
+
+static inline int
+PyDict_PopString(PyObject *dict, const char *key, PyObject **result)
+{
+ PyObject *key_obj = PyUnicode_FromString(key);
+ if (key_obj == NULL) {
+ if (result != NULL) {
+ *result = NULL;
+ }
+ return -1;
+ }
+
+ int res = PyDict_Pop(dict, key_obj, result);
+ Py_DECREF(key_obj);
+ return res;
+}
+#endif
+
+
+#if PY_VERSION_HEX < 0x030200A4
+// Python 3.2.0a4 added Py_hash_t type
+typedef Py_ssize_t Py_hash_t;
+#endif
+
+
+// gh-111545 added Py_HashPointer() to Python 3.13.0a3
+#if PY_VERSION_HEX < 0x030D00A3
+static inline Py_hash_t Py_HashPointer(const void *ptr)
+{
+#if PY_VERSION_HEX >= 0x030900A4 && !defined(PYPY_VERSION)
+ return _Py_HashPointer(ptr);
+#else
+ return _Py_HashPointer(_Py_CAST(void*, ptr));
+#endif
+}
+#endif
+
+
+// Python 3.13a4 added a PyTime API.
+// Use the private API added to Python 3.5.
+#if PY_VERSION_HEX < 0x030D00A4 && PY_VERSION_HEX >= 0x03050000
+typedef _PyTime_t PyTime_t;
+#define PyTime_MIN _PyTime_MIN
+#define PyTime_MAX _PyTime_MAX
+
+static inline double PyTime_AsSecondsDouble(PyTime_t t)
+{ return _PyTime_AsSecondsDouble(t); }
+
+static inline int PyTime_Monotonic(PyTime_t *result)
+{ return _PyTime_GetMonotonicClockWithInfo(result, NULL); }
+
+static inline int PyTime_Time(PyTime_t *result)
+{ return _PyTime_GetSystemClockWithInfo(result, NULL); }
+
+static inline int PyTime_PerfCounter(PyTime_t *result)
+{
+#if PY_VERSION_HEX >= 0x03070000 && !defined(PYPY_VERSION)
+ return _PyTime_GetPerfCounterWithInfo(result, NULL);
+#elif PY_VERSION_HEX >= 0x03070000
+ // Call time.perf_counter_ns() and convert Python int object to PyTime_t.
+ // Cache time.perf_counter_ns() function for best performance.
+ static PyObject *func = NULL;
+ if (func == NULL) {
+ PyObject *mod = PyImport_ImportModule("time");
+ if (mod == NULL) {
+ return -1;
+ }
+
+ func = PyObject_GetAttrString(mod, "perf_counter_ns");
+ Py_DECREF(mod);
+ if (func == NULL) {
+ return -1;
+ }
+ }
+
+ PyObject *res = PyObject_CallNoArgs(func);
+ if (res == NULL) {
+ return -1;
+ }
+ long long value = PyLong_AsLongLong(res);
+ Py_DECREF(res);
+
+ if (value == -1 && PyErr_Occurred()) {
+ return -1;
+ }
+
+ Py_BUILD_ASSERT(sizeof(value) >= sizeof(PyTime_t));
+ *result = (PyTime_t)value;
+ return 0;
+#else
+ // Call time.perf_counter() and convert C double to PyTime_t.
+ // Cache time.perf_counter() function for best performance.
+ static PyObject *func = NULL;
+ if (func == NULL) {
+ PyObject *mod = PyImport_ImportModule("time");
+ if (mod == NULL) {
+ return -1;
+ }
+
+ func = PyObject_GetAttrString(mod, "perf_counter");
+ Py_DECREF(mod);
+ if (func == NULL) {
+ return -1;
+ }
+ }
+
+ PyObject *res = PyObject_CallNoArgs(func);
+ if (res == NULL) {
+ return -1;
+ }
+ double d = PyFloat_AsDouble(res);
+ Py_DECREF(res);
+
+ if (d == -1.0 && PyErr_Occurred()) {
+ return -1;
+ }
+
+ // Avoid floor() to avoid having to link to libm
+ *result = (PyTime_t)(d * 1e9);
+ return 0;
+#endif
+}
+
+#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.8.
+#if (!defined(PyHASH_BITS) \
+ && ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \
+ || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \
+ && PYPY_VERSION_NUM >= 0x07030800)))
+# define PyHASH_BITS _PyHASH_BITS
+# define PyHASH_MODULUS _PyHASH_MODULUS
+# define PyHASH_INF _PyHASH_INF
+# define PyHASH_IMAG _PyHASH_IMAG
+#endif
+
+
+// gh-111545 added Py_GetConstant() and Py_GetConstantBorrowed()
+// to Python 3.13.0a6
+#if PY_VERSION_HEX < 0x030D00A6 && !defined(Py_CONSTANT_NONE)
+
+#define Py_CONSTANT_NONE 0
+#define Py_CONSTANT_FALSE 1
+#define Py_CONSTANT_TRUE 2
+#define Py_CONSTANT_ELLIPSIS 3
+#define Py_CONSTANT_NOT_IMPLEMENTED 4
+#define Py_CONSTANT_ZERO 5
+#define Py_CONSTANT_ONE 6
+#define Py_CONSTANT_EMPTY_STR 7
+#define Py_CONSTANT_EMPTY_BYTES 8
+#define Py_CONSTANT_EMPTY_TUPLE 9
+
+static inline PyObject* Py_GetConstant(unsigned int constant_id)
+{
+ static PyObject* constants[Py_CONSTANT_EMPTY_TUPLE + 1] = {NULL};
+
+ if (constants[Py_CONSTANT_NONE] == NULL) {
+ constants[Py_CONSTANT_NONE] = Py_None;
+ constants[Py_CONSTANT_FALSE] = Py_False;
+ constants[Py_CONSTANT_TRUE] = Py_True;
+ constants[Py_CONSTANT_ELLIPSIS] = Py_Ellipsis;
+ constants[Py_CONSTANT_NOT_IMPLEMENTED] = Py_NotImplemented;
+
+ constants[Py_CONSTANT_ZERO] = PyLong_FromLong(0);
+ if (constants[Py_CONSTANT_ZERO] == NULL) {
+ goto fatal_error;
+ }
+
+ constants[Py_CONSTANT_ONE] = PyLong_FromLong(1);
+ if (constants[Py_CONSTANT_ONE] == NULL) {
+ goto fatal_error;
+ }
+
+ constants[Py_CONSTANT_EMPTY_STR] = PyUnicode_FromStringAndSize("", 0);
+ if (constants[Py_CONSTANT_EMPTY_STR] == NULL) {
+ goto fatal_error;
+ }
+
+ constants[Py_CONSTANT_EMPTY_BYTES] = PyBytes_FromStringAndSize("", 0);
+ if (constants[Py_CONSTANT_EMPTY_BYTES] == NULL) {
+ goto fatal_error;
+ }
+
+ constants[Py_CONSTANT_EMPTY_TUPLE] = PyTuple_New(0);
+ if (constants[Py_CONSTANT_EMPTY_TUPLE] == NULL) {
+ goto fatal_error;
+ }
+ // goto dance to avoid compiler warnings about Py_FatalError()
+ goto init_done;
+
+fatal_error:
+ // This case should never happen
+ Py_FatalError("Py_GetConstant() failed to get constants");
+ }
+
+init_done:
+ if (constant_id <= Py_CONSTANT_EMPTY_TUPLE) {
+ return Py_NewRef(constants[constant_id]);
+ }
+ else {
+ PyErr_BadInternalCall();
+ return NULL;
+ }
+}
+
+static inline PyObject* Py_GetConstantBorrowed(unsigned int constant_id)
+{
+ PyObject *obj = Py_GetConstant(constant_id);
+ Py_XDECREF(obj);
+ return obj;
+}
+#endif
+
+
+// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4
+#if PY_VERSION_HEX < 0x030D00A4
+static inline PyObject *
+PyList_GetItemRef(PyObject *op, Py_ssize_t index)
+{
+ PyObject *item = PyList_GetItem(op, index);
+ Py_XINCREF(item);
+ return item;
+}
+#endif
+
+
+// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4
+#if PY_VERSION_HEX < 0x030D00A4
+static inline int
+PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value,
+ PyObject **result)
+{
+ PyObject *value;
+ if (PyDict_GetItemRef(d, key, &value) < 0) {
+ // get error
+ if (result) {
+ *result = NULL;
+ }
+ return -1;
+ }
+ if (value != NULL) {
+ // present
+ if (result) {
+ *result = value;
+ }
+ else {
+ Py_DECREF(value);
+ }
+ return 1;
+ }
+
+ // missing: set the item
+ if (PyDict_SetItem(d, key, default_value) < 0) {
+ // set error
+ if (result) {
+ *result = NULL;
+ }
+ return -1;
+ }
+ if (result) {
+ *result = Py_NewRef(default_value);
+ }
+ return 0;
+}
+#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;
+
+static inline void PyUnicodeWriter_Discard(PyUnicodeWriter *writer)
+{
+ _PyUnicodeWriter_Dealloc((_PyUnicodeWriter*)writer);
+ PyMem_Free(writer);
+}
+
+static inline PyUnicodeWriter* PyUnicodeWriter_Create(Py_ssize_t length)
+{
+ if (length < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "length must be positive");
+ return NULL;
+ }
+
+ const size_t size = sizeof(_PyUnicodeWriter);
+ PyUnicodeWriter *pub_writer = (PyUnicodeWriter *)PyMem_Malloc(size);
+ if (pub_writer == _Py_NULL) {
+ PyErr_NoMemory();
+ return _Py_NULL;
+ }
+ _PyUnicodeWriter *writer = (_PyUnicodeWriter *)pub_writer;
+
+ _PyUnicodeWriter_Init(writer);
+ if (_PyUnicodeWriter_Prepare(writer, length, 127) < 0) {
+ PyUnicodeWriter_Discard(pub_writer);
+ return NULL;
+ }
+ writer->overallocate = 1;
+ return pub_writer;
+}
+
+static inline PyObject* PyUnicodeWriter_Finish(PyUnicodeWriter *writer)
+{
+ PyObject *str = _PyUnicodeWriter_Finish((_PyUnicodeWriter*)writer);
+ assert(((_PyUnicodeWriter*)writer)->buffer == NULL);
+ PyMem_Free(writer);
+ return str;
+}
+
+static inline int
+PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch)
+{
+ if (ch > 0x10ffff) {
+ PyErr_SetString(PyExc_ValueError,
+ "character must be in range(0x110000)");
+ return -1;
+ }
+
+ return _PyUnicodeWriter_WriteChar((_PyUnicodeWriter*)writer, ch);
+}
+
+static inline int
+PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj)
+{
+ PyObject *str = PyObject_Str(obj);
+ if (str == NULL) {
+ return -1;
+ }
+
+ int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str);
+ Py_DECREF(str);
+ return res;
+}
+
+static inline int
+PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj)
+{
+ if (obj == NULL) {
+ return _PyUnicodeWriter_WriteASCIIString((_PyUnicodeWriter*)writer,
+ "", 6);
+ }
+
+ PyObject *str = PyObject_Repr(obj);
+ if (str == NULL) {
+ return -1;
+ }
+
+ int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str);
+ Py_DECREF(str);
+ return res;
+}
+
+static inline int
+PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer,
+ const char *str, Py_ssize_t size)
+{
+ if (size < 0) {
+ size = (Py_ssize_t)strlen(str);
+ }
+
+ PyObject *str_obj = PyUnicode_FromStringAndSize(str, size);
+ if (str_obj == _Py_NULL) {
+ return -1;
+ }
+
+ int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str_obj);
+ Py_DECREF(str_obj);
+ 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)
+{
+ if (size < 0) {
+ size = (Py_ssize_t)wcslen(str);
+ }
+
+ PyObject *str_obj = PyUnicode_FromWideChar(str, size);
+ if (str_obj == _Py_NULL) {
+ return -1;
+ }
+
+ int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str_obj);
+ Py_DECREF(str_obj);
+ return res;
+}
+
+static inline int
+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 %s",
+ Py_TYPE(str)->tp_name);
+ return -1;
+ }
+ if (start < 0 || start > end) {
+ PyErr_Format(PyExc_ValueError, "invalid start argument");
+ return -1;
+ }
+ if (end > PyUnicode_GET_LENGTH(str)) {
+ PyErr_Format(PyExc_ValueError, "invalid end argument");
+ return -1;
+ }
+
+ return _PyUnicodeWriter_WriteSubstring((_PyUnicodeWriter*)writer, str,
+ start, end);
+}
+
+static inline int
+PyUnicodeWriter_Format(PyUnicodeWriter *writer, const char *format, ...)
+{
+ va_list vargs;
+ va_start(vargs, format);
+ PyObject *str = PyUnicode_FromFormatV(format, vargs);
+ va_end(vargs);
+ if (str == _Py_NULL) {
+ return -1;
+ }
+
+ int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str);
+ Py_DECREF(str);
+ return res;
+}
+#endif // PY_VERSION_HEX < 0x030E0000
+
+// gh-116560 added PyLong_GetSign() to Python 3.14.0a0
+#if PY_VERSION_HEX < 0x030E00A0
+static inline int PyLong_GetSign(PyObject *obj, int *sign)
+{
+ if (!PyLong_Check(obj)) {
+ PyErr_Format(PyExc_TypeError, "expect int, got %s", Py_TYPE(obj)->tp_name);
+ return -1;
+ }
+
+ *sign = _PyLong_Sign(obj);
+ return 0;
+}
+#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
+
+#if PY_VERSION_HEX >= 0x030d0000 && !defined(PYPY_VERSION)
+PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *str1, PyObject *str2);
+#endif
+
+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)
+ 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
+
+
+// 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
+
+
+#if PY_VERSION_HEX < 0x030E00A0
+
+#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION)
+PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void *src, Py_ssize_t len);
+#endif
+
+static inline Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len)
+{
+#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION)
+ 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
+
+
+#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
+
+
+#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
+
+
+// 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
+
+
+#if PY_VERSION_HEX < 0x030C00A3
+# define Py_T_SHORT 0
+# define Py_T_INT 1
+# define Py_T_LONG 2
+# define Py_T_FLOAT 3
+# define Py_T_DOUBLE 4
+# define Py_T_STRING 5
+# define _Py_T_OBJECT 6
+# define Py_T_CHAR 7
+# define Py_T_BYTE 8
+# define Py_T_UBYTE 9
+# define Py_T_USHORT 10
+# define Py_T_UINT 11
+# define Py_T_ULONG 12
+# define Py_T_STRING_INPLACE 13
+# define Py_T_BOOL 14
+# define Py_T_OBJECT_EX 16
+# define Py_T_LONGLONG 17
+# define Py_T_ULONGLONG 18
+# define Py_T_PYSSIZET 19
+
+# if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION)
+# define _Py_T_NONE 20
+# endif
+
+# define Py_READONLY 1
+# define Py_AUDIT_READ 2
+# define _Py_WRITE_RESTRICTED 4
+#endif
+
+
+// gh-127350 added Py_fopen() and Py_fclose() to Python 3.14a4
+#if PY_VERSION_HEX < 0x030E00A4
+
+#if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION)
+PyAPI_FUNC(FILE*) _Py_fopen_obj(PyObject *path, const char *mode);
+#endif
+
+static inline FILE* Py_fopen(PyObject *path, const char *mode)
+{
+#if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION)
+ 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
+}
+
+static inline int Py_fclose(FILE *file)
+{
+ return fclose(file);
+}
+#endif
+
+
+#if 0x03080000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030E0000 && !defined(PYPY_VERSION)
+PyAPI_FUNC(const PyConfig*) _Py_GetConfig(void);
+
+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),
+#if 0x03090000 <= PY_VERSION_HEX
+ PYTHONCAPI_COMPAT_SPEC(platlibdir, WSTR, "platlibdir"),
+#endif
+ 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),
+ 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 < sizeof(config_spec) / sizeof(config_spec[0]); 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);
+ }
+
+ 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)
+
+// gh-133144 added PyUnstable_Object_IsUniquelyReferenced() to Python 3.14.0b1.
+// Adapted from _PyObject_IsUniquelyReferenced() implementation.
+#if PY_VERSION_HEX < 0x030E00B0
+static inline int PyUnstable_Object_IsUniquelyReferenced(PyObject *obj)
+{
+#if !defined(Py_GIL_DISABLED)
+ return Py_REFCNT(obj) == 1;
+#else
+ // NOTE: the entire ob_ref_shared field must be zero, including flags, to
+ // ensure that other threads cannot concurrently create new references to
+ // this object.
+ return (_Py_IsOwnedByCurrentThread(obj) &&
+ _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local) == 1 &&
+ _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared) == 0);
+#endif
+}
+#endif
+
+// gh-128926 added PyUnstable_TryIncRef() and PyUnstable_EnableTryIncRef() to
+// Python 3.14.0a5. Adapted from _Py_TryIncref() and _PyObject_SetMaybeWeakref().
+#if PY_VERSION_HEX < 0x030E00A5
+static inline int PyUnstable_TryIncRef(PyObject *op)
+{
+#ifndef Py_GIL_DISABLED
+ if (Py_REFCNT(op) > 0) {
+ Py_INCREF(op);
+ return 1;
+ }
+ return 0;
+#else
+ // _Py_TryIncrefFast()
+ uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
+ local += 1;
+ if (local == 0) {
+ // immortal
+ return 1;
+ }
+ if (_Py_IsOwnedByCurrentThread(op)) {
+ _Py_INCREF_STAT_INC();
+ _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local);
+#ifdef Py_REF_DEBUG
+ _Py_INCREF_IncRefTotal();
+#endif
+ return 1;
+ }
+
+ // _Py_TryIncRefShared()
+ Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared);
+ for (;;) {
+ // If the shared refcount is zero and the object is either merged
+ // or may not have weak references, then we cannot incref it.
+ if (shared == 0 || shared == _Py_REF_MERGED) {
+ return 0;
+ }
+
+ if (_Py_atomic_compare_exchange_ssize(
+ &op->ob_ref_shared,
+ &shared,
+ shared + (1 << _Py_REF_SHARED_SHIFT))) {
+#ifdef Py_REF_DEBUG
+ _Py_INCREF_IncRefTotal();
+#endif
+ _Py_INCREF_STAT_INC();
+ return 1;
+ }
+ }
+#endif
+}
+
+static inline void PyUnstable_EnableTryIncRef(PyObject *op)
+{
+#ifdef Py_GIL_DISABLED
+ // _PyObject_SetMaybeWeakref()
+ if (_Py_IsImmortal(op)) {
+ return;
+ }
+ for (;;) {
+ Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared);
+ if ((shared & _Py_REF_SHARED_FLAG_MASK) != 0) {
+ // Nothing to do if it's in WEAKREFS, QUEUED, or MERGED states.
+ return;
+ }
+ if (_Py_atomic_compare_exchange_ssize(
+ &op->ob_ref_shared, &shared, shared | _Py_REF_MAYBE_WEAKREF)) {
+ return;
+ }
+ }
+#else
+ (void)op; // unused argument
+#endif
+}
+#endif
+
+
+#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
+
+
+#if PY_VERSION_HEX < 0x030F00A1
+typedef struct PyBytesWriter {
+ char small_buffer[256];
+ PyObject *obj;
+ Py_ssize_t size;
+} PyBytesWriter;
+
+static inline Py_ssize_t
+_PyBytesWriter_GetAllocated(PyBytesWriter *writer)
+{
+ if (writer->obj == NULL) {
+ return sizeof(writer->small_buffer);
+ }
+ else {
+ return PyBytes_GET_SIZE(writer->obj);
+ }
+}
+
+
+static inline int
+_PyBytesWriter_Resize_impl(PyBytesWriter *writer, Py_ssize_t size,
+ int resize)
+{
+ int overallocate = resize;
+ assert(size >= 0);
+
+ if (size <= _PyBytesWriter_GetAllocated(writer)) {
+ return 0;
+ }
+
+ if (overallocate) {
+#ifdef MS_WINDOWS
+ /* On Windows, overallocate by 50% is the best factor */
+ if (size <= (PY_SSIZE_T_MAX - size / 2)) {
+ size += size / 2;
+ }
+#else
+ /* On Linux, overallocate by 25% is the best factor */
+ if (size <= (PY_SSIZE_T_MAX - size / 4)) {
+ size += size / 4;
+ }
+#endif
+ }
+
+ if (writer->obj != NULL) {
+ if (_PyBytes_Resize(&writer->obj, size)) {
+ return -1;
+ }
+ assert(writer->obj != NULL);
+ }
+ else {
+ writer->obj = PyBytes_FromStringAndSize(NULL, size);
+ if (writer->obj == NULL) {
+ return -1;
+ }
+
+ if (resize) {
+ assert((size_t)size > sizeof(writer->small_buffer));
+ memcpy(PyBytes_AS_STRING(writer->obj),
+ writer->small_buffer,
+ sizeof(writer->small_buffer));
+ }
+ }
+ return 0;
+}
+
+static inline void*
+PyBytesWriter_GetData(PyBytesWriter *writer)
+{
+ if (writer->obj == NULL) {
+ return writer->small_buffer;
+ }
+ else {
+ return PyBytes_AS_STRING(writer->obj);
+ }
+}
+
+static inline Py_ssize_t
+PyBytesWriter_GetSize(PyBytesWriter *writer)
+{
+ return writer->size;
+}
+
+static inline void
+PyBytesWriter_Discard(PyBytesWriter *writer)
+{
+ if (writer == NULL) {
+ return;
+ }
+
+ Py_XDECREF(writer->obj);
+ PyMem_Free(writer);
+}
+
+static inline PyBytesWriter*
+PyBytesWriter_Create(Py_ssize_t size)
+{
+ if (size < 0) {
+ PyErr_SetString(PyExc_ValueError, "size must be >= 0");
+ return NULL;
+ }
+
+ PyBytesWriter *writer = (PyBytesWriter*)PyMem_Malloc(sizeof(PyBytesWriter));
+ if (writer == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ writer->obj = NULL;
+ writer->size = 0;
+
+ if (size >= 1) {
+ if (_PyBytesWriter_Resize_impl(writer, size, 0) < 0) {
+ PyBytesWriter_Discard(writer);
+ return NULL;
+ }
+ writer->size = size;
+ }
+ return writer;
+}
+
+static inline PyObject*
+PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
+{
+ PyObject *result;
+ if (size == 0) {
+ result = PyBytes_FromStringAndSize("", 0);
+ }
+ else if (writer->obj != NULL) {
+ if (size != PyBytes_GET_SIZE(writer->obj)) {
+ if (_PyBytes_Resize(&writer->obj, size)) {
+ goto error;
+ }
+ }
+ result = writer->obj;
+ writer->obj = NULL;
+ }
+ else {
+ result = PyBytes_FromStringAndSize(writer->small_buffer, size);
+ }
+ PyBytesWriter_Discard(writer);
+ return result;
+
+error:
+ PyBytesWriter_Discard(writer);
+ return NULL;
+}
+
+static inline PyObject*
+PyBytesWriter_Finish(PyBytesWriter *writer)
+{
+ return PyBytesWriter_FinishWithSize(writer, writer->size);
+}
+
+static inline PyObject*
+PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
+{
+ Py_ssize_t size = (char*)buf - (char*)PyBytesWriter_GetData(writer);
+ if (size < 0 || size > _PyBytesWriter_GetAllocated(writer)) {
+ PyBytesWriter_Discard(writer);
+ PyErr_SetString(PyExc_ValueError, "invalid end pointer");
+ return NULL;
+ }
+
+ return PyBytesWriter_FinishWithSize(writer, size);
+}
+
+static inline int
+PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)
+{
+ if (size < 0) {
+ PyErr_SetString(PyExc_ValueError, "size must be >= 0");
+ return -1;
+ }
+ if (_PyBytesWriter_Resize_impl(writer, size, 1) < 0) {
+ return -1;
+ }
+ writer->size = size;
+ return 0;
+}
+
+static inline int
+PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t size)
+{
+ if (size < 0 && writer->size + size < 0) {
+ PyErr_SetString(PyExc_ValueError, "invalid size");
+ return -1;
+ }
+ if (size > PY_SSIZE_T_MAX - writer->size) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ size = writer->size + size;
+
+ if (_PyBytesWriter_Resize_impl(writer, size, 1) < 0) {
+ return -1;
+ }
+ writer->size = size;
+ return 0;
+}
+
+static inline void*
+PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer,
+ Py_ssize_t size, void *buf)
+{
+ Py_ssize_t pos = (char*)buf - (char*)PyBytesWriter_GetData(writer);
+ if (PyBytesWriter_Grow(writer, size) < 0) {
+ return NULL;
+ }
+ return (char*)PyBytesWriter_GetData(writer) + pos;
+}
+
+static inline int
+PyBytesWriter_WriteBytes(PyBytesWriter *writer,
+ const void *bytes, Py_ssize_t size)
+{
+ if (size < 0) {
+ size_t len = strlen((const char*)bytes);
+ if (len > (size_t)PY_SSIZE_T_MAX) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ size = (Py_ssize_t)len;
+ }
+
+ Py_ssize_t pos = writer->size;
+ if (PyBytesWriter_Grow(writer, size) < 0) {
+ return -1;
+ }
+ char *buf = (char*)PyBytesWriter_GetData(writer);
+ memcpy(buf + pos, bytes, (size_t)size);
+ return 0;
+}
+
+static inline int
+PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
+ Py_GCC_ATTRIBUTE((format(printf, 2, 3)));
+
+static inline int
+PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
+{
+ va_list vargs;
+ va_start(vargs, format);
+ PyObject *str = PyBytes_FromFormatV(format, vargs);
+ va_end(vargs);
+
+ if (str == NULL) {
+ return -1;
+ }
+ int res = PyBytesWriter_WriteBytes(writer,
+ PyBytes_AS_STRING(str),
+ PyBytes_GET_SIZE(str));
+ Py_DECREF(str);
+ return res;
+}
+#endif // PY_VERSION_HEX < 0x030F00A1
+
+
+#if PY_VERSION_HEX < 0x030F00A1
+static inline PyObject*
+PyTuple_FromArray(PyObject *const *array, Py_ssize_t size)
+{
+ PyObject *tuple = PyTuple_New(size);
+ if (tuple == NULL) {
+ return NULL;
+ }
+ for (Py_ssize_t i=0; i < size; i++) {
+ PyObject *item = array[i];
+ PyTuple_SET_ITEM(tuple, i, Py_NewRef(item));
+ }
+ return tuple;
+}
+#endif
+
+
+#if PY_VERSION_HEX < 0x030F00A1
+static inline Py_hash_t
+PyUnstable_Unicode_GET_CACHED_HASH(PyObject *op)
+{
+#ifdef PYPY_VERSION
+ (void)op; // unused argument
+ return -1;
+#elif PY_VERSION_HEX >= 0x03000000
+ return ((PyASCIIObject*)op)->hash;
+#else
+ return ((PyUnicodeObject*)op)->hash;
+#endif
+}
+#endif
+
+#if 0x030D0000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030F00A7 && !defined(PYPY_VERSION)
+// Immortal objects were implemented in Python 3.12, however there is no easy API
+// to make objects immortal until 3.14 which has _Py_SetImmortal(). Since
+// immortal objects are primarily needed for free-threading, this API is implemented
+// for 3.14 using _Py_SetImmortal() and uses private macros on 3.13.
+#if 0x030E0000 <= PY_VERSION_HEX
+PyAPI_FUNC(void) _Py_SetImmortal(PyObject *op);
+#endif
+
+static inline int
+PyUnstable_SetImmortal(PyObject *op)
+{
+ assert(op != NULL);
+ if (!PyUnstable_Object_IsUniquelyReferenced(op) || PyUnicode_Check(op)) {
+ return 0;
+ }
+#if 0x030E0000 <= PY_VERSION_HEX
+ _Py_SetImmortal(op);
+#else
+ // Python 3.13 doesn't export _Py_SetImmortal() function
+ if (PyObject_GC_IsTracked(op)) {
+ PyObject_GC_UnTrack(op);
+ }
+#ifdef Py_GIL_DISABLED
+ op->ob_tid = _Py_UNOWNED_TID;
+ op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL;
+ op->ob_ref_shared = 0;
+#else
+ op->ob_refcnt = _Py_IMMORTAL_REFCNT;
+#endif
+#endif
+ return 1;
+}
+#endif
#ifdef __cplusplus
}
diff --git a/runtests.py b/runtests.py
index 38507a1..fdee19e 100755
--- a/runtests.py
+++ b/runtests.py
@@ -4,15 +4,15 @@
Usage::
- python3 test_matrix.py
- python3 test_matrix.py -v # verbose mode
+ python3 runtests.py
+ python3 runtests.py --verbose
+ python3 runtests.py --current --verbose
"""
from __future__ import absolute_import
from __future__ import print_function
import argparse
import os.path
import shutil
-import subprocess
import sys
try:
from shutil import which
@@ -29,11 +29,10 @@
TEST_UPGRADE = os.path.join(TEST_DIR, "test_upgrade_pythoncapi.py")
PYTHONS = (
+ # CPython
"python3-debug",
"python3",
"python2.7",
- "python3.4",
- "python3.5",
"python3.6",
"python3.7",
"python3.8",
@@ -41,6 +40,14 @@
"python3.10",
"python3.11",
"python3.12",
+ "python3.13",
+ "python3.13t",
+ "python3.14",
+ "python3.14t",
+ "python3.15",
+ "python3.15t",
+
+ # PyPy
"pypy",
"pypy2",
"pypy2.7",
@@ -50,6 +57,7 @@
"pypy3.8",
"pypy3.9",
"pypy3.10",
+ "pypy3.11",
)
diff --git a/tests/setup.py b/tests/setup.py
index 4deaa36..b25bc8d 100755
--- a/tests/setup.py
+++ b/tests/setup.py
@@ -13,42 +13,68 @@
# C++ is only supported on Python 3.6 and newer
-TEST_CPP = (sys.version_info >= (3, 6))
-if 0x30b0000 <= sys.hexversion <= 0x30b00b3:
- # Don't test C++ on Python 3.11b1 - 3.11b3: these versions have C++
- # compatibility issues.
- TEST_CPP = False
+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',
+ # Formatting checks
+ '-Wformat',
+ '-Wformat-nonliteral',
+ '-Wformat-security',
+ ))
+ CFLAGS = COMMON_FLAGS
+else:
+ # C compiler flags for MSVC
+ COMMON_FLAGS.extend((
+ # Treat all compiler warnings as compiler errors
+ '/WX',
))
+ # Python 3.11 and older emits C4100 "unreferenced parameter" warnings
+ # on Py_UNUSED() parameters. Py_UNUSED() was modified in Python 3.12
+ # to support MSVC.
+ if sys.version_info >= (3, 12):
+ COMMON_FLAGS.extend((
+ # Display warnings level 1 to 4
+ '/W4',
+ ))
+ CFLAGS = list(COMMON_FLAGS)
+CXXFLAGS = list(COMMON_FLAGS)
+
+if not MSVC:
+ C_VERSIONS = ('c99', 'c11')
+else:
+ # MSVC doesn't support /std:c99 flag
+ C_VERSIONS = ('c11',)
+
+if not MSVC:
+ CXX_VERSIONS = [
+ ('test_pythoncapi_compat_cpp03ext', ['-std=c++03']),
+ ('test_pythoncapi_compat_cpp11ext', ['-std=c++11']),
+ ('test_pythoncapi_compat_cpp14ext', ['-std=c++14']),
+ ('test_pythoncapi_compat_cpp17ext', ['-std=c++17']),
+ ('test_pythoncapi_compat_cpp20ext', ['-std=c++20']),
+ ]
+else:
+ # MSVC doesn't support /std:c++11
+ CXX_VERSIONS = [
+ ('test_pythoncapi_compat_cppext', None),
+ ('test_pythoncapi_compat_cpp14ext', ['/std:c++14', '/Zc:__cplusplus']),
+ ]
def main():
@@ -70,34 +96,25 @@ 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)
- extensions = [c_ext]
-
- if TEST_CPP:
- # C++ extension
-
- # MSVC has /std flag but doesn't support /std:c++11
+ extensions = []
+ for std in C_VERSIONS:
if not MSVC:
- versions = [
- ('test_pythoncapi_compat_cpp03ext', '-std=c++03'),
- ('test_pythoncapi_compat_cpp11ext', '-std=c++11'),
- ]
+ cflags = CFLAGS + ['-std=%s' % std]
else:
- versions = [('test_pythoncapi_compat_cppext', None)]
- for name, flag in versions:
- flags = list(cppflags)
- if flag is not None:
- flags.append(flag)
+ cflags = CFLAGS + ['/std:%s' % std]
+ c_ext = Extension(
+ 'test_pythoncapi_compat_cext_%s' % std,
+ sources=['test_pythoncapi_compat_cext.c'],
+ extra_compile_args=cflags)
+ extensions.append(c_ext)
+
+ if TEST_CXX:
+ # C++ extension
+ for name, std_flags in CXX_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 948906c..93005cd 100644
--- a/tests/test_pythoncapi_compat.py
+++ b/tests/test_pythoncapi_compat.py
@@ -19,17 +19,43 @@
except ImportError:
# Python 2
faulthandler = None
+try:
+ import sysconfig
+except ImportError:
+ # Python 3.1 and older
+ sysconfig = None
# test.utils
from utils import run_command, command_stdout
-TESTS = [
- ("test_pythoncapi_compat_cext", "C"),
- ("test_pythoncapi_compat_cppext", "C++"),
- ("test_pythoncapi_compat_cpp03ext", "C++03"),
- ("test_pythoncapi_compat_cpp11ext", "C++11"),
-]
+# Windows uses MSVC compiler
+MSVC = (os.name == "nt")
+
+# C++ is only supported on Python 3.6 and newer
+TEST_CXX = (sys.version_info >= (3, 6))
+
+if not MSVC:
+ C_TESTS = [
+ ("test_pythoncapi_compat_cext_c99", "C99"),
+ ("test_pythoncapi_compat_cext_c11", "C11"),
+ ]
+ CXX_TESTS = [
+ ("test_pythoncapi_compat_cpp03ext", "C++03"),
+ ("test_pythoncapi_compat_cpp11ext", "C++11"),
+ ("test_pythoncapi_compat_cpp14ext", "C++14"),
+ ("test_pythoncapi_compat_cpp17ext", "C++17"),
+ ("test_pythoncapi_compat_cpp20ext", "C++20"),
+ ]
+else:
+ C_TESTS = [
+ ("test_pythoncapi_compat_cext_c11", "C11"),
+ ]
+ CXX_TESTS = [
+ ("test_pythoncapi_compat_cppext", "C++"),
+ ("test_pythoncapi_compat_cpp14ext", "C++14"),
+ ]
+
VERBOSE = False
@@ -38,9 +64,7 @@ def display_title(title):
if not VERBOSE:
return
- ver = sys.version_info
- title = "Python %s.%s: %s" % (ver.major, ver.minor, title)
-
+ title = "%s: %s" % (python_version(), title)
print(title)
print("=" * len(title))
print()
@@ -70,9 +94,12 @@ def import_tests(module_name):
if not pythonpath:
raise Exception("Failed to find the build directory")
- sys.path.append(pythonpath)
-
- return __import__(module_name)
+ old_sys_path = list(sys.path)
+ try:
+ sys.path.append(pythonpath)
+ return __import__(module_name)
+ finally:
+ sys.path[:] = old_sys_path
def _run_tests(tests, verbose):
@@ -83,10 +110,13 @@ def _run_tests(tests, verbose):
test_func()
+_HAS_CLEAR_INTERNAL_CACHES = hasattr(sys, '_clear_internal_caches')
_HAS_CLEAR_TYPE_CACHE = hasattr(sys, '_clear_type_cache')
def _refleak_cleanup():
- if _HAS_CLEAR_TYPE_CACHE:
+ if _HAS_CLEAR_INTERNAL_CACHES:
+ sys._clear_internal_caches()
+ elif _HAS_CLEAR_TYPE_CACHE:
sys._clear_type_cache()
gc.collect()
@@ -123,25 +153,22 @@ def python_version():
python_impl = "PyPy"
else:
python_impl = 'Python'
- return "%s %s.%s (%s build)" % (python_impl, ver.major, ver.minor, build)
+ pyver = "%s.%s" % (ver.major, ver.minor)
+ if ver >= (3, 13):
+ if sysconfig.get_config_var('Py_GIL_DISABLED'):
+ # Free-threaded build
+ pyver += "t"
+ return "%s %s (%s build)" % (python_impl, pyver, build)
def run_tests(module_name, lang):
+ if VERBOSE:
+ print("")
+
title = "Test %s (%s)" % (module_name, lang)
display_title(title)
- try:
- testmod = import_tests(module_name)
- except ImportError:
- # The C extension must always be available
- if lang == "C":
- raise
-
- if VERBOSE:
- print("%s: skip %s, missing %s extension"
- % (python_version(), lang, module_name))
- print()
- return
+ testmod = import_tests(module_name)
if VERBOSE:
empty_line = False
@@ -188,6 +215,10 @@ def main():
global VERBOSE
VERBOSE = ("-v" in sys.argv[1:] or "--verbose" in sys.argv[1:])
+ if (3, 13) <= sys.version_info <= (3, 13, 0, 'alpha', 4):
+ print("SKIP Python 3.13 alpha 1..4: not supported!")
+ return
+
if faulthandler is not None:
faulthandler.enable()
@@ -197,7 +228,10 @@ def main():
build_ext()
- for module_name, lang in TESTS:
+ tests = list(C_TESTS)
+ if TEST_CXX:
+ tests += CXX_TESTS
+ for module_name, lang in tests:
run_tests(module_name, lang)
diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c
index 78ee1c8..1074266 100644
--- a/tests/test_pythoncapi_compat_cext.c
+++ b/tests/test_pythoncapi_compat_cext.c
@@ -2,6 +2,7 @@
#undef NDEBUG
#include "pythoncapi_compat.h"
+#include // T_SHORT, READONLY
#ifdef NDEBUG
# error "assertions must be enabled"
@@ -15,12 +16,24 @@
# define PYTHON3 1
#endif
-#if defined(_MSC_VER) && defined(__cplusplus)
-# define MODULE_NAME test_pythoncapi_compat_cppext
-#elif defined(__cplusplus) && __cplusplus >= 201103
+#if defined(__cplusplus) && __cplusplus >= 202002L
+# define MODULE_NAME test_pythoncapi_compat_cpp20ext
+#elif defined(__cplusplus) && __cplusplus >= 201703L
+# define MODULE_NAME test_pythoncapi_compat_cpp17ext
+#elif defined(__cplusplus) && __cplusplus >= 201402L
+# define MODULE_NAME test_pythoncapi_compat_cpp14ext
+#elif defined(__cplusplus) && __cplusplus >= 201103L
# 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
+#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
+# define MODULE_NAME test_pythoncapi_compat_cext_c23
+#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+# define MODULE_NAME test_pythoncapi_compat_cext_c11
+#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+# define MODULE_NAME test_pythoncapi_compat_cext_c99
#else
# define MODULE_NAME test_pythoncapi_compat_cext
#endif
@@ -53,6 +66,7 @@
// Marker to check that pointer value was set
static const char uninitialized[] = "uninitialized";
#define UNINITIALIZED_OBJ ((PyObject *)uninitialized)
+#define UNINITIALIZED_INT 0x83ff979
static PyObject*
@@ -255,7 +269,10 @@ test_frame(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored))
// test PyFrame_GetLocals()
PyObject *locals = PyFrame_GetLocals(frame);
assert(locals != _Py_NULL);
+ // Python 3.13 creates a local proxy
+#if PY_VERSION_HEX < 0x030D0000
assert(PyDict_Check(locals));
+#endif
// test PyFrame_GetGlobals()
PyObject *globals = PyFrame_GetGlobals(frame);
@@ -752,7 +769,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);
@@ -1253,6 +1270,138 @@ test_dict_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
}
+static PyObject *
+test_dict_pop(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ PyObject *dict = PyDict_New();
+ if (dict == NULL) {
+ return NULL;
+ }
+
+ PyObject *key = PyUnicode_FromString("key");
+ assert(key != NULL);
+ PyObject *value = PyUnicode_FromString("abc");
+ assert(value != NULL);
+
+ // test PyDict_Pop(), get the removed value, key is present
+ assert(PyDict_SetItem(dict, key, value) == 0);
+ PyObject *removed = UNINITIALIZED_OBJ;
+ assert(PyDict_Pop(dict, key, &removed) == 1);
+ assert(removed == value);
+ Py_DECREF(removed);
+
+ // test PyDict_Pop(), ignore the removed value, key is present
+ assert(PyDict_SetItem(dict, key, value) == 0);
+ assert(PyDict_Pop(dict, key, NULL) == 1);
+
+ // test PyDict_Pop(), key is missing
+ removed = UNINITIALIZED_OBJ;
+ assert(PyDict_Pop(dict, key, &removed) == 0);
+ assert(removed == NULL);
+ assert(PyDict_Pop(dict, key, NULL) == 0);
+
+ // test PyDict_PopString(), get the removed value, key is present
+ assert(PyDict_SetItem(dict, key, value) == 0);
+ removed = UNINITIALIZED_OBJ;
+ assert(PyDict_PopString(dict, "key", &removed) == 1);
+ assert(removed == value);
+ Py_DECREF(removed);
+
+ // test PyDict_PopString(), ignore the removed value, key is present
+ assert(PyDict_SetItem(dict, key, value) == 0);
+ assert(PyDict_PopString(dict, "key", NULL) == 1);
+
+ // test PyDict_PopString(), key is missing
+ removed = UNINITIALIZED_OBJ;
+ assert(PyDict_PopString(dict, "key", &removed) == 0);
+ assert(removed == NULL);
+ assert(PyDict_PopString(dict, "key", NULL) == 0);
+
+ // dict error
+ removed = UNINITIALIZED_OBJ;
+ assert(PyDict_Pop(key, key, &removed) == -1);
+ assert(removed == NULL);
+ assert(PyErr_ExceptionMatches(PyExc_SystemError));
+ PyErr_Clear();
+
+ assert(PyDict_Pop(key, key, NULL) == -1);
+ assert(PyErr_ExceptionMatches(PyExc_SystemError));
+ PyErr_Clear();
+
+ removed = UNINITIALIZED_OBJ;
+ assert(PyDict_PopString(key, "key", &removed) == -1);
+ assert(removed == NULL);
+ assert(PyErr_ExceptionMatches(PyExc_SystemError));
+ PyErr_Clear();
+
+ assert(PyDict_PopString(key, "key", NULL) == -1);
+ assert(PyErr_ExceptionMatches(PyExc_SystemError));
+ PyErr_Clear();
+
+ // exit
+ Py_DECREF(dict);
+ Py_DECREF(key);
+ Py_DECREF(value);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *
+test_dict_setdefault(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ PyObject *dict = PyDict_New();
+ if (dict == NULL) {
+ return NULL;
+ }
+ PyObject *key = PyUnicode_FromString("key");
+ assert(key != NULL);
+ PyObject *value = PyUnicode_FromString("abc");
+ assert(value != NULL);
+ PyObject *invalid_key = PyList_New(0); // not hashable key
+ assert(invalid_key != NULL);
+
+ // insert item
+ PyObject *result = UNINITIALIZED_OBJ;
+ assert(PyDict_SetDefaultRef(dict, key, value, &result) == 0);
+ assert(result == value);
+ Py_DECREF(result);
+
+ // item already present
+ result = UNINITIALIZED_OBJ;
+ assert(PyDict_SetDefaultRef(dict, key, value, &result) == 1);
+ assert(result == value);
+ Py_DECREF(result);
+
+ // error: invalid key
+ assert(!PyErr_Occurred());
+ result = UNINITIALIZED_OBJ;
+ assert(PyDict_SetDefaultRef(dict, invalid_key, value, &result) == -1);
+ assert(result == NULL);
+ assert(PyErr_Occurred());
+ PyErr_Clear();
+
+ // insert item with NULL result
+ assert(PyDict_Pop(dict, key, NULL) == 1);
+ assert(PyDict_SetDefaultRef(dict, key, value, NULL) == 0);
+
+ // item already present with NULL result
+ assert(PyDict_SetDefaultRef(dict, key, value, NULL) == 1);
+
+ // error: invalid key with NULL result
+ assert(!PyErr_Occurred());
+ assert(PyDict_SetDefaultRef(dict, invalid_key, value, NULL) == -1);
+ assert(PyErr_Occurred());
+ PyErr_Clear();
+
+ // exit
+ Py_DECREF(dict);
+ Py_DECREF(key);
+ Py_DECREF(value);
+ Py_DECREF(invalid_key);
+ Py_RETURN_NONE;
+}
+
+
static PyObject *
test_long_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
{
@@ -1278,14 +1427,87 @@ test_long_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
PyErr_Clear();
Py_DECREF(obj2);
+ // test PyLong_GetSign()
+ int sign = UNINITIALIZED_INT;
+ 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);
+
+#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);
+ if (writer == NULL) {
+ return NULL;
+ }
+ PyLongWriter_Discard(writer);
+
+ writer = PyLongWriter_Create(1, 1, (void **)&digits);
+ if (writer == NULL) {
+ return NULL;
+ }
+ digits[0] = 123;
+ obj = PyLongWriter_Finish(writer);
+ if (obj == NULL) {
+ return NULL;
+ }
+
+ check_int(obj, -123);
+ if (PyLong_Export(obj, &long_export) < 0) {
+ return NULL;
+ }
+ assert(long_export.value == -123);
+ assert(long_export.digits == NULL);
+ PyLong_FreeExport(&long_export);
+ Py_DECREF(obj);
+
+ writer = PyLongWriter_Create(0, 5, (void **)&digits);
+ if (writer == NULL) {
+ return NULL;
+ }
+ digits[0] = 1;
+ digits[1] = 0;
+ digits[2] = 0;
+ digits[3] = 0;
+ digits[4] = 1;
+ obj = PyLongWriter_Finish(writer);
+ if (obj == NULL) {
+ return NULL;
+ }
+
+ if (PyLong_Export(obj, &long_export) < 0) {
+ return NULL;
+ }
+ 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;
}
// --- 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 {
@@ -1296,12 +1518,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;
}
@@ -1334,6 +1558,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;
@@ -1361,70 +1586,1061 @@ test_managed_dict(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
#endif // PY_VERSION_HEX >= 0x030B00A3
-static struct PyMethodDef methods[] = {
- {"test_object", test_object, METH_NOARGS, _Py_NULL},
- {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL},
+static PyObject *
+test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ PyObject *abc = PyUnicode_FromString("abc");
+ if (abc == NULL) {
+ return NULL;
+ }
+
+ PyObject *abc0def = PyUnicode_FromStringAndSize("abc\0def", 7);
+ if (abc0def == NULL) {
+ Py_DECREF(abc);
+ return NULL;
+ }
+
+ // PyUnicode_EqualToUTF8() and PyUnicode_EqualToUTF8AndSize() can be called
+ // with an exception raised and they must not clear the current exception.
+ PyErr_NoMemory();
+
+ assert(PyUnicode_EqualToUTF8AndSize(abc, "abc", 3) == 1);
+ assert(PyUnicode_EqualToUTF8AndSize(abc, "Python", 6) == 0);
+ assert(PyUnicode_EqualToUTF8AndSize(abc0def, "abc\0def", 7) == 1);
+
+ assert(PyUnicode_EqualToUTF8(abc, "abc") == 1);
+ assert(PyUnicode_EqualToUTF8(abc, "Python") == 0);
+ assert(PyUnicode_EqualToUTF8(abc0def, "abc\0def") == 0);
+
+ 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();
+
+ // Test PyUnstable_Unicode_GET_CACHED_HASH()
+#ifdef PYPY_VERSION
+ assert(PyUnstable_Unicode_GET_CACHED_HASH(abc) == -1);
+#else
+ Py_hash_t hash = PyObject_Hash(abc);
+ assert(hash != -1);
+ assert(PyUnstable_Unicode_GET_CACHED_HASH(abc) == hash);
+#endif
+
+ Py_DECREF(abc);
+ Py_DECREF(abc0def);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *
+test_list(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ PyObject *list = PyList_New(0);
+ if (list == NULL) {
+ return NULL;
+ }
+
+ // test PyList_Extend()
+ {
+ PyObject *abc = PyUnicode_FromString("abc");
+ if (abc == NULL) {
+ Py_DECREF(list);
+ return NULL;
+ }
+
+ assert(PyList_Extend(list, abc) == 0);
+ Py_DECREF(abc);
+ assert(PyList_GET_SIZE(list) == 3);
+ }
+
+ // test PyList_GetItemRef()
+ PyObject *item = PyList_GetItemRef(list, 1);
+ assert(item != NULL);
+ assert(item == PyList_GetItem(list, 1));
+ Py_DECREF(item);
+
+ // test PyList_Clear()
+ assert(PyList_Clear(list) == 0);
+ assert(PyList_GET_SIZE(list) == 0);
+
+ Py_DECREF(list);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *
+test_hash(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ void *ptr0 = NULL;
+ assert(Py_HashPointer(ptr0) == 0);
+
#ifndef PYPY_VERSION
- {"test_frame", test_frame, METH_NOARGS, _Py_NULL},
+#if SIZEOF_VOID_P == 8
+ void *ptr1 = (void*)(uintptr_t)0xABCDEF1234567890;
+ assert(Py_HashPointer(ptr1) == (uintptr_t)0x0ABCDEF123456789);
+#else
+ void *ptr1 = (void*)(uintptr_t)0xDEADCAFE;
+ assert(Py_HashPointer(ptr1) == (uintptr_t)0xEDEADCAF);
#endif
- {"test_thread_state", test_thread_state, METH_NOARGS, _Py_NULL},
- {"test_interpreter", test_interpreter, METH_NOARGS, _Py_NULL},
- {"test_calls", test_calls, METH_NOARGS, _Py_NULL},
- {"test_gc", test_gc, METH_NOARGS, _Py_NULL},
- {"test_module", test_module, METH_NOARGS, _Py_NULL},
-#if (PY_VERSION_HEX <= 0x030B00A1 || 0x030B00A7 <= PY_VERSION_HEX) && !defined(PYPY_VERSION)
- {"test_float_pack", test_float_pack, METH_NOARGS, _Py_NULL},
+#else
+ // PyPy
+#if SIZEOF_VOID_P == 8
+ void *ptr1 = (void*)(uintptr_t)0xABCDEF1234567890;
+#else
+ void *ptr1 = (void*)(uintptr_t)0xDEADCAFE;
#endif
-#ifndef PYPY_VERSION
- {"test_code", test_code, METH_NOARGS, _Py_NULL},
+ assert(Py_HashPointer(ptr1) == (Py_hash_t)ptr1);
#endif
- {"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL},
- {"test_import", test_import, METH_NOARGS, _Py_NULL},
- {"test_weakref", test_weakref, METH_NOARGS, _Py_NULL},
- {"func_varargs", (PyCFunction)(void*)func_varargs, METH_VARARGS | METH_KEYWORDS, _Py_NULL},
- {"test_vectorcall", test_vectorcall, METH_NOARGS, _Py_NULL},
- {"test_getattr", test_getattr, METH_NOARGS, _Py_NULL},
- {"test_getitem", test_getitem, METH_NOARGS, _Py_NULL},
- {"test_dict_api", test_dict_api, METH_NOARGS, _Py_NULL},
- {"test_long_api", test_long_api, METH_NOARGS, _Py_NULL},
-#ifdef TEST_MANAGED_DICT
- {"test_managed_dict", test_managed_dict, METH_NOARGS, _Py_NULL},
+
+#if ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \
+ || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \
+ && PYPY_VERSION_NUM >= 0x07030800))
+ // Just check that constants are available
+ size_t bits = PyHASH_BITS;
+ assert(bits >= 8);
+ size_t mod = PyHASH_MODULUS;
+ assert(mod >= 7);
+ size_t inf = PyHASH_INF;
+ assert(inf != 0);
+ size_t imag = PyHASH_IMAG;
+ assert(imag != 0);
#endif
- {_Py_NULL, _Py_NULL, 0, _Py_NULL}
-};
+ // 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);
-static int
-module_exec(PyObject *module)
+ Py_DECREF(abc);
+ }
+
+ Py_RETURN_NONE;
+}
+
+
+#if PY_VERSION_HEX >= 0x03050000
+#define TEST_PYTIME
+
+static PyObject *
+test_time(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
{
-#ifdef __cplusplus
- if (PyModule_AddIntMacro(module, __cplusplus)) {
- return -1;
+ PyTime_t t;
+#define UNINITIALIZED_TIME ((PyTime_t)-483884113929936179)
+
+ t = UNINITIALIZED_TIME;
+ assert(PyTime_Time(&t) == 0);
+ assert(t != UNINITIALIZED_TIME);
+
+ t = UNINITIALIZED_TIME;
+ assert(PyTime_Monotonic(&t) == 0);
+ assert(t != UNINITIALIZED_TIME);
+
+ // Test multiple times since an implementation uses a cache
+ for (int i=0; i < 5; i++) {
+ t = UNINITIALIZED_TIME;
+ assert(PyTime_PerfCounter(&t) == 0);
+ assert(t != UNINITIALIZED_TIME);
}
+
+ assert(PyTime_AsSecondsDouble(1) == 1e-9);
+ assert(PyTime_AsSecondsDouble(1500 * 1000 * 1000) == 1.5);
+ assert(PyTime_AsSecondsDouble(-500 * 1000 * 1000) == -0.5);
+
+ Py_RETURN_NONE;
+}
#endif
- if (PyModule_AddStringMacro(module, PY_VERSION)) {
- return -1;
+
+
+static void
+check_get_constant(PyObject* (*get_constant)(unsigned int), int borrowed)
+{
+#define CLEAR(var) if (!borrowed) { Py_DECREF(var); }
+
+ PyObject *obj, *expected;
+
+ // Py_CONSTANT_NONE
+ obj = get_constant(Py_CONSTANT_NONE);
+ assert(obj == Py_None);
+ CLEAR(obj);
+
+ // Py_CONSTANT_FALSE
+ obj = get_constant(Py_CONSTANT_FALSE);
+ assert(obj == Py_False);
+ CLEAR(obj);
+
+ // Py_CONSTANT_TRUE
+ obj = get_constant(Py_CONSTANT_TRUE);
+ assert(obj == Py_True);
+ CLEAR(obj);
+
+ // Py_CONSTANT_ELLIPSIS
+ obj = get_constant(Py_CONSTANT_ELLIPSIS);
+ assert(obj == Py_Ellipsis);
+ CLEAR(obj);
+
+ // Py_CONSTANT_NOT_IMPLEMENTED
+ obj = get_constant(Py_CONSTANT_NOT_IMPLEMENTED);
+ assert(obj == Py_NotImplemented);
+ CLEAR(obj);
+
+ // Py_CONSTANT_ZERO
+ obj = get_constant(Py_CONSTANT_ZERO);
+ expected = PyLong_FromLong(0);
+ assert(expected != NULL);
+ assert(Py_TYPE(obj) == &PyLong_Type);
+ assert(PyObject_RichCompareBool(obj, expected, Py_EQ) == 1);
+ CLEAR(obj);
+ Py_DECREF(expected);
+
+ // Py_CONSTANT_ONE
+ obj = get_constant(Py_CONSTANT_ONE);
+ expected = PyLong_FromLong(1);
+ assert(expected != NULL);
+ assert(Py_TYPE(obj) == &PyLong_Type);
+ assert(PyObject_RichCompareBool(obj, expected, Py_EQ) == 1);
+ CLEAR(obj);
+ Py_DECREF(expected);
+
+ // Py_CONSTANT_EMPTY_STR
+ obj = get_constant(Py_CONSTANT_EMPTY_STR);
+ assert(Py_TYPE(obj) == &PyUnicode_Type);
+#if PY_VERSION_HEX >= 0x03030000
+ assert(PyUnicode_GetLength(obj) == 0);
+#else
+ assert(PyUnicode_GetSize(obj) == 0);
+#endif
+ CLEAR(obj);
+
+ // Py_CONSTANT_EMPTY_BYTES
+ obj = get_constant(Py_CONSTANT_EMPTY_BYTES);
+ assert(Py_TYPE(obj) == &PyBytes_Type);
+ assert(PyBytes_Size(obj) == 0);
+ CLEAR(obj);
+
+ // Py_CONSTANT_EMPTY_TUPLE
+ obj = get_constant(Py_CONSTANT_EMPTY_TUPLE);
+ assert(Py_TYPE(obj) == &PyTuple_Type);
+ assert(PyTuple_Size(obj) == 0);
+ CLEAR(obj);
+
+#undef CLEAR
+}
+
+
+static PyObject *
+test_get_constant(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ check_get_constant(Py_GetConstant, 0);
+ check_get_constant(Py_GetConstantBorrowed, 1);
+ Py_RETURN_NONE;
+}
+
+
+#if PY_VERSION_HEX < 0x030E0000 && PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION)
+#define TEST_UNICODEWRITER 1
+
+static PyObject *
+test_unicodewriter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
+{
+ PyUnicodeWriter *writer = PyUnicodeWriter_Create(0);
+ if (writer == NULL) {
+ return NULL;
}
- if (PyModule_AddIntMacro(module, PY_VERSION_HEX)) {
- return -1;
+ int ret;
+
+ // test PyUnicodeWriter_WriteStr()
+ PyObject *str = PyUnicode_FromString("var");
+ if (str == NULL) {
+ goto error;
}
-#ifdef PYPY_VERSION
- if (PyModule_AddStringMacro(module, PYPY_VERSION)) {
- return -1;
+ ret = PyUnicodeWriter_WriteStr(writer, str);
+ Py_CLEAR(str);
+ if (ret < 0) {
+ goto error;
}
-#endif
-#ifdef PYPY_VERSION_NUM
- if (PyModule_AddIntMacro(module, PYPY_VERSION_NUM)) {
- return -1;
+
+ // test PyUnicodeWriter_WriteChar()
+ if (PyUnicodeWriter_WriteChar(writer, '=') < 0) {
+ goto error;
}
-#endif
- return 0;
+
+ // test PyUnicodeWriter_WriteSubstring()
+ str = PyUnicode_FromString("[long]");
+ if (str == NULL) {
+ goto error;
+ }
+ ret = PyUnicodeWriter_WriteSubstring(writer, str, 1, 5);
+ Py_CLEAR(str);
+ if (ret < 0) {
+ 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;
+ }
+ if (PyUnicodeWriter_WriteChar(writer, ' ') < 0) {
+ goto error;
+ }
+
+ // test PyUnicodeWriter_WriteRepr()
+ str = PyUnicode_FromString("repr");
+ if (str == NULL) {
+ goto error;
+ }
+ if (PyUnicodeWriter_WriteRepr(writer, str) < 0) {
+ goto error;
+ }
+ Py_CLEAR(str);
+ if (PyUnicodeWriter_WriteRepr(writer, NULL) < 0) {
+ goto error;
+ }
+
+ {
+ PyObject *result = PyUnicodeWriter_Finish(writer);
+ if (result == NULL) {
+ return NULL;
+ }
+ const char *expected = "var=long non-ASCII valu\xC3\xA9 'repr'";
+ assert(PyUnicode_EqualToUTF8(result, expected));
+ Py_DECREF(result);
+ }
+
+ Py_RETURN_NONE;
+
+error:
+ PyUnicodeWriter_Discard(writer);
+ return NULL;
}
-#if PY_VERSION_HEX >= 0x03050000
-static PyModuleDef_Slot module_slots[] = {
- {Py_mod_exec, _Py_CAST(void*, module_exec)},
+static PyObject *
+test_unicodewriter_widechar(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
+{
+ PyUnicodeWriter *writer = PyUnicodeWriter_Create(0);
+ if (writer == NULL) {
+ return NULL;
+ }
+
+ // test PyUnicodeWriter_WriteWideChar()
+ int ret = PyUnicodeWriter_WriteWideChar(writer, L"euro=\u20AC", -1);
+ if (ret < 0) {
+ goto error;
+ }
+
+ {
+ PyObject *result = PyUnicodeWriter_Finish(writer);
+ if (result == NULL) {
+ return NULL;
+ }
+ assert(PyUnicode_EqualToUTF8(result, "euro=\xe2\x82\xac"));
+ Py_DECREF(result);
+ }
+
+ Py_RETURN_NONE;
+
+error:
+ PyUnicodeWriter_Discard(writer);
+ return NULL;
+}
+
+
+static PyObject *
+test_unicodewriter_format(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
+{
+ PyUnicodeWriter *writer = PyUnicodeWriter_Create(0);
+ if (writer == NULL) {
+ return NULL;
+ }
+
+ // test PyUnicodeWriter_Format()
+ if (PyUnicodeWriter_Format(writer, "%s %i", "Hello", 123) < 0) {
+ goto error;
+ }
+
+ // test PyUnicodeWriter_WriteChar()
+ if (PyUnicodeWriter_WriteChar(writer, '.') < 0) {
+ goto error;
+ }
+
+ {
+ PyObject *result = PyUnicodeWriter_Finish(writer);
+ if (result == NULL) {
+ return NULL;
+ }
+ assert(PyUnicode_EqualToUTF8(result, "Hello 123."));
+ Py_DECREF(result);
+ }
+
+ Py_RETURN_NONE;
+
+error:
+ PyUnicodeWriter_Discard(writer);
+ return NULL;
+}
+#endif
+
+static PyObject *
+test_uniquely_referenced(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
+{
+ PyObject *obj = Py_BuildValue("(s, s)", "hello", "world");
+ if (obj == NULL) {
+ return NULL;
+ }
+
+ assert(PyUnstable_Object_IsUniquelyReferenced(obj));
+
+ Py_INCREF(obj);
+
+ assert(!PyUnstable_Object_IsUniquelyReferenced(obj));
+
+ Py_DECREF(obj);
+ Py_DECREF(obj);
+
+ Py_RETURN_NONE;
+}
+
+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;
+ }
+ 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 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 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 PyObject *
+test_structmember(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ assert(Py_T_SHORT == T_SHORT);
+ assert(Py_T_SHORT == T_SHORT);
+ assert(Py_T_INT == T_INT);
+ assert(Py_T_LONG == T_LONG);
+ assert(Py_T_FLOAT == T_FLOAT);
+ assert(Py_T_DOUBLE == T_DOUBLE);
+ assert(Py_T_STRING == T_STRING);
+ assert(_Py_T_OBJECT == T_OBJECT);
+ assert(Py_T_CHAR == T_CHAR);
+ assert(Py_T_BYTE == T_BYTE);
+ assert(Py_T_UBYTE == T_UBYTE);
+ assert(Py_T_USHORT == T_USHORT);
+ assert(Py_T_UINT == T_UINT);
+ assert(Py_T_ULONG == T_ULONG);
+ assert(Py_T_STRING_INPLACE == T_STRING_INPLACE);
+ assert(Py_T_BOOL == T_BOOL);
+ assert(Py_T_OBJECT_EX == T_OBJECT_EX);
+ assert(Py_T_LONGLONG == T_LONGLONG);
+ assert(Py_T_ULONGLONG == T_ULONGLONG);
+ assert(Py_T_PYSSIZET == T_PYSSIZET);
+#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION)
+ assert(_Py_T_NONE == T_NONE);
+#endif
+ assert(Py_READONLY == READONLY);
+ assert(Py_AUDIT_READ == READ_RESTRICTED);
+ assert(_Py_WRITE_RESTRICTED == PY_WRITE_RESTRICTED);
+
+ Py_RETURN_NONE;
+}
+
+
+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;
+}
+
+
+#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);
+
+ // 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();
+
+ // 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 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 int
+test_byteswriter_highlevel(void)
+{
+ PyObject *obj;
+ PyBytesWriter *writer = PyBytesWriter_Create(0);
+ if (writer == NULL) {
+ goto error;
+ }
+ if (PyBytesWriter_WriteBytes(writer, "Hello", -1) < 0) {
+ goto error;
+ }
+ if (PyBytesWriter_Format(writer, " %s!", "World") < 0) {
+ goto error;
+ }
+
+ obj = PyBytesWriter_Finish(writer);
+ if (obj == NULL) {
+ return -1;
+ }
+ assert(PyBytes_Check(obj));
+ assert(strcmp(PyBytes_AS_STRING(obj), "Hello World!") == 0);
+ Py_DECREF(obj);
+ return 0;
+
+error:
+ PyBytesWriter_Discard(writer);
+ return -1;
+}
+
+static int
+test_byteswriter_abc(void)
+{
+ PyBytesWriter *writer = PyBytesWriter_Create(3);
+ if (writer == NULL) {
+ return -1;
+ }
+
+ char *str = (char*)PyBytesWriter_GetData(writer);
+ memcpy(str, "abc", 3);
+
+ PyObject *obj = PyBytesWriter_Finish(writer);
+ if (obj == NULL) {
+ return -1;
+ }
+ assert(PyBytes_Check(obj));
+ assert(strcmp(PyBytes_AS_STRING(obj), "abc") == 0);
+ Py_DECREF(obj);
+ return 0;
+}
+
+static int
+test_byteswriter_grow(void)
+{
+ PyBytesWriter *writer = PyBytesWriter_Create(10);
+ if (writer == NULL) {
+ return -1;
+ }
+
+ char *buf = (char*)PyBytesWriter_GetData(writer);
+ memcpy(buf, "Hello ", strlen("Hello "));
+ buf += strlen("Hello ");
+
+ buf = (char*)PyBytesWriter_GrowAndUpdatePointer(writer, 10, buf);
+ if (buf == NULL) {
+ PyBytesWriter_Discard(writer);
+ return -1;
+ }
+
+ memcpy(buf, "World", strlen("World"));
+ buf += strlen("World");
+
+ PyObject *obj = PyBytesWriter_FinishWithPointer(writer, buf);
+ if (obj == NULL) {
+ return -1;
+ }
+ assert(PyBytes_Check(obj));
+ assert(strcmp(PyBytes_AS_STRING(obj), "Hello World") == 0);
+ Py_DECREF(obj);
+ return 0;
+}
+
+static PyObject *
+test_byteswriter(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ if (test_byteswriter_highlevel() < 0) {
+ return NULL;
+ }
+ if (test_byteswriter_abc() < 0) {
+ return NULL;
+ }
+ if (test_byteswriter_grow() < 0) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+
+static PyObject*
+test_tuple_fromarray(void)
+{
+ PyObject* array[] = {
+ PyLong_FromLong(1),
+ PyLong_FromLong(2),
+ PyLong_FromLong(3)
+ };
+ PyObject *tuple = PyTuple_FromArray(array, 3);
+ if (tuple == NULL) {
+ goto error;
+ }
+
+ assert(PyTuple_GET_SIZE(tuple) == 3);
+ assert(PyTuple_GET_ITEM(tuple, 0) == array[0]);
+ assert(PyTuple_GET_ITEM(tuple, 1) == array[1]);
+ assert(PyTuple_GET_ITEM(tuple, 2) == array[2]);
+
+ Py_DECREF(tuple);
+ Py_DECREF(array[0]);
+ Py_DECREF(array[1]);
+ Py_DECREF(array[2]);
+
+ // Test PyTuple_FromArray(NULL, 0)
+ tuple = PyTuple_FromArray(NULL, 0);
+ if (tuple == NULL) {
+ return NULL;
+ }
+ assert(PyTuple_GET_SIZE(tuple) == 0);
+ Py_DECREF(tuple);
+
+ Py_RETURN_NONE;
+
+error:
+ Py_DECREF(array[0]);
+ Py_DECREF(array[1]);
+ Py_DECREF(array[2]);
+ return NULL;
+}
+
+
+static PyObject*
+test_tuple(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ return test_tuple_fromarray();
+}
+
+// Test adapted from CPython's _testcapi/object.c
+static int TryIncref_dealloc_called = 0;
+
+static void
+TryIncref_dealloc(PyObject *op)
+{
+ // PyUnstable_TryIncRef should return 0 if object is being deallocated
+ assert(Py_REFCNT(op) == 0);
+ assert(!PyUnstable_TryIncRef(op));
+ assert(Py_REFCNT(op) == 0);
+
+ TryIncref_dealloc_called++;
+ Py_TYPE(op)->tp_free(op);
+}
+
+static PyTypeObject TryIncrefType;
+
+static PyObject*
+test_try_incref(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ TryIncref_dealloc_called = 0;
+
+ PyObject *obj = PyObject_New(PyObject, &TryIncrefType);
+ if (obj == _Py_NULL) {
+ return _Py_NULL;
+ }
+
+ PyUnstable_EnableTryIncRef(obj);
+
+ Py_ssize_t refcount = Py_REFCNT(obj);
+ assert(PyUnstable_TryIncRef(obj));
+ assert(Py_REFCNT(obj) == refcount + 1);
+
+ Py_DECREF(obj);
+ Py_DECREF(obj);
+
+ assert(TryIncref_dealloc_called == 1);
+ Py_RETURN_NONE;
+}
+
+#if 0x030D0000 <= PY_VERSION_HEX && !defined(PYPY_VERSION)
+static PyObject *
+test_set_immortal(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ PyObject object;
+ memset(&object, 0, sizeof(PyObject));
+#ifdef Py_GIL_DISABLED
+ object.ob_tid = _Py_ThreadId();
+ object.ob_gc_bits = 0;
+ object.ob_ref_local = 1;
+ object.ob_ref_shared = 0;
+#else
+ object.ob_refcnt = 1;
+#endif
+ object.ob_type = &PyBaseObject_Type;
+
+ int rc = PyUnstable_SetImmortal(&object);
+ assert(rc == 1);
+ Py_DECREF(&object); // should not dealloc
+
+ // Check already immortal object
+ rc = PyUnstable_SetImmortal(&object);
+ assert(rc == 0);
+
+ // Check unicode objects
+ PyObject *unicode = PyUnicode_FromString("test");
+ rc = PyUnstable_SetImmortal(unicode);
+ assert(rc == 0);
+ Py_DECREF(unicode);
+ 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},
+#ifndef PYPY_VERSION
+ {"test_frame", test_frame, METH_NOARGS, _Py_NULL},
+#endif
+ {"test_thread_state", test_thread_state, METH_NOARGS, _Py_NULL},
+ {"test_interpreter", test_interpreter, METH_NOARGS, _Py_NULL},
+ {"test_calls", test_calls, METH_NOARGS, _Py_NULL},
+ {"test_gc", test_gc, METH_NOARGS, _Py_NULL},
+ {"test_module", test_module, METH_NOARGS, _Py_NULL},
+#if (PY_VERSION_HEX <= 0x030B00A1 || 0x030B00A7 <= PY_VERSION_HEX) && !defined(PYPY_VERSION)
+ {"test_float_pack", test_float_pack, METH_NOARGS, _Py_NULL},
+#endif
+#ifndef PYPY_VERSION
+ {"test_code", test_code, METH_NOARGS, _Py_NULL},
+#endif
+ {"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL},
+ {"test_import", test_import, METH_NOARGS, _Py_NULL},
+ {"test_weakref", test_weakref, METH_NOARGS, _Py_NULL},
+ {"func_varargs", (PyCFunction)(void*)func_varargs, METH_VARARGS | METH_KEYWORDS, _Py_NULL},
+ {"test_vectorcall", test_vectorcall, METH_NOARGS, _Py_NULL},
+ {"test_getattr", test_getattr, METH_NOARGS, _Py_NULL},
+ {"test_getitem", test_getitem, METH_NOARGS, _Py_NULL},
+ {"test_dict_api", test_dict_api, METH_NOARGS, _Py_NULL},
+ {"test_dict_pop", test_dict_pop, METH_NOARGS, _Py_NULL},
+ {"test_dict_setdefault", test_dict_setdefault, METH_NOARGS, _Py_NULL},
+ {"test_long_api", test_long_api, METH_NOARGS, _Py_NULL},
+#ifdef TEST_MANAGED_DICT
+ {"test_managed_dict", test_managed_dict, METH_NOARGS, _Py_NULL},
+#endif
+ {"test_unicode", test_unicode, METH_NOARGS, _Py_NULL},
+ {"test_list", test_list, METH_NOARGS, _Py_NULL},
+ {"test_hash", test_hash, METH_NOARGS, _Py_NULL},
+#ifdef TEST_PYTIME
+ {"test_time", test_time, METH_NOARGS, _Py_NULL},
+#endif
+ {"test_get_constant", test_get_constant, METH_NOARGS, _Py_NULL},
+#ifdef TEST_UNICODEWRITER
+ {"test_unicodewriter", test_unicodewriter, METH_NOARGS, _Py_NULL},
+ {"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},
+ {"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},
+#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},
+ {"test_uniquely_referenced", test_uniquely_referenced, METH_NOARGS, _Py_NULL},
+ {"test_byteswriter", test_byteswriter, METH_NOARGS, _Py_NULL},
+ {"test_tuple", test_tuple, METH_NOARGS, _Py_NULL},
+ {"test_try_incref", test_try_incref, METH_NOARGS, _Py_NULL},
+#if 0x030D0000 <= PY_VERSION_HEX && !defined(PYPY_VERSION)
+ {"test_set_immortal", test_set_immortal, METH_NOARGS, _Py_NULL},
+#endif
+ {_Py_NULL, _Py_NULL, 0, _Py_NULL}
+};
+
+
+static int
+module_exec(PyObject *module)
+{
+#ifdef __cplusplus
+ if (PyModule_AddIntMacro(module, __cplusplus)) {
+ return -1;
+ }
+#endif
+ if (PyModule_AddStringMacro(module, PY_VERSION)) {
+ return -1;
+ }
+ if (PyModule_AddIntMacro(module, PY_VERSION_HEX)) {
+ return -1;
+ }
+#ifdef PYPY_VERSION
+ if (PyModule_AddStringMacro(module, PYPY_VERSION)) {
+ return -1;
+ }
+#endif
+#ifdef PYPY_VERSION_NUM
+ if (PyModule_AddIntMacro(module, PYPY_VERSION_NUM)) {
+ return -1;
+ }
+#endif
+ TryIncrefType.tp_name = "TryIncrefType";
+ TryIncrefType.tp_basicsize = sizeof(PyObject);
+ TryIncrefType.tp_dealloc = TryIncref_dealloc;
+ TryIncrefType.tp_free = PyObject_Del;
+ if (PyType_Ready(&TryIncrefType) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+
+#if PY_VERSION_HEX >= 0x03050000
+static PyModuleDef_Slot module_slots[] = {
+ {Py_mod_exec, _Py_CAST(void*, module_exec)},
+#if PY_VERSION_HEX >= 0x030D0000
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+#endif
{0, _Py_NULL}
};
#endif
@@ -1435,7 +2651,7 @@ static struct PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
MODULE_NAME_STR, // m_name
_Py_NULL, // m_doc
- 0, // m_doc
+ 0, // m_size
methods, // m_methods
#if PY_VERSION_HEX >= 0x03050000
module_slots, // m_slots
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 ea6e722..4348e70 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}>'
@@ -507,7 +507,7 @@ def __init__(self, args=None):
self.operations = None
self.applied_operations = set()
- # Set temporariliy by patch()
+ # Set temporarily by patch()
self._has_pythoncapi_compat = None
self._applied_operations = None
@@ -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)