From df4ea503593f393fdfe11828a95860e1aa3ccbf0 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 3 Oct 2026 04:05:59 +0300 Subject: [PATCH 1/5] Update actions (from the master branch) --- .github/workflows/backport.yml | 2 +- .github/workflows/ci.yml | 3 +++ .github/workflows/coverage.yml | 14 +++++++------- .github/workflows/linter.yml | 6 +++--- .github/workflows/publish.yml | 8 ++++---- .github/workflows/wheels.yml | 6 +++--- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index a9b20369..75406db6 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -25,7 +25,7 @@ jobs: echo "matched=$matched" >> $GITHUB_OUTPUT - name: Backport Action if: fromJSON(steps.check_labels.outputs.matched) > 0 - uses: sorenlouv/backport-github-action@v12.0.0 + uses: sorenlouv/backport-github-action@v12.0.4 with: github_token: ${{ secrets.GITHUB_TOKEN }} auto_backport_label_prefix: backport-to- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6f4fdfd..6de0c852 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,9 @@ on: workflow_dispatch: schedule: - cron: '0 0 * * 2' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true jobs: linter: uses: ./.github/workflows/linter.yml diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f5292d90..37f53504 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -5,16 +5,16 @@ jobs: timeout-minutes: 20 runs-on: ${{ matrix.os }} env: - CFLAGS: -Wall -Wpedantic -Werror -std=c17 -Wconversion + CFLAGS: -Wall -Werror -std=c17 -Wconversion strategy: matrix: os: [ubuntu-24.04] - python-version: [pypy3.11, "graalpy-25.0", "3.x"] + python-version: [pypy3.12, "graalpy-25", "3.x"] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} allow-prereleases: true @@ -26,7 +26,7 @@ jobs: sudo locale-gen ps_AF.UTF-8 - name: Cache GNU GMP builds id: cache-gmp - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: .local key: ${{ matrix.os }}-${{ hashFiles('scripts/*') }} @@ -59,7 +59,7 @@ jobs: needs: - coverage steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 - run: sudo apt-get update @@ -81,7 +81,7 @@ jobs: path: | build/coverage/ build/coverage*.info - - uses: codecov/codecov-action@v6 + - uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} gcov_ignore: pythoncapi_compat.h diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 5baf1700..82d16bd4 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -4,10 +4,10 @@ jobs: linter: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.x" - - uses: j178/prek-action@v2 + - uses: j178/prek-action@v3.0.0 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e7991db1..03bfe02a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,10 +6,10 @@ jobs: if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.x" - run: sudo apt-get update @@ -17,7 +17,7 @@ jobs: - run: pip install build - name: Cache GNU GMP builds id: cache-gmp - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: .local key: ${{ matrix.os }}-${{ hashFiles('scripts/*') }} @@ -67,7 +67,7 @@ jobs: pattern: python-gmp-* path: dist/ merge-multiple: true - - uses: sigstore/gh-action-sigstore-python@v3.3.0 + - uses: sigstore/gh-action-sigstore-python@v3.5.0 with: inputs: >- ./dist/*.tar.gz diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 66263a5f..dca0a0ac 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -15,10 +15,10 @@ jobs: msystem: clangarm64 menv: clang-aarch64 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: msys2/setup-msys2@v2.31.1 + - uses: msys2/setup-msys2@v2.33.0 name: Setup msys2 with: install: >- @@ -42,7 +42,7 @@ jobs: - run: echo "PKG_CONFIG_PATH=${{ github.workspace }}/.local/lib/pkgconfig" >> $env:GITHUB_ENV if: ${{ startsWith(matrix.os, 'windows') }} - name: Build wheels - uses: pypa/cibuildwheel@v4.0.0 + uses: pypa/cibuildwheel@84f6233892f964b4c2aba196595e85602bc61b34 - uses: actions/upload-artifact@v7 with: name: wheels-${{ matrix.os }} From 574292a58ba07263d6b81c21abb9e22789a1e71b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 2 Oct 2026 10:50:27 +0300 Subject: [PATCH 2/5] Use pypy3.12 for coverage tests And freeze hypothesis. (cherry picked from commit 0fa09260d24d6f19cdf0ea65312b8b9a97867c01) --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d38d56d1..ae27197a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,9 @@ Homepage = "https://github.com/diofant/python-gmp" Documentation = "https://python-gmp.readthedocs.io/en/latest/" [project.optional-dependencies] -tests = ["pytest", "hypothesis", "mpmath"] +tests = ["pytest", "hypothesis; implementation_name != 'pypy'", + "hypothesis<=6.150.0; implementation_name == 'pypy'", + "mpmath"] ci = ["python-gmp[tests]", "pytest-xdist"] docs = ["sphinx>=8.2"] develop = ["python-gmp[tests,docs]", "pre-commit", "pyperf"] From e7d0e8c65ec0a57e1fdbdf6e7089fd8aaeab2306 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 2 Jul 2026 04:20:20 +0300 Subject: [PATCH 3/5] Test on GraalPy-25.3 Drop workarounds for older releases, add regression tests. See #232. (cherry picked from commit 64253f616423d4ae622baf00c1b00794c327b148) --- README.rst | 2 +- pyproject.toml | 2 +- tests/test_functions.py | 2 +- tests/test_mpz.py | 9 +-------- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 93b9bdd4..74ecb2b6 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,7 @@ functions, compatible with the Python stdlib's submodule `math.integer `_. This module requires Python 3.11 or later versions and has been tested with -CPython 3.11 through 3.15, with PyPy3.11 7.3.23 and with GraalPy 25.0.3. +CPython 3.11 through 3.15, with PyPy3.12 8.0.0 and with GraalPy 25.3.4.1. Free-threading builds of the CPython are supported. Releases are available in the Python Package Index (PyPI) at diff --git a/pyproject.toml b/pyproject.toml index ae27197a..03897a36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ select = ["E", "F", "I", "PT", "W", "Q", "SIM"] [tool.cibuildwheel] build-frontend = {name="build", args=["--verbose", "-Csetup-args=--vsenv"]} enable = "pypy cpython-prerelease graalpy" -skip = "gp3*win*amd64*" +skip = "gp3*win*amd64* gp312_250-macos*" before-all = "sh scripts/cibw_before_all.sh" test-extras = "ci" diff --git a/tests/test_functions.py b/tests/test_functions.py index 41adf211..29c0ba10 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -236,7 +236,7 @@ def test_interfaces(): @pytest.mark.skipif(platform.python_implementation() == "GraalVM", - reason="oracle/graalpython#593") + reason="oracle/graalpython#998") def test_func_api(): for fn in ["comb", "factorial", "gcd", "isqrt", "lcm", "perm"]: f = getattr(math, fn) diff --git a/tests/test_mpz.py b/tests/test_mpz.py index e1b272c6..2d21b5b7 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -452,10 +452,6 @@ def test_add_int_subclasses(): def test_binary_mixed(x, y): mx = mpz(x) for op in [operator.add, operator.mul, operator.sub]: - if (platform.python_implementation() == "GraalVM" - and op in [operator.add, operator.sub] - and str(y.imag) == "-0.0"): - continue # issue oracle/graalpython#585 assert str(op(mx, y)) == str(op(x, y)) assert str(op(y, mx)) == str(op(y, x)) if op != operator.sub: @@ -712,8 +708,6 @@ def test_power_errors(): pow(mpz(10**1000), 1j) with pytest.raises(TypeError): pow(object(), mpz(321)) - if platform.python_implementation() == "GraalVM": - return # issue oracle/graalpython#551 if BITCNT_MAX < (1<<64) - 1: with pytest.raises(MemoryError): pow(mpz(1<<64), (1<<31) - 1) @@ -813,8 +807,6 @@ def test_methods(x): assert meth(mx) == meth(x) -@pytest.mark.skipif(platform.python_implementation() == "GraalVM", - reason="oracle/graalpython#883") @given(bigints(), integers(min_value=0, max_value=10000), sampled_from(["big", "little"]), booleans()) @example(0, 0, "big", False) @@ -839,6 +831,7 @@ def test_methods(x): @example(128, 1, "big", False) @example(-32383289396013590652, 0, "big", True) @example(-384, 1, "big", True) +@example(32768, 2, "big", True) def test_to_bytes_bulk(x, length, byteorder, signed): try: rx = x.to_bytes(length, byteorder, signed=signed) From a0ab02084eef755cd57f4fa384dd3836dd374c96 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 11 Aug 2026 11:01:29 +0300 Subject: [PATCH 4/5] Revert "Freeze meson deps for GraalPy" This reverts commit 7332c05d19631f15957a2f87c4d3dbaec75efff0. (cherry picked from commit 69779d3cb6270a4afbe6a782ded99d9f99678ad5) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 03897a36..1e4ccf59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] build-backend = "mesonpy" -requires = ["meson-python", "meson<1.12"] +requires = ["meson-python"] [project] name = "python-gmp" From 08cc0cc6001b505e7a71034fe5b68c30e7114173 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 24 Sep 2026 09:18:57 +0300 Subject: [PATCH 5/5] Update pythoncapi_compat.h (should fix compatibility with PyPy3.12) (cherry picked from commit de0952b86d7175fc3d61d2afcd3ca6d178822342) --- pythoncapi_compat.h | 224 ++++++++++++++++---------------------------- 1 file changed, 81 insertions(+), 143 deletions(-) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index cdfdafa8..fa47980b 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1,4 +1,5 @@ // Header file providing new C API functions to old Python versions. +// Python 3.6 or newer is required. // // File distributed under the Zero Clause BSD (0BSD) license. // Copyright Contributors to the pythoncapi_compat project. @@ -88,27 +89,6 @@ static inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) #endif -// Py_SETREF() and Py_XSETREF() were added to Python 3.5.2. -// It is excluded from the limited C API. -#if (PY_VERSION_HEX < 0x03050200 && !defined(Py_SETREF)) && !defined(Py_LIMITED_API) -#define Py_SETREF(dst, src) \ - do { \ - PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \ - PyObject *_tmp_dst = (*_tmp_dst_ptr); \ - *_tmp_dst_ptr = _PyObject_CAST(src); \ - Py_DECREF(_tmp_dst); \ - } while (0) - -#define Py_XSETREF(dst, src) \ - do { \ - PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \ - PyObject *_tmp_dst = (*_tmp_dst_ptr); \ - *_tmp_dst_ptr = _PyObject_CAST(src); \ - Py_XDECREF(_tmp_dst); \ - } while (0) -#endif - - // bpo-43753 added Py_Is(), Py_IsNone(), Py_IsTrue() and Py_IsFalse() // to Python 3.10.0b1. #if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_Is) @@ -146,7 +126,8 @@ static inline void _Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) // bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1 -#if PY_VERSION_HEX < 0x030900B1 || defined(PYPY_VERSION) +// PyPy added PyFrame_GetCode() to PyPy3.12 v8.0.0 +#if PY_VERSION_HEX < 0x030900B1 || (defined(PYPY_VERSION) && PY_VERSION_HEX < 0x030C0000) static inline PyCodeObject* PyFrame_GetCode(PyFrameObject *frame) { assert(frame != _Py_NULL); @@ -186,13 +167,9 @@ static inline PyFrameObject* _PyFrame_GetBackBorrow(PyFrameObject *frame) #if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) static inline PyObject* PyFrame_GetLocals(PyFrameObject *frame) { -#if PY_VERSION_HEX >= 0x030400B1 if (PyFrame_FastToLocalsWithError(frame) < 0) { return NULL; } -#else - PyFrame_FastToLocals(frame); -#endif return Py_NewRef(frame->f_locals); } #endif @@ -245,22 +222,14 @@ static inline PyObject* PyFrame_GetVar(PyFrameObject *frame, PyObject *name) if (locals == NULL) { return NULL; } -#if PY_VERSION_HEX >= 0x03000000 value = PyDict_GetItemWithError(locals, name); -#else - value = _PyDict_GetItemWithError(locals, name); -#endif Py_DECREF(locals); if (value == NULL) { if (PyErr_Occurred()) { return NULL; } -#if PY_VERSION_HEX >= 0x03000000 PyErr_Format(PyExc_NameError, "variable %R does not exist", name); -#else - PyErr_SetString(PyExc_NameError, "variable does not exist"); -#endif return NULL; } return Py_NewRef(value); @@ -274,11 +243,7 @@ static inline PyObject* PyFrame_GetVarString(PyFrameObject *frame, const char *name) { PyObject *name_obj, *value; -#if PY_VERSION_HEX >= 0x03000000 name_obj = PyUnicode_FromString(name); -#else - name_obj = PyString_FromString(name); -#endif if (name_obj == NULL) { return NULL; } @@ -321,7 +286,8 @@ _PyThreadState_GetFrameBorrow(PyThreadState *tstate) // bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a5 -#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) +// PyPy added PyInterpreterState_Get() to PyPy3.12 v8.0.0 +#if PY_VERSION_HEX < 0x030900A5 || (defined(PYPY_VERSION) && PY_VERSION_HEX < 0x030C0000) static inline PyInterpreterState* PyInterpreterState_Get(void) { PyThreadState *tstate; @@ -458,7 +424,7 @@ static inline int PyObject_GC_IsTracked(PyObject* obj) // bpo-40241 added PyObject_GC_IsFinalized() to Python 3.9.0a6. // bpo-18112 added _PyGCHead_FINALIZED() to Python 3.4.0 final. -#if PY_VERSION_HEX < 0x030900A6 && PY_VERSION_HEX >= 0x030400F0 && !defined(PYPY_VERSION) +#if PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION) static inline int PyObject_GC_IsFinalized(PyObject *obj) { PyGC_Head *gc = _Py_CAST(PyGC_Head*, obj) - 1; @@ -480,7 +446,7 @@ static inline int _Py_IS_TYPE(PyObject *ob, PyTypeObject *type) { // bpo-11734 added _PyFloat_Pack2() and _PyFloat_Unpack2() to Python 3.6.0b1. // Python 3.11a2 moved _PyFloat_Pack2() and _PyFloat_Unpack2() to the internal // C API: Python 3.11a2-3.11a6 versions are not supported. -#if 0x030600B1 <= PY_VERSION_HEX && PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION) +#if PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION) static inline int PyFloat_Pack2(double x, char *p, int le) { return _PyFloat_Pack2(x, (unsigned char*)p, le); } @@ -544,7 +510,7 @@ static inline PyObject* PyCode_GetCellvars(PyCodeObject *code) // Py_UNUSED() was added to Python 3.4.0b2. -#if PY_VERSION_HEX < 0x030400B2 && !defined(Py_UNUSED) +#ifndef Py_UNUSED # if defined(__GNUC__) || defined(__clang__) # define Py_UNUSED(name) _unused_ ## name __attribute__((unused)) # else @@ -705,13 +671,8 @@ PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result) static inline int PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result) { - PyObject *name_obj; int rc; -#if PY_VERSION_HEX >= 0x03000000 - name_obj = PyUnicode_FromString(attr_name); -#else - name_obj = PyString_FromString(attr_name); -#endif + PyObject *name_obj = PyUnicode_FromString(attr_name); if (name_obj == NULL) { *result = NULL; return -1; @@ -743,13 +704,8 @@ PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result) static inline int PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **result) { - PyObject *key_obj; int rc; -#if PY_VERSION_HEX >= 0x03000000 - key_obj = PyUnicode_FromString(key); -#else - key_obj = PyString_FromString(key); -#endif + PyObject *key_obj = PyUnicode_FromString(key); if (key_obj == NULL) { *result = NULL; return -1; @@ -812,11 +768,7 @@ PyObject_HasAttrStringWithError(PyObject *obj, const char *attr) static inline int PyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result) { -#if PY_VERSION_HEX >= 0x03000000 PyObject *item = PyDict_GetItemWithError(mp, key); -#else - PyObject *item = _PyDict_GetItemWithError(mp, key); -#endif if (item != NULL) { *result = Py_NewRef(item); return 1; // found @@ -833,11 +785,7 @@ static inline int PyDict_GetItemStringRef(PyObject *mp, const char *key, PyObject **result) { int res; -#if PY_VERSION_HEX >= 0x03000000 PyObject *key_obj = PyUnicode_FromString(key); -#else - PyObject *key_obj = PyString_FromString(key); -#endif if (key_obj == NULL) { *result = NULL; return -1; @@ -864,7 +812,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. -#if (0x030201B1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030D00A1) \ +#if (PY_VERSION_HEX < 0x030D00A1) \ && (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x7030000) static inline int Py_IsFinalizing(void) { @@ -941,7 +889,7 @@ PyObject_ClearManagedDict(PyObject *obj) // gh-108867 added PyThreadState_GetUnchecked() to Python 3.13.0a1 // Python 3.5.2 added _PyThreadState_UncheckedGet(). -#if PY_VERSION_HEX >= 0x03050200 && PY_VERSION_HEX < 0x030D00A1 +#if PY_VERSION_HEX < 0x030D00A1 static inline PyThreadState* PyThreadState_GetUnchecked(void) { @@ -963,13 +911,12 @@ PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t str_ // 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 { + // Python 3.3.0a1 added PyUnicode_AsUTF8AndSize() utf8 = PyUnicode_AsUTF8AndSize(unicode, &len); if (utf8 == NULL) { // Memory allocation failure. The API cannot report error, @@ -984,31 +931,6 @@ PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t str_ 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); @@ -1056,10 +978,8 @@ PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result) // 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 +#if defined(PYPY_VERSION) || 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 @@ -1100,12 +1020,6 @@ PyDict_PopString(PyObject *dict, const char *key, PyObject **result) #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) @@ -1121,7 +1035,7 @@ static inline Py_hash_t Py_HashPointer(const void *ptr) // 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 +#if PY_VERSION_HEX < 0x030D00A4 typedef _PyTime_t PyTime_t; #define PyTime_MIN _PyTime_MIN #define PyTime_MAX _PyTime_MAX @@ -1355,7 +1269,7 @@ PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, # define Py_END_CRITICAL_SECTION2() } #endif -#if PY_VERSION_HEX < 0x030E0000 && PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) +#if PY_VERSION_HEX < 0x030E0000 && !defined(PYPY_VERSION) typedef struct PyUnicodeWriter PyUnicodeWriter; static inline void PyUnicodeWriter_Discard(PyUnicodeWriter *writer) @@ -1425,6 +1339,11 @@ PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) 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; @@ -1569,6 +1488,11 @@ static inline int PyLong_IsZero(PyObject *obj) // 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)) { @@ -1583,10 +1507,8 @@ static inline int PyUnicode_Equal(PyObject *str1, PyObject *str2) } #if PY_VERSION_HEX >= 0x030d0000 && !defined(PYPY_VERSION) - PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *str1, PyObject *str2); - return _PyUnicode_Equal(str1, str2); -#elif PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) +#elif !defined(PYPY_VERSION) return _PyUnicode_EQ(str1, str2); #elif PY_VERSION_HEX >= 0x03090000 && defined(PYPY_VERSION) return _PyUnicode_EQ(str1, str2); @@ -1607,11 +1529,14 @@ static inline PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable) #if PY_VERSION_HEX < 0x030E00A0 + +#ifndef 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) - PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void *src, Py_ssize_t len); - +#ifndef PYPY_VERSION return _Py_HashBytes(ptr, len); #else Py_hash_t hash; @@ -1737,7 +1662,7 @@ static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) // 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) +#if PY_VERSION_HEX < 0x030E00A2 && !defined(PYPY_VERSION) // Helpers to access PyLongObject internals. static inline void _PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) @@ -1936,7 +1861,7 @@ PyLongWriter_Finish(PyLongWriter *writer) # define Py_T_ULONGLONG 18 # define Py_T_PYSSIZET 19 -# if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +# ifndef PYPY_VERSION # define _Py_T_NONE 20 # endif @@ -1948,26 +1873,21 @@ PyLongWriter_Finish(PyLongWriter *writer) // gh-127350 added Py_fopen() and Py_fclose() to Python 3.14a4 #if PY_VERSION_HEX < 0x030E00A4 + +#ifndef 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) - PyAPI_FUNC(FILE*) _Py_fopen_obj(PyObject *path, const char *mode); - +#ifndef 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); @@ -2212,7 +2132,8 @@ PyConfig_GetInt(const char *name, int *value) // gh-133144 added PyUnstable_Object_IsUniquelyReferenced() to Python 3.14.0b1. // Adapted from _PyObject_IsUniquelyReferenced() implementation. -#if PY_VERSION_HEX < 0x030E00B0 +// Not available on PyPy: Py_REFCNT() semantics is different on PyPy. +#if PY_VERSION_HEX < 0x030E00B0 && !defined(PYPY_VERSION) static inline int PyUnstable_Object_IsUniquelyReferenced(PyObject *obj) { #if !defined(Py_GIL_DISABLED) @@ -2230,7 +2151,8 @@ static inline int PyUnstable_Object_IsUniquelyReferenced(PyObject *obj) // 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 +// Not available on PyPy: Py_REFCNT() semantics is different on PyPy. +#if PY_VERSION_HEX < 0x030E00A5 && !defined(PYPY_VERSION) static inline int PyUnstable_TryIncRef(PyObject *op) { #ifndef Py_GIL_DISABLED @@ -2301,18 +2223,14 @@ static inline void PyUnstable_EnableTryIncRef(PyObject *op) (void)op; // unused argument #endif } -#endif +#endif // PY_VERSION_HEX < 0x030E00A5 && !defined(PYPY_VERSION) #if PY_VERSION_HEX < 0x030F0000 static inline PyObject* PySys_GetAttrString(const char *name) { -#if PY_VERSION_HEX >= 0x03000000 PyObject *value = Py_XNewRef(PySys_GetObject(name)); -#else - PyObject *value = Py_XNewRef(PySys_GetObject((char*)name)); -#endif if (value != NULL) { return value; } @@ -2325,11 +2243,7 @@ PySys_GetAttrString(const char *name) 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; } @@ -2340,11 +2254,7 @@ PySys_GetAttr(PyObject *name) 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; } @@ -2354,11 +2264,7 @@ PySys_GetOptionalAttrString(const char *name, PyObject **value) 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; @@ -2651,14 +2557,46 @@ 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; + return ((PyASCIIObject*)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 }