From 829cf37f1ea2a4d81c831b0e6cdee4fc8afc3026 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 16:04:58 +0000 Subject: [PATCH] Test and build wheels on Python 3.15 Adds Python 3.15 to CI, covering the same ground the 3.14 rows get: - test.yml gains 3.15 and 3.15t (default and free-threaded builds). - test-system-uchardet.yml gains 3.15t. A system build compiles against upstream uchardet's headers instead of the vendored sources, so it is where a new interpreter is most likely to break first. - build-and-upload-to-pypi.yml moves from cibuildwheel 4.1.0 to 4.2.0, the first release that knows about CPython 3.15. 4.2.0 builds and tests cp315/cp315t by default -- cibuildwheel gates a Python behind the `cpython-prerelease` enable group only until its first release candidate, since the ABI is frozen at rc1, so no opt-in is needed and the wheels are forward-compatible with 3.15.0 final. Under the old pin 3.15 could not be built at any setting. `allow-prereleases` on setup-python resolves `3.15` to the newest pre-release the runner image publishes (3.15.0rc1 today) and will pick up 3.15.0 final with no further change. It leaves the stable rows alone: it widens `3.X` to `~3.X.0-0`, and a pre-release only wins when no stable release satisfies the spec, so 3.10-3.14 still resolve to their newest stable patch. The new rows are advisory (`continue-on-error`) while 3.15 is a pre-release, so a CPython-side regression in rc2 or final cannot block merges; the comments record how to promote them. tox.ini's envlist was still py36-py311, predating both the 3.10 floor in requires-python and the versions CI actually runs; it now matches the workflow matrix. No source change was needed: against the 3.15.0rc1 build the runners use, the extension compiles and the suite passes on both 3.15 (150 passed) and free-threaded 3.15t (151 passed, GIL confirmed disabled). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Jr6Lsfd3fULmP1Gi4b6nDK --- .../workflows/build-and-upload-to-pypi.yml | 10 ++++++- .github/workflows/test-system-uchardet.yml | 12 ++++++++ .github/workflows/test.yml | 29 ++++++++++++++++++- pyproject.toml | 1 + tox.ini | 5 +++- 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-upload-to-pypi.yml b/.github/workflows/build-and-upload-to-pypi.yml index 44b6a90..3bc449a 100644 --- a/.github/workflows/build-and-upload-to-pypi.yml +++ b/.github/workflows/build-and-upload-to-pypi.yml @@ -54,7 +54,15 @@ jobs: # "Unknown enable group". cp313t is not buildable at this pin at any # setting, so 3.13t is covered by test.yml instead. Build selection # follows requires-python (>=3.10) in pyproject.toml. - uses: pypa/cibuildwheel@v4.1.0 + # + # 4.2.0 is the first pin that knows about CPython 3.15, and it builds + # and tests cp315/cp315t by default -- no `CIBW_ENABLE: + # cpython-prerelease` needed. cibuildwheel gates a Python behind that + # enable group only until its first release candidate, because the ABI + # is frozen at rc1; 3.15.0rc1 is out, so wheels built here are + # forward-compatible with 3.15.0 final. (Under the previous 4.1.0 pin + # 3.15 could not be built at all, at any setting.) + uses: pypa/cibuildwheel@v4.2.0 env: CIBW_ARCHS: ${{ matrix.cibw_archs }} diff --git a/.github/workflows/test-system-uchardet.yml b/.github/workflows/test-system-uchardet.yml index b34ee56..650da47 100644 --- a/.github/workflows/test-system-uchardet.yml +++ b/.github/workflows/test-system-uchardet.yml @@ -19,6 +19,8 @@ jobs: system-uchardet: name: Build against system uchardet (from git), Python ${{ matrix.python-version }} runs-on: ubuntu-latest + # Only the 3.15 row below sets this; the rest stay hard requirements. + continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: @@ -30,6 +32,14 @@ jobs: # cibuildwheel cannot produce cp313t wheels at all, which makes 3.13t # the least-covered configuration in the project overall. python-version: ['3.12', '3.13t', '3.14t'] + experimental: [false] + include: + # Free-threaded 3.15 (a pre-release; see test.yml for why these rows + # are advisory and how to promote them). The system build is where a + # new interpreter is most likely to break first: it compiles against + # upstream uchardet's headers rather than the vendored sources. + - python-version: '3.15t' + experimental: true steps: - uses: actions/checkout@v4 @@ -43,6 +53,8 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + # Required for the 3.15 row; see test.yml for the full reasoning. + allow-prereleases: true - name: Build and install uchardet from git run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4146113..c644355 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,9 @@ jobs: test: name: Test on Python ${{ matrix.python-version }} runs-on: ubuntu-latest + # Only the 3.15 rows below set this; every other row is `false` and stays + # a hard requirement. + continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: @@ -19,9 +22,27 @@ jobs: # The `t` entries are the free-threaded (no-GIL) builds. They are the # only coverage for the freethreading_compatible declaration -- without # them a regression is invisible until someone imports cchardet on a - # free-threaded interpreter. Note cibuildwheel 4.1.0 cannot build + # free-threaded interpreter. Note cibuildwheel 4.2.0 still cannot build # cp313t wheels at all, so 3.13t is tested here or nowhere. python-version: ['3.10', '3.11', '3.12', '3.13', '3.14', '3.13t', '3.14t'] + experimental: [false] + include: + # Python 3.15, resolved by `allow-prereleases` below to whatever + # pre-release the runner image currently publishes -- 3.15.0rc1 at + # the time of writing, and 3.15.0 final once it ships, with no change + # needed here. Both the default and the free-threaded build, matching + # the coverage the 3.14 rows get. + # + # Advisory (`continue-on-error`) while 3.15 is a pre-release: the + # extension builds and the suite passes on 3.15.0rc1 today, but rc2 + # and the final release are still to come and a CPython-side + # regression must not block merges. Move these entries up into the + # `python-version` list to make 3.15 a required check once 3.15.0 + # final is out. + - python-version: '3.15' + experimental: true + - python-version: '3.15t' + experimental: true steps: - uses: actions/checkout@v4 @@ -35,6 +56,12 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + # Needed for the 3.15 rows -- a bare `3.15` only matches stable + # releases and fails with "Version 3.15 was not found in the local + # cache". Harmless for the rest: this widens `3.X` to `~3.X.0-0`, and + # a pre-release is only chosen when no stable release satisfies the + # spec, so 3.10-3.14 still resolve to their newest stable patch. + allow-prereleases: true - name: Install package and test dependencies run: | diff --git a/pyproject.toml b/pyproject.toml index 3f750f9..b0bc29a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", # Tier 2: multithreaded use is tested and the thread-safety caveats are # documented (detect() is safe to call concurrently; a UniversalDetector # instance is per-stream and must not be shared). See the README. diff --git a/tox.ini b/tox.ini index 3950b52..11ea938 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,8 @@ [tox] -envlist = py36, py37, py38, py39, py310, py311 +# Matches requires-python (>=3.10) in pyproject.toml and the matrix in +# .github/workflows/test.yml. py315 is Python 3.15, still a pre-release -- +# tox will skip it unless a 3.15 interpreter is on PATH. +envlist = py310, py311, py312, py313, py314, py315 [testenv] whitelist_externals = make