diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c05de277..a3e4fa0c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,8 @@ updates: directory: "/" schedule: interval: "monthly" + rebase-strategy: "disabled" + groups: + actions-deps: + patterns: + - "*" diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 56d58396..a9b20369 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@v10.2.0 + uses: sorenlouv/backport-github-action@v12.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} auto_backport_label_prefix: backport-to- diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 183c3b08..f5292d90 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -48,7 +48,7 @@ jobs: lcov --remove coverage.info "*.h" --ignore-errors unused \ --output-file coverage.info cp coverage.info build/coverage-${{ matrix.python-version }}.info - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: name: coverage-${{ matrix.python-version }} path: | @@ -64,7 +64,7 @@ jobs: fetch-depth: 0 - run: sudo apt-get update - run: sudo apt-get install lcov diff-cover - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: pattern: coverage-* path: build/ @@ -75,13 +75,13 @@ jobs: - run: | diff-cover build/coverage*.info --fail-under=100 \ --compare-branch=origin/master - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: name: coverage path: | build/coverage/ build/coverage*.info - - uses: codecov/codecov-action@v5 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} gcov_ignore: pythoncapi_compat.h diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index f28cd489..5baf1700 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -10,4 +10,4 @@ jobs: - uses: actions/setup-python@v6 with: python-version: "3.x" - - uses: pre-commit/action@v3.0.1 + - uses: j178/prek-action@v2 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0b318483..e7991db1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -26,7 +26,7 @@ jobs: - run: python -m build -s env: PKG_CONFIG_PATH: ${{ github.workspace }}/.local/lib/pkgconfig - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: name: python-gmp-sdist path: dist/ @@ -45,7 +45,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: pattern: python-gmp-* path: dist/ @@ -62,12 +62,12 @@ jobs: contents: write id-token: write steps: - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: pattern: python-gmp-* path: dist/ merge-multiple: true - - uses: sigstore/gh-action-sigstore-python@v3.2.0 + - uses: sigstore/gh-action-sigstore-python@v3.3.0 with: inputs: >- ./dist/*.tar.gz diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 554e9ee6..66263a5f 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -6,8 +6,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-24.04, ubuntu-24.04-arm, macos-15-intel, macos-15, - windows-2022] + os: [ubuntu-24.04, ubuntu-24.04-arm, macos-26-intel, macos-26, + windows-2025-vs2026] msystem: [ucrt64] menv: [ucrt-x86_64] include: @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 - - uses: msys2/setup-msys2@v2.30.0 + - uses: msys2/setup-msys2@v2.31.1 name: Setup msys2 with: install: >- @@ -42,8 +42,8 @@ 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@v3.3.1 - - uses: actions/upload-artifact@v6 + uses: pypa/cibuildwheel@v4.0.0 + - uses: actions/upload-artifact@v7 with: name: wheels-${{ matrix.os }} path: ./wheelhouse/ @@ -52,7 +52,7 @@ jobs: needs: build_wheels steps: - name: Merge Wheels - uses: actions/upload-artifact/merge@v6 + uses: actions/upload-artifact/merge@v7 with: name: python-gmp-wheels pattern: wheels-* diff --git a/README.rst b/README.rst index aa437551..93b9bdd4 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.14, with PyPy3.11 7.3.20 and with GraalPy 25.0. +CPython 3.11 through 3.15, with PyPy3.11 7.3.23 and with GraalPy 25.0.3. Free-threading builds of the CPython are supported. Releases are available in the Python Package Index (PyPI) at diff --git a/bench/README.rst b/bench/README.rst index 474d3da7..a5fae97b 100644 --- a/bench/README.rst +++ b/bench/README.rst @@ -5,6 +5,7 @@ It's possible to run them also with gmpy2's and flint's integer types: .. code:: sh ( export T="gmpy2.mpz"; \ + export PYTHONPATH=. ; \ python bench/mul.py -q --copy-env --rigorous -o $T.json ) Beware, that the gmp prefers clang over gcc and extensions might diff --git a/bench/collatz.py b/bench/collatz.py index 19826554..ee60f630 100644 --- a/bench/collatz.py +++ b/bench/collatz.py @@ -1,15 +1,8 @@ -# collatz.py - -import os +# bench/collatz.py import pyperf -if os.getenv("T") == "gmpy2.mpz": - from gmpy2 import mpz -elif os.getenv("T") == "flint.fmpz": - from flint import fmpz as mpz -else: - from gmp import mpz +from bench.utils import mpz zero = mpz(0) one = mpz(1) diff --git a/bench/harmonic.py b/bench/harmonic.py new file mode 100644 index 00000000..067fa21c --- /dev/null +++ b/bench/harmonic.py @@ -0,0 +1,12 @@ +# bench/harmonic.py + +from fractions import Fraction + +import pyperf + +from bench.utils import mpz, mysum + +runner = pyperf.Runner() +for n in [100, 1000]: + xs = [Fraction(mpz(1), mpz(i)) for i in range(1, n + 1)] + runner.bench_func(f"H({n})", mysum, xs) diff --git a/bench/mul.py b/bench/mul.py index 65ff8973..b7e6f056 100644 --- a/bench/mul.py +++ b/bench/mul.py @@ -1,16 +1,10 @@ -# mul.py +# bench/mul.py -import os from operator import mul import pyperf -if os.getenv("T") == "gmpy2.mpz": - from gmpy2 import mpz -elif os.getenv("T") == "flint.fmpz": - from flint import fmpz as mpz -else: - from gmp import mpz +from bench.utils import mpz values = ["1<<7", "1<<38", "1<<300", "1<<3000"] diff --git a/bench/sum.py b/bench/sum.py new file mode 100644 index 00000000..1d348f50 --- /dev/null +++ b/bench/sum.py @@ -0,0 +1,10 @@ +# bench/sum.py + +import pyperf + +from bench.utils import mpz, mysum + +runner = pyperf.Runner() +for n in [100, 1000]: + xs = [mpz(i) for i in range(1, n + 1)] + runner.bench_func(f"sum({n})", mysum, xs) diff --git a/bench/utils.py b/bench/utils.py new file mode 100644 index 00000000..49234f1a --- /dev/null +++ b/bench/utils.py @@ -0,0 +1,17 @@ +import os + +if os.getenv("T") == "gmpy2.mpz": + from gmpy2 import mpz +elif os.getenv("T") == "flint.fmpz": + from flint import fmpz as mpz +elif os.getenv("T") == "int": + mpz = int +else: + from gmp import mpz # noqa: F401 + + +def mysum(xs): + total = xs[0] + for t in xs[1:]: + total += t + return t diff --git a/pyproject.toml b/pyproject.toml index fdaac951..5806366e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = ["Development Status :: 4 - Beta", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Programming Language :: Python :: Free Threading :: 2 - Beta", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", @@ -62,8 +63,8 @@ select = ["E", "F", "I", "PT", "W", "Q", "SIM"] [tool.cibuildwheel] build-frontend = {name="build", args=["--verbose", "-Csetup-args=--vsenv"]} -enable = "pypy cpython-prerelease cpython-freethreading graalpy" -skip = "gp311_242* gp3*win*amd64*" +enable = "pypy cpython-prerelease graalpy" +skip = "gp3*win*amd64*" before-all = "sh scripts/cibw_before_all.sh" test-extras = "ci" diff --git a/tests/conftest.py b/tests/conftest.py index 5e638a67..090ec111 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,7 @@ def pytest_configure(config): from hypothesis import settings default = settings.get_profile("default") - settings.register_profile("default", settings(default, deadline=1600)) + settings.register_profile("default", settings(default, deadline=3000)) ci = settings.get_profile("ci") if platform.python_implementation() != "GraalVM": ci = settings(ci, max_examples=10000) @@ -20,9 +20,9 @@ def pytest_report_header(config): print(f""" Using the ZZ library v{gmp._zz_version} - Bits per digit : {gmp.mpz_info.bits_per_digit} - sizeof(zz_digit_t): {gmp.mpz_info.sizeof_digit} - Maximal bit count : {gmp.mpz_info.bitcnt_max} + Bits per digit : {gmp.mpz_info.bits_per_digit} + sizeof(digit) : {gmp.mpz_info.sizeof_digit} + Maximal bit count: {gmp.mpz_info.bitcnt_max} The gmp module v{gmp.__version__} """) diff --git a/tests/test_functions.py b/tests/test_functions.py index cceab97e..41adf211 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -142,6 +142,7 @@ def test_mpmath_normalize(sign, man, exp, prec, rnd): sign = int(sign) bc = mman.bit_length() res = mpmath.libmp.libmpf._normalize(sign, man, exp, bc, prec, rnd) + assert all(type(_) is int for _ in res) assert _mpmath_normalize(sign, mman, exp, bc, prec, rnd) == res @@ -154,6 +155,7 @@ def test_mpmath_create(man, exp, prec, rnd): mpmath = pytest.importorskip("mpmath") mman = mpz(man) res = mpmath.libmp.from_man_exp(man, exp, prec, rnd) + assert all(type(_) is int for _ in res) assert _mpmath_create(mman, exp, prec, rnd) == res assert mman == man assert _mpmath_create(man, exp, prec, rnd) == res @@ -233,9 +235,8 @@ def test_interfaces(): _mpmath_normalize(1, mpz(111), 11, 12, 13, 1j) -# See pypy/pypy#5368 and oracle/graalpython#593 -@pytest.mark.skipif(platform.python_implementation() != "CPython", - reason="no way to specify a signature") +@pytest.mark.skipif(platform.python_implementation() == "GraalVM", + reason="oracle/graalpython#593") 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 03f8659a..e1b272c6 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -127,9 +127,9 @@ def test_format_interface(): mx.__format__(321) with pytest.raises(ValueError, match="Unknown format code"): format(mx, "q") - if platform.python_implementation() != "PyPy": # XXX: pypy/pypy#5311 - with pytest.raises(ValueError, match="Unknown format code"): - format(mx, "\x81") + with pytest.raises(ValueError, + match="(Unknown format code|Invalid format specifier)"): + format(mx, "\x81") with pytest.raises(ValueError, match=(r"Negative zero coercion \(z\) not allowed|" "Invalid conversion specification")): @@ -195,10 +195,6 @@ def test_format_interface(): assert format(mx, ".,f") == "123.000,000" assert format(mx, "._f") == "123.000_000" - if (platform.python_implementation() == "PyPy" - and sys.pypy_version_info[:3] <= (7, 3, 20)): - return # XXX: pypy/pypy#5311 - try: locale.setlocale(locale.LC_ALL, "ru_RU.UTF-8") s = locale.localeconv()["thousands_sep"] @@ -517,8 +513,6 @@ def test_divmod_bulk(x, y): with pytest.raises(ZeroDivisionError): x % my return - if y < 0 and platform.python_implementation() == "GraalVM": - return # issue oracle/graalpython#534 r = x // y assert mx // my == r assert mx // y == r @@ -700,11 +694,11 @@ def test_power_mod(x, y, z): assert pow(mx, my, mz) == r assert pow(mx, my, z) == r assert pow(mx, y, mz) == r - if platform.python_implementation() == "PyPy": - return # XXX: pypy/pypy#5207 assert pow(x, my, mz) == r assert pow(mx, y, z) == r assert pow(x, my, z) == r + if platform.python_implementation() == "PyPy": + return # XXX: pypy/pypy#5207 assert pow(x, y, mz) == r @@ -1075,9 +1069,8 @@ def f(n): assert all(f.result() == 1 for f in futures) -# See pypy/pypy#5368 and oracle/graalpython#593 -@pytest.mark.skipif(platform.python_implementation() != "CPython", - reason="no way to specify a signature") +@pytest.mark.skipif(platform.python_implementation() == "GraalVM", + reason="oracle/graalpython#593") def test_int_api(): for meth in dir(int): m = getattr(int, meth) diff --git a/utils.h b/utils.h index f13b23c1..4bf83020 100644 --- a/utils.h +++ b/utils.h @@ -1,14 +1,8 @@ #ifndef UTILS_H #define UTILS_H -#if defined(__MINGW32__) && defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Warray-bounds" -#endif -#if defined(__clang__) -# pragma GCC diagnostic push /* XXX: pypy/pypy#5312 */ -# pragma GCC diagnostic ignored "-Wnewline-eof" -#endif +/* For GraalVM: unicodeobject.h, implicit conversion changes + signedness: 'enum PyUnicode_Kind' to 'int' */ #if defined(__GNUC__) || defined(__clang__) # pragma GCC diagnostic push /* XXX: oracle/graalpython#580 */ # pragma GCC diagnostic ignored "-Wsign-conversion" @@ -22,12 +16,6 @@ #if defined(__GNUC__) || defined(__clang__) # pragma GCC diagnostic pop #endif -#if defined(__clang__) -# pragma GCC diagnostic pop -#endif -#if defined(__MINGW32__) && defined(__GNUC__) -# pragma GCC diagnostic pop -#endif typedef struct gmp_pyargs { Py_ssize_t maxpos;