diff --git a/.github/workflows/external-zstd.yml b/.github/workflows/external-zstd.yml index 2e0f3b69..42bf25e6 100644 --- a/.github/workflows/external-zstd.yml +++ b/.github/workflows/external-zstd.yml @@ -61,4 +61,11 @@ jobs: - name: Build run: | + # Homebrew's prefix (/opt/homebrew on Apple Silicon) isn't on the + # default compiler search paths, and its libzstd is arm64 only, so + # don't build the universal2 binary the Python framework asks for. + ZSTD_PREFIX="$(brew --prefix zstd)" + export CFLAGS="-I${ZSTD_PREFIX}/include" + export LDFLAGS="-L${ZSTD_PREFIX}/lib" + export ARCHFLAGS="-arch arm64" python -m pip install --config-settings=--global-option=--system-zstd . diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b4019757..162f3fbe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,6 +29,8 @@ jobs: - '3.13t' - '3.14' - '3.14t' + - '3.15' + - '3.15t' arch: - 'arm64' - 'x86' diff --git a/.github/workflows/typing.yml b/.github/workflows/typing.yml index 35786201..f94dde4e 100644 --- a/.github/workflows/typing.yml +++ b/.github/workflows/typing.yml @@ -22,6 +22,8 @@ jobs: - '3.13t' - '3.14' - '3.14t' + - '3.15' + - '3.15t' runs-on: 'ubuntu-24.04' steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index f9e8f2e5..5fc9da6e 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -22,6 +22,8 @@ jobs: - 'cp313' - 'cp314' - 'cp314t' + - 'cp315' + - 'cp315t' arch: - 'aarch64' - 'i686' @@ -66,11 +68,19 @@ jobs: image: 'manylinux2014' - py: 'cp314t' image: 'manylinux2014' + - py: 'cp315' + image: 'manylinux2014' + - py: 'cp315t' + image: 'manylinux2014' # Drop support for musllinux_1_1 on 3.14+ - py: 'cp314' image: 'musllinux_1_1' - py: 'cp314t' image: 'musllinux_1_1' + - py: 'cp315' + image: 'musllinux_1_1' + - py: 'cp315t' + image: 'musllinux_1_1' runs-on: ${{ matrix.arch == 'aarch64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} env: CIBW_ARCHS: ${{ matrix.arch }} @@ -124,6 +134,8 @@ jobs: - 'cp313' - 'cp314' - 'cp314t' + - 'cp315' + - 'cp315t' arch: - 'arm64' - 'x86_64' @@ -164,6 +176,8 @@ jobs: - 'cp313' - 'cp314' - 'cp314t' + - 'cp315' + - 'cp315t' arch: - 'win32' - 'win_amd64' diff --git a/Cargo.lock b/Cargo.lock index c78c052c..a59cc5d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,12 +11,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - [[package]] name = "bindgen" version = "0.72.1" @@ -146,12 +140,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" -[[package]] -name = "indoc" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" - [[package]] name = "itertools" version = "0.13.0" @@ -193,15 +181,6 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -257,36 +236,32 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.25.1" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a" +checksum = "700d18fa267b73b9b521fd7e13580e2f446916f176cacee1ab63fcc8191f1655" dependencies = [ - "indoc", "libc", - "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", - "unindent", ] [[package]] name = "pyo3-build-config" -version = "0.25.1" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598" +checksum = "7b3fc0c4d08f6bb10e71fe39dfb9e2f59c6eb6854e22ec8092f50c69a4499adb" dependencies = [ - "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.25.1" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c" +checksum = "dfc0b8e19df29aad7086cf977bb0c2a2f143e30567eb113e9cf72b62ca698330" dependencies = [ "libc", "pyo3-build-config", @@ -294,9 +269,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.25.1" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50" +checksum = "6100e8a4b5eba53afaa5ed078364851a0b2499c553a44c31026b929049b49dc6" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -306,13 +281,12 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.25.1" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc" +checksum = "6143877a16e82b5a727b7127ff4cd86858a24a28d745d72f43e6f227c7b1bdb3" dependencies = [ "heck", "proc-macro2", - "pyo3-build-config", "quote", "syn", ] @@ -428,12 +402,6 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" -[[package]] -name = "unindent" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" - [[package]] name = "wasi" version = "0.14.5+wasi-0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 157822f0..9da71a77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,5 +26,5 @@ version = "2.0.16" features = ["experimental", "legacy", "zstdmt"] [dependencies.pyo3] -version = "0.25.1" +version = "0.29.3" features = ["extension-module"] diff --git a/ci/conda/meta.yaml b/ci/conda/meta.yaml index cbea594a..2311ac3b 100644 --- a/ci/conda/meta.yaml +++ b/ci/conda/meta.yaml @@ -1,8 +1,12 @@ -{% set data = load_setup_py_data() %} +# Don't use load_setup_py_data(): it runs setup.py inside the conda-build +# process, and setup.py's sys.path.insert(0, ".") then makes the unbuilt +# zstandard package in the source tree shadow the zstandard bindings that +# conda-package-handling needs for .conda support. +{% set version_match = load_file_regex(load_file="../../c-ext/python-zstandard.h", regex_pattern='PYTHON_ZSTANDARD_VERSION "([^"]+)"', from_recipe_dir=True) %} package: name: zstandard - version: "{{ data.get('version') }}" + version: "{{ version_match.group(1) }}" source: path: ../../ diff --git a/docs/installing.rst b/docs/installing.rst index c2f0e72b..8391f899 100644 --- a/docs/installing.rst +++ b/docs/installing.rst @@ -22,10 +22,10 @@ this package with ``conda``. Requirements ============ -This package is designed to run with Python 3.10, 3.11, 3.12, 3.13, and 3.14 -on common platforms (Linux, Windows, and OS X). On PyPy (both PyPy2 and PyPy3) -we support version 6.0.0 and above. x86 and x86_64 are well-tested on Windows. -Only x86_64 is well-tested on Linux and macOS. +This package is designed to run with Python 3.10, 3.11, 3.12, 3.13, 3.14, +and 3.15 on common platforms (Linux, Windows, and OS X). On PyPy (both PyPy2 +and PyPy3) we support version 6.0.0 and above. x86 and x86_64 are well-tested +on Windows. Only x86_64 is well-tested on Linux and macOS. CFFI Backend ============ diff --git a/docs/news.rst b/docs/news.rst index 83641a75..7df71020 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -17,6 +17,15 @@ Backwards Compatibility Notes * cibuildwheel upgrade required us to drop generating the CPython 3.13 free-threaded wheels. +Changes +------- + +* Python 3.15 support. We build wheels (including free-threaded ``cp315t`` + wheels) and have CI test coverage. Like 3.14, Linux wheels are built with + ``manylinux_2_28`` and ``musllinux_1_2``. +* PyO3 Rust crate upgraded from 0.25 to 0.29, which is required for + Python 3.15 support in the Rust backend. + 0.25.0 (released 2025-09-14) ============================ diff --git a/pyproject.toml b/pyproject.toml index b358a7f5..b2b74145 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ classifiers = [ "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 :: 1 - Unstable", ] keywords = ["zstandard", "zstd", "compression"] @@ -42,14 +43,20 @@ requires = [ build-backend = "setuptools.build_meta" [dependency-groups] -dev = [ +# Dependencies needed to run the test suite. cibuildwheel installs this group +# unlocked inside its build containers, so keep it free of packages (like mypy) +# without wheels for every platform we build. +test = [ "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'", "hypothesis==6.111.0", - "mypy>=1.17.1", "pytest>=8.4.1", "pytest-xdist>=3.8.0", ] +dev = [ + { include-group = "test" }, + "mypy>=1.17.1", +] [tool.pytest.ini_options] testpaths = ["tests"] @@ -71,7 +78,7 @@ test-command = [ "pytest -v --numprocesses=auto", "PYTHON_ZSTANDARD_IMPORT_POLICY=cffi pytest -v --numprocesses=auto", ] -test-groups = ["dev"] +test-groups = ["test"] test-sources = ["tests"] [tool.cibuildwheel.linux] diff --git a/rust-ext/src/buffers.rs b/rust-ext/src/buffers.rs index 9ad609b1..289d1a48 100644 --- a/rust-ext/src/buffers.rs +++ b/rust-ext/src/buffers.rs @@ -26,7 +26,7 @@ pub(crate) struct BufferSegment { #[pyclass(module = "zstandard.backend_rust", name = "BufferSegment")] pub struct ZstdBufferSegment { /// The object backing storage. For reference counting. - _parent: PyObject, + _parent: Py, /// PyBuffer into parent object. buffer: PyBuffer, /// Offset of segment within data. @@ -53,7 +53,7 @@ impl ZstdBufferSegment { } // PyBufferProtocol. - fn bf_getbuffer(slf: PyRefMut, view: PyObject, flags: i32) -> PyResult<()> { + fn bf_getbuffer(slf: PyRefMut, view: Py, flags: i32) -> PyResult<()> { let slice = slf.as_slice(); if unsafe { @@ -74,7 +74,7 @@ impl ZstdBufferSegment { } #[allow(unused_variables)] - fn bf_releasebuffer(slf: PyRefMut, view: PyObject) {} + fn bf_releasebuffer(slf: PyRefMut, view: Py) {} // Our methods. @@ -90,7 +90,7 @@ impl ZstdBufferSegment { #[pyclass(module = "zstandard.backend_rust", name = "BufferSegments")] pub struct ZstdBufferSegments { - parent: PyObject, + parent: Py, } unsafe impl Sync for ZstdBufferSegments {} @@ -98,10 +98,10 @@ unsafe impl Sync for ZstdBufferSegments {} #[pymethods] impl ZstdBufferSegments { // PyBufferProtocol. - fn bf_getbuffer(slf: PyRefMut, view: PyObject, flags: i32) -> PyResult<()> { + fn bf_getbuffer(slf: PyRefMut, view: Py, flags: i32) -> PyResult<()> { let py = slf.py(); - let parent = slf.parent.downcast_bound::(py)?; + let parent = slf.parent.cast_bound::(py)?; if unsafe { pyo3::ffi::PyBuffer_FillInfo( @@ -121,12 +121,12 @@ impl ZstdBufferSegments { } #[allow(unused_variables)] - fn bf_releasebuffer(slf: PyRefMut, view: PyObject) {} + fn bf_releasebuffer(slf: PyRefMut, view: Py) {} } #[pyclass(module = "zstandard.backend_rust", name = "BufferWithSegments")] pub struct ZstdBufferWithSegments { - source: PyObject, + source: Py, pub(crate) buffer: PyBuffer, pub(crate) segments: Vec, } @@ -161,7 +161,7 @@ impl ZstdBufferWithSegments { } fn __getitem__(&self, key: isize) -> PyResult { - let py = unsafe { Python::assume_gil_acquired() }; + let py = unsafe { Python::assume_attached() }; if key < 0 { return Err(PyIndexError::new_err("offset must be non-negative")); @@ -180,14 +180,14 @@ impl ZstdBufferWithSegments { Ok(ZstdBufferSegment { _parent: self.source.clone_ref(py), - buffer: PyBuffer::get(self.source.downcast_bound(py)?)?, + buffer: PyBuffer::get(self.source.cast_bound(py)?)?, offset: segment.offset as _, len: segment.length as _, }) } // PyBufferProtocol. - fn bf_getbuffer(slf: PyRefMut, view: PyObject, flags: i32) -> PyResult<()> { + fn bf_getbuffer(slf: PyRefMut, view: Py, flags: i32) -> PyResult<()> { if unsafe { pyo3::ffi::PyBuffer_FillInfo( view.as_ptr() as *mut Py_buffer, @@ -206,7 +206,7 @@ impl ZstdBufferWithSegments { } #[allow(unused_variables)] - fn bf_releasebuffer(slf: PyRefMut, view: PyObject) {} + fn bf_releasebuffer(slf: PyRefMut, view: Py) {} // Our methods. @@ -256,8 +256,7 @@ impl ZstdBufferWithSegments { fn segments(slf: PyRef, py: Python) -> PyResult { Ok(ZstdBufferSegments { - // TODO surely there is a better way to cast self to PyObject? - parent: unsafe { Py::from_borrowed_ptr(py, slf.as_ptr()) }, + parent: slf.into_pyobject(py)?.into_any().unbind(), }) } @@ -272,7 +271,7 @@ impl ZstdBufferWithSegments { )] pub struct ZstdBufferWithSegmentsCollection { // Py. - pub(crate) buffers: Vec, + pub(crate) buffers: Vec>, first_elements: Vec, } @@ -287,7 +286,7 @@ impl ZstdBufferWithSegmentsCollection { } fn __getitem__(&self, key: isize) -> PyResult { - let py = unsafe { Python::assume_gil_acquired() }; + let py = unsafe { Python::assume_attached() }; if key < 0 { return Err(PyIndexError::new_err("offset must be non-negative")); @@ -309,7 +308,7 @@ impl ZstdBufferWithSegmentsCollection { offset = self.first_elements[buffer_index - 1]; } - let item = segment.downcast_bound::(py)?; + let item = segment.cast_bound::(py)?; return item.borrow().__getitem__((key - offset) as isize); } @@ -332,7 +331,7 @@ impl ZstdBufferWithSegmentsCollection { let mut offset = 0; for item in py_args { - let item = item.downcast::().map_err(|_| { + let item = item.cast::().map_err(|_| { PyTypeError::new_err("arguments must be BufferWithSegments instances") })?; let segment = item.borrow(); @@ -359,7 +358,7 @@ impl ZstdBufferWithSegmentsCollection { let mut size = 0; for buffer in &self.buffers { - let item = buffer.downcast_bound::(py)?; + let item = buffer.cast_bound::(py)?; for segment in &item.borrow().segments { size += segment.length as usize; diff --git a/rust-ext/src/compression_chunker.rs b/rust-ext/src/compression_chunker.rs index 5ce66fd2..e4323e1e 100644 --- a/rust-ext/src/compression_chunker.rs +++ b/rust-ext/src/compression_chunker.rs @@ -196,12 +196,12 @@ impl ZstdCompressionChunkerIterator { slf } - fn __next__(mut slf: PyRefMut) -> PyResult> { + fn __next__(mut slf: PyRefMut) -> PyResult>> { if slf.finished { return Ok(None); } - let py = unsafe { Python::assume_gil_acquired() }; + let py = unsafe { Python::assume_attached() }; // Consume any data left in the input. while let Some(mut in_buffer) = slf.source.input_buffer(py)? { diff --git a/rust-ext/src/compression_dict.rs b/rust-ext/src/compression_dict.rs index 89b3d743..1338f59d 100644 --- a/rust-ext/src/compression_dict.rs +++ b/rust-ext/src/compression_dict.rs @@ -257,7 +257,7 @@ fn train_dictionary( // validated to be PyBytes. for sample in samples.iter() { let bytes = sample - .downcast::() + .cast::() .or_else(|_| Err(PyValueError::new_err("samples must be bytes")))?; samples_len += bytes.as_bytes().len(); @@ -267,7 +267,7 @@ fn train_dictionary( let mut sample_sizes: Vec = Vec::with_capacity(samples.len()); for sample in samples.iter() { - let bytes = sample.downcast::()?; + let bytes = sample.cast::()?; let data = bytes.as_bytes(); sample_sizes.push(data.len()); samples_buffer.extend_from_slice(data); diff --git a/rust-ext/src/compression_reader.rs b/rust-ext/src/compression_reader.rs index 47626e34..54fd9829 100644 --- a/rust-ext/src/compression_reader.rs +++ b/rust-ext/src/compression_reader.rs @@ -145,9 +145,9 @@ impl ZstdCompressionReader { fn __exit__<'p>( mut slf: PyRefMut<'p, Self>, py: Python<'p>, - _exc_type: PyObject, - _exc_value: PyObject, - _exc_tb: PyObject, + _exc_type: Py, + _exc_value: Py, + _exc_tb: Py, ) -> PyResult { slf.entered = false; slf.close(py)?; diff --git a/rust-ext/src/compression_writer.rs b/rust-ext/src/compression_writer.rs index 06fe57be..60dc2ed6 100644 --- a/rust-ext/src/compression_writer.rs +++ b/rust-ext/src/compression_writer.rs @@ -22,7 +22,7 @@ const FLUSH_FRAME: usize = 1; #[pyclass(module = "zstandard.backend_rust")] pub struct ZstdCompressionWriter { cctx: Arc>, - writer: PyObject, + writer: Py, write_return_read: bool, closefd: bool, entered: bool, @@ -111,7 +111,7 @@ impl ZstdCompressionWriter { self.cctx.memory_size() } - fn fileno(&self, py: Python) -> PyResult { + fn fileno(&self, py: Python) -> PyResult> { if let Ok(fileno) = self.writer.getattr(py, "fileno") { fileno.call0(py) } else { diff --git a/rust-ext/src/compressionobj.rs b/rust-ext/src/compressionobj.rs index 63f98ca8..e55248f0 100644 --- a/rust-ext/src/compressionobj.rs +++ b/rust-ext/src/compressionobj.rs @@ -52,7 +52,7 @@ impl ZstdCompressionObj { let cctx = &self.cctx; while !source.is_empty() { let result = py - .allow_threads(|| { + .detach(|| { cctx.compress_chunk( source, zstd_sys::ZSTD_EndDirective::ZSTD_e_continue, @@ -100,7 +100,7 @@ impl ZstdCompressionObj { loop { let (chunk, _, call_again) = py - .allow_threads(|| cctx.compress_chunk(&[], flush_mode, write_size)) + .detach(|| cctx.compress_chunk(&[], flush_mode, write_size)) .or_else(|msg| { Err(ZstdError::new_err(format!( "error ending compression stream: {}", diff --git a/rust-ext/src/compressor.rs b/rust-ext/src/compressor.rs index fac6d259..eb3e7797 100644 --- a/rust-ext/src/compressor.rs +++ b/rust-ext/src/compressor.rs @@ -170,7 +170,7 @@ impl ZstdCompressor { // TODO implement 0 copy via Py_SIZE(). let data = py - .allow_threads(|| cctx.compress(source)) + .detach(|| cctx.compress(source)) .or_else(|msg| Err(ZstdError::new_err(format!("cannot compress: {}", msg))))?; Ok(PyBytes::new(py, &data)) @@ -264,7 +264,7 @@ impl ZstdCompressor { // Try to read from source stream. let read_object = ifh.call_method("read", (read_size,), None)?; - let read_bytes = read_object.downcast::()?; + let read_bytes = read_object.cast::()?; let read_data = read_bytes.as_bytes(); // If no data was read we are at EOF. @@ -281,7 +281,7 @@ impl ZstdCompressor { while !source.is_empty() { let result = py - .allow_threads(|| { + .detach(|| { cctx.compress_chunk( source, zstd_sys::ZSTD_EndDirective::ZSTD_e_continue, diff --git a/rust-ext/src/compressor_iterator.rs b/rust-ext/src/compressor_iterator.rs index 86bf25d2..7cb1a07a 100644 --- a/rust-ext/src/compressor_iterator.rs +++ b/rust-ext/src/compressor_iterator.rs @@ -32,12 +32,12 @@ impl ZstdCompressorIterator { slf } - fn __next__(mut slf: PyRefMut) -> PyResult> { + fn __next__(mut slf: PyRefMut) -> PyResult>> { if slf.finished_output { return Ok(None); } - let py = unsafe { Python::assume_gil_acquired() }; + let py = unsafe { Python::assume_attached() }; let mut dest_buffer: Vec = Vec::with_capacity(slf.write_size); diff --git a/rust-ext/src/compressor_multi.rs b/rust-ext/src/compressor_multi.rs index 5ebb9e12..964419c7 100644 --- a/rust-ext/src/compressor_multi.rs +++ b/rust-ext/src/compressor_multi.rs @@ -43,7 +43,7 @@ pub fn multi_compress_to_buffer( let mut sources = vec![]; let mut total_source_size = 0; - if let Ok(buffer) = data.downcast::() { + if let Ok(buffer) = data.cast::() { sources.reserve_exact(buffer.borrow().segments.len()); let borrow = buffer.borrow(); @@ -54,11 +54,11 @@ pub fn multi_compress_to_buffer( sources.push(DataSource { data: slice }); total_source_size += slice.len(); } - } else if let Ok(collection) = data.downcast::() { + } else if let Ok(collection) = data.cast::() { sources.reserve_exact(collection.len()?); for buffer_obj in &collection.borrow().buffers { - let buffer = buffer_obj.downcast_bound::(py)?; + let buffer = buffer_obj.cast_bound::(py)?; let borrow = buffer.borrow(); for i in 0..borrow.segments.len() { diff --git a/rust-ext/src/decompression_writer.rs b/rust-ext/src/decompression_writer.rs index 02c3861b..55694d7b 100644 --- a/rust-ext/src/decompression_writer.rs +++ b/rust-ext/src/decompression_writer.rs @@ -19,7 +19,7 @@ use { #[pyclass(module = "zstandard.backend_rust")] pub struct ZstdDecompressionWriter { dctx: Arc>, - writer: PyObject, + writer: Py, write_size: usize, write_return_read: bool, closefd: bool, @@ -87,9 +87,9 @@ impl ZstdDecompressionWriter { fn __exit__<'p>( mut slf: PyRefMut<'p, Self>, py: Python<'p>, - exc_type: PyObject, - exc_value: PyObject, - exc_tb: PyObject, + exc_type: Py, + exc_value: Py, + exc_tb: Py, ) -> PyResult { slf.entered = false; slf.close(py)?; @@ -129,7 +129,7 @@ impl ZstdDecompressionWriter { self.closed } - fn fileno(&self, py: Python) -> PyResult { + fn fileno(&self, py: Python) -> PyResult> { if let Ok(fileno) = self.writer.getattr(py, "fileno") { fileno.call0(py) } else { @@ -139,7 +139,7 @@ impl ZstdDecompressionWriter { } } - fn flush(&self, py: Python) -> PyResult { + fn flush(&self, py: Python) -> PyResult> { if self.closed { return Err(PyValueError::new_err("stream is closed")); } diff --git a/rust-ext/src/decompressor.rs b/rust-ext/src/decompressor.rs index 00a1acfd..ba8746b0 100644 --- a/rust-ext/src/decompressor.rs +++ b/rust-ext/src/decompressor.rs @@ -127,7 +127,7 @@ impl ZstdDecompressor { // Read all available input. loop { let read_object = ifh.call_method1("read", (read_size,))?; - let read_bytes = read_object.downcast::()?; + let read_bytes = read_object.cast::()?; let read_data = read_bytes.as_bytes(); if read_data.len() == 0 { diff --git a/rust-ext/src/decompressor_iterator.rs b/rust-ext/src/decompressor_iterator.rs index ac6f255c..dd3583c9 100644 --- a/rust-ext/src/decompressor_iterator.rs +++ b/rust-ext/src/decompressor_iterator.rs @@ -32,12 +32,12 @@ impl ZstdDecompressorIterator { slf } - fn __next__(mut slf: PyRefMut) -> PyResult> { + fn __next__(mut slf: PyRefMut) -> PyResult>> { if slf.finished_output { return Ok(None); } - let py = unsafe { Python::assume_gil_acquired() }; + let py = unsafe { Python::assume_attached() }; let mut dest_buffer: Vec = Vec::with_capacity(slf.write_size); diff --git a/rust-ext/src/decompressor_multi.rs b/rust-ext/src/decompressor_multi.rs index 787a63ac..49cefd68 100644 --- a/rust-ext/src/decompressor_multi.rs +++ b/rust-ext/src/decompressor_multi.rs @@ -49,7 +49,7 @@ pub fn multi_decompress_to_buffer( let mut sources = vec![]; - if let Ok(buffer) = frames.downcast::() { + if let Ok(buffer) = frames.cast::() { if decompressed_sizes.is_some() && frame_sizes.len() != buffer.len()? { return Err(PyValueError::new_err(format!( "decompressed_sizes size mismatch; expected {}, got {}", @@ -70,7 +70,7 @@ pub fn multi_decompress_to_buffer( decompressed_size: *frame_sizes.get(i).unwrap_or(&0) as _, }); } - } else if let Ok(collection) = frames.downcast::() { + } else if let Ok(collection) = frames.cast::() { let frames_count = collection.len()?; if decompressed_sizes.is_some() && frame_sizes.len() != frames_count { @@ -85,7 +85,7 @@ pub fn multi_decompress_to_buffer( let mut offset = 0; for buffer_obj in &collection.borrow().buffers { - let buffer = buffer_obj.downcast_bound::(py)?; + let buffer = buffer_obj.cast_bound::(py)?; let borrow = buffer.borrow(); for i in 0..borrow.segments.len() { diff --git a/rust-ext/src/stream.rs b/rust-ext/src/stream.rs index 72dbbe4e..4e121f8b 100644 --- a/rust-ext/src/stream.rs +++ b/rust-ext/src/stream.rs @@ -11,8 +11,8 @@ use { /// Describes a type that can be resolved to a `zstd_sys::ZSTD_inBuffer`. pub trait InBufferSource { - /// Obtain the PyObject this instance is reading from. - fn source_object(&self) -> &PyObject; + /// Obtain the Py this instance is reading from. + fn source_object(&self) -> &Py; /// The size of the input object, if available. fn source_size(&self) -> Option; @@ -29,7 +29,7 @@ pub trait InBufferSource { /// A data source where data is obtaine by calling `read()`. struct ReadSource { - source: PyObject, + source: Py, buffer: Option>, read_size: usize, finished: bool, @@ -37,7 +37,7 @@ struct ReadSource { } impl InBufferSource for ReadSource { - fn source_object(&self) -> &PyObject { + fn source_object(&self) -> &Py { &self.source } @@ -94,16 +94,16 @@ impl InBufferSource for ReadSource { } } -/// A data source where data is obtained from a `PyObject` +/// A data source where data is obtained from a `Py` /// conforming to the buffer protocol. struct BufferSource { - source: PyObject, + source: Py, buffer: PyBuffer, offset: usize, } impl InBufferSource for BufferSource { - fn source_object(&self) -> &PyObject { + fn source_object(&self) -> &Py { &self.source } diff --git a/tests/conftest.py b/tests/conftest.py index 84e71215..dcba0693 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,7 +25,9 @@ def pytest_configure(config): default_settings = hypothesis.settings(deadline=10000) hypothesis.settings.register_profile("default", default_settings) - ci_settings = hypothesis.settings(deadline=20000, max_examples=1000) + # Shared CI runners (notably the Intel macOS ones) are slow and noisy + # enough that per-example deadlines only produce flaky failures. + ci_settings = hypothesis.settings(deadline=None, max_examples=1000) hypothesis.settings.register_profile("ci", ci_settings) expensive_settings = hypothesis.settings(deadline=None, max_examples=10000) diff --git a/uv.lock b/uv.lock index 89fdd450..717aa37f 100644 --- a/uv.lock +++ b/uv.lock @@ -420,6 +420,13 @@ dev = [ { name = "pytest" }, { name = "pytest-xdist" }, ] +test = [ + { name = "cffi", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14' and platform_python_implementation != 'PyPy'" }, + { name = "cffi", version = "2.0.0b1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14' and platform_python_implementation != 'PyPy'" }, + { name = "hypothesis" }, + { name = "pytest" }, + { name = "pytest-xdist" }, +] [package.metadata] requires-dist = [ @@ -437,3 +444,10 @@ dev = [ { name = "pytest", specifier = ">=8.4.1" }, { name = "pytest-xdist", specifier = ">=3.8.0" }, ] +test = [ + { name = "cffi", marker = "python_full_version < '3.14' and platform_python_implementation != 'PyPy'", specifier = "~=1.17" }, + { name = "cffi", marker = "python_full_version >= '3.14' and platform_python_implementation != 'PyPy'", specifier = ">=2.0.0b0" }, + { name = "hypothesis", specifier = "==6.111.0" }, + { name = "pytest", specifier = ">=8.4.1" }, + { name = "pytest-xdist", specifier = ">=3.8.0" }, +]