diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ede98ae..5bd8c871 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,9 @@ jobs: - '3.11' - '3.12' - '3.13' - - '3.14.0-rc.2' + - '3.13t' + - '3.14' + - '3.14t' arch: - 'arm64' - 'x86' @@ -77,9 +79,10 @@ jobs: with: python-version: ${{ matrix.py }} architecture: ${{ matrix.arch }} + allow-prereleases: true - name: Install Rust - if: matrix.arch != 'x86' && matrix.py != '3.14.0-rc.2' + if: matrix.arch != 'x86' && (matrix.py != '3.14' || matrix.py != '3.14t') uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # v1 with: toolchain: stable @@ -96,14 +99,14 @@ jobs: python -m pip install --require-hashes -r ci/requirements.txt - name: Build (Rust) - if: matrix.arch != 'x86' && matrix.py != '3.14.0-rc.2' + if: matrix.arch != 'x86' && (matrix.py != '3.14' || matrix.py != '3.14t') env: PIP_CONSTRAINT: 'ci/constraints.txt' run: | python -m pip -v install --config-settings='--build-option=--rust-backend' -e . - name: Build (No Rust) - if: matrix.arch == 'x86' || matrix.py == '3.14.0-rc.2' + if: matrix.arch == 'x86' || matrix.py == '3.14' || matrix.py == '3.14t' env: PIP_CONSTRAINT: 'ci/constraints.txt' run: | @@ -114,13 +117,14 @@ jobs: pytest --numprocesses=auto --hypothesis-profile=${HYPOTHESIS_PROFILE} -v tests/ - name: Test CFFI Backend + if: matrix.py != '3.13t' env: PYTHON_ZSTANDARD_IMPORT_POLICY: 'cffi' run: | pytest --numprocesses=auto --hypothesis-profile=${HYPOTHESIS_PROFILE} -v tests/ - name: Test Rust Backend - if: matrix.arch != 'x86' && matrix.py != '3.14.0-rc.2' + if: matrix.arch != 'x86' && (matrix.py != '3.14' || matrix.py != '3.14t' ) # Rust backend is currently experimental. So ignore failures in it. continue-on-error: true env: diff --git a/.github/workflows/typing.yml b/.github/workflows/typing.yml index 4ad483ac..7d56e7d0 100644 --- a/.github/workflows/typing.yml +++ b/.github/workflows/typing.yml @@ -15,7 +15,9 @@ jobs: - '3.11' - '3.12' - '3.13' + - '3.13t' - '3.14' + - '3.14t' runs-on: 'ubuntu-24.04' steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index c993e5c8..d8af256e 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -29,7 +29,9 @@ jobs: - 'cp311-cp311' - 'cp312-cp312' - 'cp313-cp313' + - 'cp313-cp313t' - 'cp314-cp314' + - 'cp314-cp314t' exclude: - image: 'musllinux_1_1_aarch64' py: 'cp314-cp314' @@ -65,7 +67,9 @@ jobs: - 'cp311' - 'cp312' - 'cp313' + - 'cp313t' - 'cp314' + - 'cp314t' arch: - 'arm64' - 'x86_64' @@ -74,7 +78,9 @@ jobs: CIBW_ARCHS: ${{ matrix.arch }} CIBW_BUILD: ${{ matrix.py }}-* CIBW_BUILD_VERBOSITY: '1' + CIBW_ENABLE: cpython-prerelease cpython-freethreading ZSTD_WARNINGS_AS_ERRORS: '1' + steps: - name: Set up Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 @@ -109,7 +115,9 @@ jobs: - '3.11' - '3.12' - '3.13' - - '3.14.0-rc.2' + - '3.13t' + - '3.14' + - '3.14t' arch: - 'x86' - 'x64' @@ -130,6 +138,7 @@ jobs: with: python-version: ${{ matrix.py }} architecture: ${{ matrix.arch }} + allow-prereleases: true - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: diff --git a/Cargo.lock b/Cargo.lock index 73ce919d..15fa1e5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -250,11 +250,10 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" +checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a" dependencies = [ - "cfg-if", "indoc", "libc", "memoffset", @@ -268,9 +267,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" +checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598" dependencies = [ "once_cell", "target-lexicon", @@ -278,9 +277,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" +checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c" dependencies = [ "libc", "pyo3-build-config", @@ -288,9 +287,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" +checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -300,9 +299,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" +checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc" dependencies = [ "heck", "proc-macro2", @@ -313,7 +312,7 @@ dependencies = [ [[package]] name = "python-zstandard" -version = "0.24.0-pre" +version = "0.25.0-pre" dependencies = [ "libc", "num_cpus", diff --git a/Cargo.toml b/Cargo.toml index ccf3237d..a8f8e0eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,5 +26,5 @@ version = "2.0.10+zstd.1.5.6" features = ["experimental", "legacy", "zstdmt"] [dependencies.pyo3] -version = "0.24.2" +version = "0.25.1" features = ["extension-module"] diff --git a/c-ext/backend_c.c b/c-ext/backend_c.c index 4b09d1fb..888a6767 100644 --- a/c-ext/backend_c.c +++ b/c-ext/backend_c.c @@ -313,7 +313,7 @@ size_t roundpow2(size_t i) { int safe_pybytes_resize(PyObject **obj, Py_ssize_t size) { PyObject *tmp; - if ((*obj)->ob_refcnt == 1) { + if (Py_REFCNT(*obj) == 1) { return _PyBytes_Resize(obj, size); } diff --git a/pyproject.toml b/pyproject.toml index dca8a82a..e91e1281 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,13 +19,17 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: Free Threading :: 1 - Unstable", + "Programming Language :: Python :: Free Threading", + ] keywords = ["zstandard", "zstd", "compression"] dependencies = [] [project.optional-dependencies] cffi = [ - 'cffi>=1.17 ; python_version >= "3.13" and platform_python_implementation != "PyPy"', + "cffi~=1.17; platform_python_implementation != 'PyPy' and python_version < '3.14'", + "cffi>=2.0.0b; platform_python_implementation != 'PyPy' and python_version >= '3.14'", ] [project.urls] @@ -34,7 +38,8 @@ Documentation = "https://python-zstandard.readthedocs.io/en/latest/" [build-system] requires = [ - "cffi>=1.17.0 ; platform_python_implementation != 'PyPy'", + "cffi~=1.17; platform_python_implementation != 'PyPy' and python_version < '3.14'", + "cffi>=2.0.0b; platform_python_implementation != 'PyPy' and python_version >= '3.14'", "packaging", "setuptools", ] diff --git a/rust-ext/src/buffers.rs b/rust-ext/src/buffers.rs index b48b7660..00c1ddd8 100644 --- a/rust-ext/src/buffers.rs +++ b/rust-ext/src/buffers.rs @@ -11,7 +11,7 @@ use { exceptions::{PyIndexError, PyTypeError, PyValueError}, ffi::Py_buffer, prelude::*, - types::{PyBytes, PyTuple}, + types::{PyBytes, PyTuple}, IntoPyObjectExt, }, }; @@ -242,7 +242,7 @@ impl ZstdBufferWithSegments { } Ok(Self { - source: data.into_py(py), + source: data.into_py_any(py).unwrap(), buffer: data_buffer, segments, }) @@ -344,7 +344,7 @@ impl ZstdBufferWithSegmentsCollection { offset += segment.segments.len(); - buffers.push(item.to_object(py)); + buffers.push(item.into_py_any(py).unwrap()); first_elements.push(offset); } diff --git a/rust-ext/src/compression_chunker.rs b/rust-ext/src/compression_chunker.rs index c3828302..4e8b3276 100644 --- a/rust-ext/src/compression_chunker.rs +++ b/rust-ext/src/compression_chunker.rs @@ -10,7 +10,7 @@ use { stream::{make_in_buffer_source, InBufferSource}, zstd_safe::CCtx, }, - pyo3::{prelude::*, types::PyBytes}, + pyo3::{prelude::*, types::PyBytes, IntoPyObjectExt}, std::sync::Arc, }; @@ -225,7 +225,7 @@ impl ZstdCompressionChunkerIterator { let chunk = PyBytes::new(py, &slf.dest_buffer); slf.dest_buffer.clear(); - return Ok(Some(chunk.into_py(py))); + return Ok(Some(chunk.into_py_any(py).unwrap())); } // Else continue to compress available input data. @@ -278,6 +278,6 @@ impl ZstdCompressionChunkerIterator { let chunk = PyBytes::new(py, &slf.dest_buffer); slf.dest_buffer.clear(); - Ok(Some(chunk.into_py(py))) + Ok(Some(chunk.into_py_any(py).unwrap())) } } diff --git a/rust-ext/src/compression_writer.rs b/rust-ext/src/compression_writer.rs index 570c24ea..b7f12366 100644 --- a/rust-ext/src/compression_writer.rs +++ b/rust-ext/src/compression_writer.rs @@ -10,7 +10,7 @@ use { buffer::PyBuffer, exceptions::{PyNotImplementedError, PyOSError, PyValueError}, prelude::*, - types::PyBytes, + types::PyBytes, IntoPyObjectExt, }, std::sync::Arc, }; @@ -48,7 +48,7 @@ impl ZstdCompressionWriter { Ok(Self { cctx, - writer: writer.into_py(py), + writer: writer.into_py_any(py).unwrap(), write_return_read, closefd, entered: false, diff --git a/rust-ext/src/compressor_iterator.rs b/rust-ext/src/compressor_iterator.rs index 7810594a..a8223c91 100644 --- a/rust-ext/src/compressor_iterator.rs +++ b/rust-ext/src/compressor_iterator.rs @@ -10,7 +10,7 @@ use { stream::{make_in_buffer_source, InBufferSource}, zstd_safe::CCtx, }, - pyo3::{prelude::*, types::PyBytes}, + pyo3::{prelude::*, types::PyBytes, IntoPyObjectExt}, std::sync::Arc, }; @@ -60,7 +60,7 @@ impl ZstdCompressorIterator { // TODO avoid buffer copy let chunk = PyBytes::new(py, &dest_buffer); - return Ok(Some(chunk.into_py(py))); + return Ok(Some(chunk.into_py_any(py).unwrap())); } // Else read another chunk in hopes of producing output data. @@ -94,7 +94,7 @@ impl ZstdCompressorIterator { // TODO avoid buffer copy. let chunk = PyBytes::new(py, &dest_buffer); - return Ok(Some(chunk.into_py(py))); + return Ok(Some(chunk.into_py_any(py).unwrap())); } Ok(None) diff --git a/rust-ext/src/decompression_writer.rs b/rust-ext/src/decompression_writer.rs index b6321acc..3ae45424 100644 --- a/rust-ext/src/decompression_writer.rs +++ b/rust-ext/src/decompression_writer.rs @@ -10,7 +10,7 @@ use { buffer::PyBuffer, exceptions::{PyOSError, PyValueError}, prelude::*, - types::PyBytes, + types::PyBytes, IntoPyObjectExt, }, std::sync::Arc, }; @@ -40,7 +40,7 @@ impl ZstdDecompressionWriter { ) -> PyResult { Ok(Self { dctx, - writer: writer.into_py(py), + writer: writer.into_py_any(py).unwrap(), write_size, write_return_read, closefd, diff --git a/rust-ext/src/decompressor_iterator.rs b/rust-ext/src/decompressor_iterator.rs index ce7d9a8f..38a4f04f 100644 --- a/rust-ext/src/decompressor_iterator.rs +++ b/rust-ext/src/decompressor_iterator.rs @@ -10,7 +10,7 @@ use { stream::{make_in_buffer_source, InBufferSource}, zstd_safe::DCtx, }, - pyo3::{exceptions::PyValueError, prelude::*, types::PyBytes}, + pyo3::{exceptions::PyValueError, prelude::*, types::PyBytes, IntoPyObjectExt}, std::{cmp::min, sync::Arc}, }; @@ -60,7 +60,7 @@ impl ZstdDecompressorIterator { if !dest_buffer.is_empty() { // TODO avoid buffer copy. let chunk = PyBytes::new(py, &dest_buffer); - return Ok(Some(chunk.into_py(py))); + return Ok(Some(chunk.into_py_any(py).unwrap())); } // Repeat loop to collect more input data. @@ -71,7 +71,7 @@ impl ZstdDecompressorIterator { if !dest_buffer.is_empty() { // TODO avoid buffer copy. let chunk = PyBytes::new(py, &dest_buffer); - Ok(Some(chunk.into_py(py))) + Ok(Some(chunk.into_py_any(py).unwrap())) } else { Ok(None) } diff --git a/rust-ext/src/stream.rs b/rust-ext/src/stream.rs index 89b35e98..130184e6 100644 --- a/rust-ext/src/stream.rs +++ b/rust-ext/src/stream.rs @@ -5,7 +5,7 @@ // of the BSD license. See the LICENSE file for details. use { - pyo3::{buffer::PyBuffer, exceptions::PyValueError, prelude::*}, + pyo3::{buffer::PyBuffer, exceptions::PyValueError, prelude::*, IntoPyObjectExt}, zstd_sys::ZSTD_inBuffer, }; @@ -139,7 +139,7 @@ pub(crate) fn make_in_buffer_source( ) -> PyResult> { if source.hasattr("read")? { Ok(Box::new(ReadSource { - source: source.into_py(py), + source: source.into_py_any(py).unwrap(), buffer: None, read_size, finished: false, @@ -153,7 +153,7 @@ pub(crate) fn make_in_buffer_source( })?; Ok(Box::new(BufferSource { - source: source.into_py(py), + source: source.into_py_any(py).unwrap(), buffer, offset: 0, })) diff --git a/setup.py b/setup.py index f868a924..cfd77f97 100755 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ import os import platform import sys +import sysconfig from setuptools import setup @@ -30,7 +31,8 @@ # garbage collection pitfalls. # Require 1.17 everywhere so we don't have to think about supporting older # versions. -MINIMUM_CFFI_VERSION = "1.17" +# Require 2.0 for Python 3.14+ to add improved free-threading support +MINIMUM_CFFI_VERSION = "2.0" if sys.version_info[0:2] >= (3, 14) else "1.17" ext_suffix = os.environ.get("SETUPTOOLS_EXT_SUFFIX") if ext_suffix: @@ -89,6 +91,10 @@ if platform.python_implementation() == "PyPy": C_BACKEND = False +# cffi 2.0 only introduced no-GIL support for 3.14+. +if platform.python_implementation() == "CPython" and sys.version_info[0:2] == (3, 13) and sysconfig.get_config_var("Py_GIL_DISABLED"): + CFFI_BACKEND = False + if "--legacy" in sys.argv: SUPPORT_LEGACY = True sys.argv.remove("--legacy")