From f0ed57757947c6008567a6502d7960d80d33559a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 22:30:22 +0000 Subject: [PATCH 1/6] Bump codecov/codecov-action from 4 to 5 (#4718) * Bump codecov/codecov-action from 4 to 5 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * format ci yml * fix "file" -> "files" deprecation --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: David Hewitt --- .github/workflows/ci.yml | 164 +++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 83 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af1217b31fa..b0bf3ea4bab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: "3.12" - run: python -m pip install --upgrade pip && pip install nox - uses: dtolnay/rust-toolchain@stable with: @@ -41,11 +41,10 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: "3.12" - name: resolve MSRV id: resolve-msrv - run: - echo MSRV=`python -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["package"]["rust-version"])'` >> $GITHUB_OUTPUT + run: echo MSRV=`python -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["package"]["rust-version"])'` >> $GITHUB_OUTPUT semver-checks: if: github.ref != 'refs/heads/main' @@ -55,7 +54,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: "3.12" - uses: obi1kenobi/cargo-semver-checks-action@v2 check-msrv: @@ -69,7 +68,7 @@ jobs: components: rust-src - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: "3.12" - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.event_name != 'merge_group' }} @@ -91,43 +90,44 @@ jobs: fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }} matrix: rust: [stable] - platform: [ - { - os: "macos-latest", - python-architecture: "arm64", - rust-target: "aarch64-apple-darwin", - }, - { - os: "ubuntu-latest", - python-architecture: "x64", - rust-target: "x86_64-unknown-linux-gnu", - }, - { - os: "ubuntu-latest", - python-architecture: "x64", - rust-target: "powerpc64le-unknown-linux-gnu", - }, - { - os: "ubuntu-latest", - python-architecture: "x64", - rust-target: "s390x-unknown-linux-gnu", - }, - { - os: "ubuntu-latest", - python-architecture: "x64", - rust-target: "wasm32-wasi", - }, - { - os: "windows-latest", - python-architecture: "x64", - rust-target: "x86_64-pc-windows-msvc", - }, - { - os: "windows-latest", - python-architecture: "x86", - rust-target: "i686-pc-windows-msvc", - }, - ] + platform: + [ + { + os: "macos-latest", + python-architecture: "arm64", + rust-target: "aarch64-apple-darwin", + }, + { + os: "ubuntu-latest", + python-architecture: "x64", + rust-target: "x86_64-unknown-linux-gnu", + }, + { + os: "ubuntu-latest", + python-architecture: "x64", + rust-target: "powerpc64le-unknown-linux-gnu", + }, + { + os: "ubuntu-latest", + python-architecture: "x64", + rust-target: "s390x-unknown-linux-gnu", + }, + { + os: "ubuntu-latest", + python-architecture: "x64", + rust-target: "wasm32-wasi", + }, + { + os: "windows-latest", + python-architecture: "x64", + rust-target: "x86_64-pc-windows-msvc", + }, + { + os: "windows-latest", + python-architecture: "x86", + rust-target: "i686-pc-windows-msvc", + }, + ] include: # Run beta clippy as a way to detect any incoming lints which may affect downstream users - rust: beta @@ -148,7 +148,7 @@ jobs: components: clippy,rust-src - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: "3.12" architecture: ${{ matrix.platform.python-architecture }} - uses: Swatinem/rust-cache@v2 with: @@ -177,15 +177,14 @@ jobs: matrix: rust: [stable] python-version: ["3.12"] - platform: - [ + platform: [ { - os: "macos-latest", # first available arm macos runner + os: "macos-latest", # first available arm macos runner python-architecture: "arm64", rust-target: "aarch64-apple-darwin", }, { - os: "macos-13", # last available x86_64 macos runner + os: "macos-13", # last available x86_64 macos runner python-architecture: "x64", rust-target: "x86_64-apple-darwin", }, @@ -234,18 +233,19 @@ jobs: fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }} matrix: rust: [stable] - python-version: [ - "3.7", - "3.8", - "3.9", - "3.10", - "3.11", - "3.12", - "3.13", - "pypy3.9", - "pypy3.10", - "graalpy24.0", - ] + python-version: + [ + "3.7", + "3.8", + "3.9", + "3.10", + "3.11", + "3.12", + "3.13", + "pypy3.9", + "pypy3.10", + "graalpy24.0", + ] platform: [ { @@ -389,7 +389,7 @@ jobs: with: # FIXME valgrind detects an issue with Python 3.12.5, needs investigation # whether it's a PyO3 issue or upstream CPython. - python-version: '3.12.4' + python-version: "3.12.4" - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.event_name != 'merge_group' }} @@ -410,7 +410,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: "3.12" - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.event_name != 'merge_group' }} @@ -432,7 +432,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: "3.12" - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.event_name != 'merge_group' }} @@ -442,7 +442,7 @@ jobs: - run: cargo rustdoc --lib --no-default-features --features full -Zunstable-options --config "build.rustdocflags=[\"--cfg\", \"docsrs\"]" coverage: - if : ${{ github.event_name != 'merge_group' }} + if: ${{ github.event_name != 'merge_group' }} needs: [fmt] name: coverage ${{ matrix.os }} strategy: @@ -453,7 +453,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: "3.12" - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.event_name != 'merge_group' }} @@ -464,9 +464,9 @@ jobs: uses: taiki-e/install-action@cargo-llvm-cov - run: python -m pip install --upgrade pip && pip install nox - run: nox -s coverage - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: - file: coverage.json + files: coverage.json name: ${{ matrix.os }} token: ${{ secrets.CODECOV_TOKEN }} @@ -552,11 +552,11 @@ jobs: test-free-threaded: needs: [fmt] - name: Free threaded tests - ${{ matrix.runner }} - runs-on: ${{ matrix.runner }} + name: Free threaded tests - ${{ matrix.os }} + runs-on: ${{ matrix.os }} strategy: matrix: - runner: ["ubuntu-latest", "macos-latest", "windows-latest"] + os: ["ubuntu-latest", "macos-latest", "windows-latest"] steps: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 @@ -568,7 +568,7 @@ jobs: # TODO: replace with actions/setup-python when there is support - uses: quansight-labs/setup-python@v5.3.1 with: - python-version: '3.13t' + python-version: "3.13t" - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - run: python3 -m sysconfig @@ -582,10 +582,10 @@ jobs: - name: Generate coverage report run: nox -s generate-coverage-report - name: Upload coverage report - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: - file: coverage.json - name: test-free-threaded + files: coverage.json + name: ${{ matrix.os }}-test-free-threaded token: ${{ secrets.CODECOV_TOKEN }} test-version-limits: @@ -596,7 +596,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: "3.12" - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.event_name != 'merge_group' }} @@ -620,7 +620,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: "3.12" - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.event_name != 'merge_group' }} @@ -659,7 +659,7 @@ jobs: flags: "-i python3.12 --features abi3 --features generate-import-lib" manylinux: off # macos x86_64 -> aarch64 - - os: "macos-13" # last x86_64 macos runners + - os: "macos-13" # last x86_64 macos runners target: "aarch64-apple-darwin" # macos aarch64 -> x86_64 - os: "macos-latest" @@ -668,11 +668,10 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: "3.12" - uses: Swatinem/rust-cache@v2 with: - workspaces: - examples/maturin-starter + workspaces: examples/maturin-starter save-if: ${{ github.event_name != 'merge_group' }} key: ${{ matrix.target }} - name: Setup cross-compiler @@ -692,11 +691,10 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: "3.12" - uses: Swatinem/rust-cache@v2 with: - workspaces: - examples/maturin-starter + workspaces: examples/maturin-starter save-if: ${{ github.event_name != 'merge_group' }} - uses: actions/cache/restore@v4 with: From e99f28fb125337c907b72aa77ceaaacc5ae6da3a Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 19 Nov 2024 22:21:17 +0000 Subject: [PATCH 2/6] add `IntoPyObjectExt` trait (#4708) * add `IntoPyObjectExt` trait * adjust method names, more docs & usage internally * more uses of `IntoPyObjectExt` * guide docs * newsfragment * fixup doctest * Update guide/src/conversions/traits.md Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com> --------- Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com> --- guide/src/conversions/traits.md | 82 ++++++++++++++------------ newsfragments/4708.added.md | 1 + pyo3-macros-backend/src/pyclass.rs | 35 +++--------- pyo3-macros-backend/src/pyimpl.rs | 5 +- src/conversion.rs | 77 +++++++++++++++++++++---- src/conversions/either.rs | 28 ++------- src/coroutine.rs | 7 +-- src/impl_/pyclass.rs | 15 ++--- src/impl_/wrap.rs | 11 +--- src/instance.rs | 4 +- src/lib.rs | 2 +- src/types/any.rs | 92 ++++++------------------------ src/types/dict.rs | 38 ++++-------- src/types/frozenset.rs | 10 +--- src/types/list.rs | 30 +++------- src/types/module.rs | 16 +++--- src/types/sequence.rs | 28 +++------ src/types/set.rs | 20 ++----- src/types/tuple.rs | 28 +++++---- src/types/weakref/proxy.rs | 11 ++-- src/types/weakref/reference.rs | 11 ++-- 21 files changed, 218 insertions(+), 333 deletions(-) create mode 100644 newsfragments/4708.added.md diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 5e3da6ea1b0..c4e8f14866c 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -490,9 +490,11 @@ If the input is neither a string nor an integer, the error message will be: - the function signature must be `fn(&Bound) -> PyResult` where `T` is the Rust type of the argument. ### `IntoPyObject` -This trait defines the to-python conversion for a Rust type. All types in PyO3 implement this trait, +The ['IntoPyObject'] trait defines the to-python conversion for a Rust type. All types in PyO3 implement this trait, as does a `#[pyclass]` which doesn't use `extends`. +This trait defines a single method, `into_pyobject()`, which returns a [`Result`] with `Ok` and `Err` types depending on the input value. For convenience, there is a companion [`IntoPyObjectExt`] trait which adds methods such as `into_py_any()` which converts the `Ok` and `Err` types to commonly used types (in the case of `into_py_any()`, `Py` and `PyErr` respectively). + Occasionally you may choose to implement this for custom types which are mapped to Python types _without_ having a unique python type. @@ -510,7 +512,7 @@ into `PyTuple` with the fields in declaration order. // structs convert into `PyDict` with field names as keys #[derive(IntoPyObject)] -struct Struct { +struct Struct { count: usize, obj: Py, } @@ -532,11 +534,11 @@ forward the implementation to the inner type. // newtype tuple structs are implicitly `transparent` #[derive(IntoPyObject)] -struct TransparentTuple(PyObject); +struct TransparentTuple(PyObject); #[derive(IntoPyObject)] #[pyo3(transparent)] -struct TransparentStruct<'py> { +struct TransparentStruct<'py> { inner: Bound<'py, PyAny>, // `'py` lifetime will be used as the Python lifetime } ``` @@ -582,7 +584,7 @@ impl<'py> IntoPyObject<'py> for MyPyObjectWrapper { } } -// equivalent to former `ToPyObject` implementations +// equivalent to former `ToPyObject` implementations impl<'a, 'py> IntoPyObject<'py> for &'a MyPyObjectWrapper { type Target = PyAny; type Output = Borrowed<'a, 'py, Self::Target>; // `Borrowed` can be used to optimized reference counting @@ -594,38 +596,6 @@ impl<'a, 'py> IntoPyObject<'py> for &'a MyPyObjectWrapper { } ``` -### `IntoPy` - -
- -⚠️ Warning: API update in progress 🛠️ - -PyO3 0.23 has introduced `IntoPyObject` as the new trait for to-python conversions. While `#[pymethods]` and `#[pyfunction]` contain a compatibility layer to allow `IntoPy` as a return type, all Python API have been migrated to use `IntoPyObject`. To migrate implement `IntoPyObject` for your type. -
- - -This trait defines the to-python conversion for a Rust type. It is usually implemented as -`IntoPy`, which is the trait needed for returning a value from `#[pyfunction]` and -`#[pymethods]`. - -All types in PyO3 implement this trait, as does a `#[pyclass]` which doesn't use `extends`. - -Occasionally you may choose to implement this for custom types which are mapped to Python types -_without_ having a unique python type. - -```rust -use pyo3::prelude::*; -# #[allow(dead_code)] -struct MyPyObjectWrapper(PyObject); - -#[allow(deprecated)] -impl IntoPy for MyPyObjectWrapper { - fn into_py(self, py: Python<'_>) -> PyObject { - self.0 - } -} -``` - #### `BoundObject` for conversions that may be `Bound` or `Borrowed` `IntoPyObject::into_py_object` returns either `Bound` or `Borrowed` depending on the implementation for a concrete type. For example, the `IntoPyObject` implementation for `u32` produces a `Bound<'py, PyInt>` and the `bool` implementation produces a `Borrowed<'py, 'py, PyBool>`: @@ -672,6 +642,8 @@ where the_vec.iter() .map(|x| { Ok( + // Note: the below is equivalent to `x.into_py_any()` + // from the `IntoPyObjectExt` trait x.into_pyobject(py) .map_err(Into::into)? .into_any() @@ -693,6 +665,38 @@ let vec_of_pyobjs: Vec> = Python::with_gil(|py| { In the example above we used `BoundObject::into_any` and `BoundObject::unbind` to manipulate the python types and smart pointers into the result type we wanted to produce from the function. +### `IntoPy` + +
+ +⚠️ Warning: API update in progress 🛠️ + +PyO3 0.23 has introduced `IntoPyObject` as the new trait for to-python conversions. While `#[pymethods]` and `#[pyfunction]` contain a compatibility layer to allow `IntoPy` as a return type, all Python API have been migrated to use `IntoPyObject`. To migrate implement `IntoPyObject` for your type. +
+ + +This trait defines the to-python conversion for a Rust type. It is usually implemented as +`IntoPy`, which is the trait needed for returning a value from `#[pyfunction]` and +`#[pymethods]`. + +All types in PyO3 implement this trait, as does a `#[pyclass]` which doesn't use `extends`. + +Occasionally you may choose to implement this for custom types which are mapped to Python types +_without_ having a unique python type. + +```rust +use pyo3::prelude::*; +# #[allow(dead_code)] +struct MyPyObjectWrapper(PyObject); + +#[allow(deprecated)] +impl IntoPy for MyPyObjectWrapper { + fn into_py(self, py: Python<'_>) -> PyObject { + self.0 + } +} +``` + ### The `ToPyObject` trait
@@ -710,8 +714,12 @@ same purpose, except that it consumes `self`. [`IntoPy`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.IntoPy.html [`FromPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.FromPyObject.html [`ToPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.ToPyObject.html +[`IntoPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.IntoPyObject.html +[`IntoPyObjectExt`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.IntoPyObjectExt.html [`PyObject`]: {{#PYO3_DOCS_URL}}/pyo3/type.PyObject.html [`PyRef`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html [`PyRefMut`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRefMut.html [`BoundObject`]: {{#PYO3_DOCS_URL}}/pyo3/instance/trait.BoundObject.html + +[`Result`]: https://doc.rust-lang.org/stable/std/result/enum.Result.html diff --git a/newsfragments/4708.added.md b/newsfragments/4708.added.md new file mode 100644 index 00000000000..c8c91d16221 --- /dev/null +++ b/newsfragments/4708.added.md @@ -0,0 +1 @@ +Add `IntoPyObjectExt` trait. diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index a83ba880271..87d02c6f878 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1367,10 +1367,7 @@ fn impl_complex_enum_tuple_variant_getitem( .map(|i| { let field_access = format_ident!("_{}", i); quote! { #i => - #pyo3_path::IntoPyObject::into_pyobject(#variant_cls::#field_access(slf)?, py) - .map(#pyo3_path::BoundObject::into_any) - .map(#pyo3_path::BoundObject::unbind) - .map_err(::std::convert::Into::into) + #pyo3_path::IntoPyObjectExt::into_py_any(#variant_cls::#field_access(slf)?, py) } }) .collect(); @@ -1852,16 +1849,10 @@ fn pyclass_richcmp_arms( .map(|span| { quote_spanned! { span => #pyo3_path::pyclass::CompareOp::Eq => { - #pyo3_path::IntoPyObject::into_pyobject(self_val == other, py) - .map(#pyo3_path::BoundObject::into_any) - .map(#pyo3_path::BoundObject::unbind) - .map_err(::std::convert::Into::into) + #pyo3_path::IntoPyObjectExt::into_py_any(self_val == other, py) }, #pyo3_path::pyclass::CompareOp::Ne => { - #pyo3_path::IntoPyObject::into_pyobject(self_val != other, py) - .map(#pyo3_path::BoundObject::into_any) - .map(#pyo3_path::BoundObject::unbind) - .map_err(::std::convert::Into::into) + #pyo3_path::IntoPyObjectExt::into_py_any(self_val != other, py) }, } }) @@ -1876,28 +1867,16 @@ fn pyclass_richcmp_arms( .map(|ord| { quote_spanned! { ord.span() => #pyo3_path::pyclass::CompareOp::Gt => { - #pyo3_path::IntoPyObject::into_pyobject(self_val > other, py) - .map(#pyo3_path::BoundObject::into_any) - .map(#pyo3_path::BoundObject::unbind) - .map_err(::std::convert::Into::into) + #pyo3_path::IntoPyObjectExt::into_py_any(self_val > other, py) }, #pyo3_path::pyclass::CompareOp::Lt => { - #pyo3_path::IntoPyObject::into_pyobject(self_val < other, py) - .map(#pyo3_path::BoundObject::into_any) - .map(#pyo3_path::BoundObject::unbind) - .map_err(::std::convert::Into::into) + #pyo3_path::IntoPyObjectExt::into_py_any(self_val < other, py) }, #pyo3_path::pyclass::CompareOp::Le => { - #pyo3_path::IntoPyObject::into_pyobject(self_val <= other, py) - .map(#pyo3_path::BoundObject::into_any) - .map(#pyo3_path::BoundObject::unbind) - .map_err(::std::convert::Into::into) + #pyo3_path::IntoPyObjectExt::into_py_any(self_val <= other, py) }, #pyo3_path::pyclass::CompareOp::Ge => { - #pyo3_path::IntoPyObject::into_pyobject(self_val >= other, py) - .map(#pyo3_path::BoundObject::into_any) - .map(#pyo3_path::BoundObject::unbind) - .map_err(::std::convert::Into::into) + #pyo3_path::IntoPyObjectExt::into_py_any(self_val >= other, py) }, } }) diff --git a/pyo3-macros-backend/src/pyimpl.rs b/pyo3-macros-backend/src/pyimpl.rs index 614db3c5459..72f06721ec4 100644 --- a/pyo3-macros-backend/src/pyimpl.rs +++ b/pyo3-macros-backend/src/pyimpl.rs @@ -213,10 +213,7 @@ pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec, ctx: &Ctx) -> MethodAndMe let associated_method = quote! { fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> { - #pyo3_path::IntoPyObject::into_pyobject(#cls::#member, py) - .map(#pyo3_path::BoundObject::into_any) - .map(#pyo3_path::BoundObject::unbind) - .map_err(::std::convert::Into::into) + #pyo3_path::IntoPyObjectExt::into_py_any(#cls::#member, py) } }; diff --git a/src/conversion.rs b/src/conversion.rs index 5986aefd6e3..82ad4d84977 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -178,8 +178,23 @@ pub trait IntoPy: Sized { /// Defines a conversion from a Rust type to a Python object, which may fail. /// +/// This trait has `#[derive(IntoPyObject)]` to automatically implement it for simple types and +/// `#[derive(IntoPyObjectRef)]` to implement the same for references. +/// /// It functions similarly to std's [`TryInto`] trait, but requires a [GIL token](Python) /// as an argument. +/// +/// The [`into_pyobject`][IntoPyObject::into_pyobject] method is designed for maximum flexibility and efficiency; it +/// - allows for a concrete Python type to be returned (the [`Target`][IntoPyObject::Target] associated type) +/// - allows for the smart pointer containing the Python object to be either `Bound<'py, Self::Target>` or `Borrowed<'a, 'py, Self::Target>` +/// to avoid unnecessary reference counting overhead +/// - allows for a custom error type to be returned in the event of a conversion error to avoid +/// unnecessarily creating a Python exception +/// +/// # See also +/// +/// - The [`IntoPyObjectExt`] trait, which provides convenience methods for common usages of +/// `IntoPyObject` which erase type information and convert errors to `PyErr`. #[cfg_attr( diagnostic_namespace, diagnostic::on_unimplemented( @@ -227,12 +242,7 @@ pub trait IntoPyObject<'py>: Sized { I: IntoIterator + AsRef<[Self]>, I::IntoIter: ExactSizeIterator, { - let mut iter = iter.into_iter().map(|e| { - e.into_pyobject(py) - .map(BoundObject::into_any) - .map(BoundObject::into_bound) - .map_err(Into::into) - }); + let mut iter = iter.into_iter().map(|e| e.into_bound_py_any(py)); let list = crate::types::list::try_new_from_iter(py, &mut iter); list.map(Bound::into_any) } @@ -250,12 +260,7 @@ pub trait IntoPyObject<'py>: Sized { I: IntoIterator + AsRef<[::BaseType]>, I::IntoIter: ExactSizeIterator, { - let mut iter = iter.into_iter().map(|e| { - e.into_pyobject(py) - .map(BoundObject::into_any) - .map(BoundObject::into_bound) - .map_err(Into::into) - }); + let mut iter = iter.into_iter().map(|e| e.into_bound_py_any(py)); let list = crate::types::list::try_new_from_iter(py, &mut iter); list.map(Bound::into_any) } @@ -347,6 +352,54 @@ where } } +mod into_pyobject_ext { + pub trait Sealed {} + impl<'py, T> Sealed for T where T: super::IntoPyObject<'py> {} +} + +/// Convenience methods for common usages of [`IntoPyObject`]. Every type that implements +/// [`IntoPyObject`] also implements this trait. +/// +/// These methods: +/// - Drop type information from the output, returning a `PyAny` object. +/// - Always convert the `Error` type to `PyErr`, which may incur a performance penalty but it +/// more convenient in contexts where the `?` operator would produce a `PyErr` anyway. +pub trait IntoPyObjectExt<'py>: IntoPyObject<'py> + into_pyobject_ext::Sealed { + /// Converts `self` into an owned Python object, dropping type information. + #[inline] + fn into_bound_py_any(self, py: Python<'py>) -> PyResult> { + match self.into_pyobject(py) { + Ok(obj) => Ok(obj.into_any().into_bound()), + Err(err) => Err(err.into()), + } + } + + /// Converts `self` into an owned Python object, dropping type information and unbinding it + /// from the `'py` lifetime. + #[inline] + fn into_py_any(self, py: Python<'py>) -> PyResult> { + match self.into_pyobject(py) { + Ok(obj) => Ok(obj.into_any().unbind()), + Err(err) => Err(err.into()), + } + } + + /// Converts `self` into a Python object. + /// + /// This is equivalent to calling [`into_pyobject`][IntoPyObject::into_pyobject] followed + /// with `.map_err(Into::into)` to convert the error type to [`PyErr`]. This is helpful + /// for generic code which wants to make use of the `?` operator. + #[inline] + fn into_pyobject_or_pyerr(self, py: Python<'py>) -> PyResult { + match self.into_pyobject(py) { + Ok(obj) => Ok(obj), + Err(err) => Err(err.into()), + } + } +} + +impl<'py, T> IntoPyObjectExt<'py> for T where T: IntoPyObject<'py> {} + /// Extract a type from a Python object. /// /// diff --git a/src/conversions/either.rs b/src/conversions/either.rs index 1fdcd22d7e2..a514b1fde8d 100644 --- a/src/conversions/either.rs +++ b/src/conversions/either.rs @@ -47,8 +47,8 @@ #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; use crate::{ - conversion::IntoPyObject, exceptions::PyTypeError, types::any::PyAnyMethods, Bound, - BoundObject, FromPyObject, PyAny, PyErr, PyObject, PyResult, Python, + exceptions::PyTypeError, types::any::PyAnyMethods, Bound, FromPyObject, IntoPyObject, + IntoPyObjectExt, PyAny, PyErr, PyObject, PyResult, Python, }; #[allow(deprecated)] use crate::{IntoPy, ToPyObject}; @@ -82,16 +82,8 @@ where fn into_pyobject(self, py: Python<'py>) -> Result { match self { - Either::Left(l) => l - .into_pyobject(py) - .map(BoundObject::into_any) - .map(BoundObject::into_bound) - .map_err(Into::into), - Either::Right(r) => r - .into_pyobject(py) - .map(BoundObject::into_any) - .map(BoundObject::into_bound) - .map_err(Into::into), + Either::Left(l) => l.into_bound_py_any(py), + Either::Right(r) => r.into_bound_py_any(py), } } } @@ -108,16 +100,8 @@ where fn into_pyobject(self, py: Python<'py>) -> Result { match self { - Either::Left(l) => l - .into_pyobject(py) - .map(BoundObject::into_any) - .map(BoundObject::into_bound) - .map_err(Into::into), - Either::Right(r) => r - .into_pyobject(py) - .map(BoundObject::into_any) - .map(BoundObject::into_bound) - .map_err(Into::into), + Either::Left(l) => l.into_bound_py_any(py), + Either::Right(r) => r.into_bound_py_any(py), } } } diff --git a/src/coroutine.rs b/src/coroutine.rs index aa4335e9d0b..671defb1770 100644 --- a/src/coroutine.rs +++ b/src/coroutine.rs @@ -15,7 +15,7 @@ use crate::{ exceptions::{PyAttributeError, PyRuntimeError, PyStopIteration}, panic::PanicException, types::{string::PyStringMethods, PyIterator, PyString}, - Bound, BoundObject, IntoPyObject, Py, PyAny, PyErr, PyObject, PyResult, Python, + Bound, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyErr, PyObject, PyResult, Python, }; pub(crate) mod cancel; @@ -60,10 +60,7 @@ impl Coroutine { let wrap = async move { let obj = future.await.map_err(Into::into)?; // SAFETY: GIL is acquired when future is polled (see `Coroutine::poll`) - obj.into_pyobject(unsafe { Python::assume_gil_acquired() }) - .map(BoundObject::into_any) - .map(BoundObject::unbind) - .map_err(Into::into) + obj.into_py_any(unsafe { Python::assume_gil_acquired() }) }; Self { name: name.map(Bound::unbind), diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index 8e7e8cf844f..7bb61442ec5 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -1,5 +1,4 @@ use crate::{ - conversion::IntoPyObject, exceptions::{PyAttributeError, PyNotImplementedError, PyRuntimeError, PyValueError}, ffi, impl_::{ @@ -10,7 +9,8 @@ use crate::{ }, pycell::PyBorrowError, types::{any::PyAnyMethods, PyBool}, - Borrowed, BoundObject, Py, PyAny, PyClass, PyErr, PyRef, PyResult, PyTypeInfo, Python, + Borrowed, BoundObject, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyClass, PyErr, PyRef, + PyResult, PyTypeInfo, Python, }; #[allow(deprecated)] use crate::{IntoPy, ToPyObject}; @@ -1532,10 +1532,7 @@ impl ConvertField, { - obj.into_pyobject(py) - .map(BoundObject::into_any) - .map(BoundObject::unbind) - .map_err(Into::into) + obj.into_py_any(py) } } @@ -1545,11 +1542,7 @@ impl ConvertField, { - obj.clone() - .into_pyobject(py) - .map(BoundObject::into_any) - .map(BoundObject::unbind) - .map_err(Into::into) + obj.clone().into_py_any(py) } } diff --git a/src/impl_/wrap.rs b/src/impl_/wrap.rs index 9381828245a..7b214219408 100644 --- a/src/impl_/wrap.rs +++ b/src/impl_/wrap.rs @@ -2,9 +2,7 @@ use std::{convert::Infallible, marker::PhantomData, ops::Deref}; #[allow(deprecated)] use crate::IntoPy; -use crate::{ - conversion::IntoPyObject, ffi, types::PyNone, Bound, BoundObject, PyObject, PyResult, Python, -}; +use crate::{ffi, types::PyNone, Bound, IntoPyObject, IntoPyObjectExt, PyObject, PyResult, Python}; /// Used to wrap values in `Option` for default arguments. pub trait SomeWrap { @@ -97,9 +95,7 @@ impl<'py, T: IntoPyObject<'py>, E> IntoPyObjectConverter> { where T: IntoPyObject<'py>, { - obj.and_then(|obj| obj.into_pyobject(py).map_err(Into::into)) - .map(BoundObject::into_any) - .map(BoundObject::unbind) + obj.and_then(|obj| obj.into_py_any(py)) } #[inline] @@ -107,8 +103,7 @@ impl<'py, T: IntoPyObject<'py>, E> IntoPyObjectConverter> { where T: IntoPyObject<'py>, { - obj.and_then(|obj| obj.into_pyobject(py).map_err(Into::into)) - .map(BoundObject::into_bound) + obj.and_then(|obj| obj.into_bound_py_any(py)) .map(Bound::into_ptr) } } diff --git a/src/instance.rs b/src/instance.rs index 14d2d11b5d7..840416116f3 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1466,7 +1466,7 @@ impl Py { /// # Example: `intern!`ing the attribute name /// /// ``` - /// # use pyo3::{intern, pyfunction, types::PyModule, IntoPyObject, PyObject, Python, PyResult}; + /// # use pyo3::{intern, pyfunction, types::PyModule, IntoPyObjectExt, PyObject, Python, PyResult}; /// # /// #[pyfunction] /// fn set_answer(ob: PyObject, py: Python<'_>) -> PyResult<()> { @@ -1474,7 +1474,7 @@ impl Py { /// } /// # /// # Python::with_gil(|py| { - /// # let ob = PyModule::new(py, "empty").unwrap().into_pyobject(py).unwrap().into_any().unbind(); + /// # let ob = PyModule::new(py, "empty").unwrap().into_py_any(py).unwrap(); /// # set_answer(ob, py).unwrap(); /// # }); /// ``` diff --git a/src/lib.rs b/src/lib.rs index 265824adab1..46a2fd53d32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -334,7 +334,7 @@ #![doc = concat!("[Features chapter of the guide]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/features.html#features-reference \"Features Reference - PyO3 user guide\"")] //! [`Ungil`]: crate::marker::Ungil pub use crate::class::*; -pub use crate::conversion::{AsPyPointer, FromPyObject, IntoPyObject}; +pub use crate::conversion::{AsPyPointer, FromPyObject, IntoPyObject, IntoPyObjectExt}; #[allow(deprecated)] pub use crate::conversion::{IntoPy, ToPyObject}; pub use crate::err::{DowncastError, DowncastIntoError, PyErr, PyErrArguments, PyResult, ToPyErr}; diff --git a/src/types/any.rs b/src/types/any.rs index e620cf6d137..d060c187631 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -11,7 +11,7 @@ use crate::type_object::{PyTypeCheck, PyTypeInfo}; #[cfg(not(any(PyPy, GraalPy)))] use crate::types::PySuper; use crate::types::{PyDict, PyIterator, PyList, PyString, PyTuple, PyType}; -use crate::{err, ffi, Borrowed, BoundObject, Python}; +use crate::{err, ffi, Borrowed, BoundObject, IntoPyObjectExt, Python}; use std::cell::UnsafeCell; use std::cmp::Ordering; use std::os::raw::c_int; @@ -922,11 +922,7 @@ macro_rules! implement_binop { let py = self.py(); inner( self, - other - .into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + other.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } }; @@ -996,15 +992,8 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { let py = self.py(); inner( self, - attr_name - .into_pyobject(py) - .map_err(Into::into)? - .as_borrowed(), - value - .into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + attr_name.into_pyobject_or_pyerr(py)?.as_borrowed(), + value.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -1019,13 +1008,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { } let py = self.py(); - inner( - self, - attr_name - .into_pyobject(py) - .map_err(Into::into)? - .as_borrowed(), - ) + inner(self, attr_name.into_pyobject_or_pyerr(py)?.as_borrowed()) } fn compare(&self, other: O) -> PyResult @@ -1057,11 +1040,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { let py = self.py(); inner( self, - other - .into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + other.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -1083,11 +1062,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { let py = self.py(); inner( self, - other - .into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + other.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), compare_op, ) } @@ -1198,11 +1173,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { let py = self.py(); inner( self, - other - .into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + other.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -1227,16 +1198,8 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { let py = self.py(); inner( self, - other - .into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), - modulus - .into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + other.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), + modulus.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -1264,11 +1227,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { } let py = self.py(); - inner( - self, - args.into_pyobject(py).map_err(Into::into)?.as_borrowed(), - kwargs, - ) + inner(self, args.into_pyobject_or_pyerr(py)?.as_borrowed(), kwargs) } #[inline] @@ -1304,7 +1263,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { N: IntoPyObject<'py, Target = PyString>, { let py = self.py(); - let name = name.into_pyobject(py).map_err(Into::into)?.into_bound(); + let name = name.into_pyobject_or_pyerr(py)?.into_bound(); unsafe { ffi::compat::PyObject_CallMethodNoArgs(self.as_ptr(), name.as_ptr()) .assume_owned_or_err(py) @@ -1354,10 +1313,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { let py = self.py(); inner( self, - key.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -1379,15 +1335,8 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { let py = self.py(); inner( self, - key.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), - value - .into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), + value.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -1404,10 +1353,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { let py = self.py(); inner( self, - key.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -1574,11 +1520,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { let py = self.py(); inner( self, - value - .into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + value.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } diff --git a/src/types/dict.rs b/src/types/dict.rs index 129f32dc9e1..b3c8e37962b 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -4,7 +4,7 @@ use crate::ffi_ptr_ext::FfiPtrExt; use crate::instance::{Borrowed, Bound}; use crate::py_result_ext::PyResultExt; use crate::types::{PyAny, PyAnyMethods, PyList, PyMapping}; -use crate::{ffi, BoundObject, IntoPyObject, Python}; +use crate::{ffi, BoundObject, IntoPyObject, IntoPyObjectExt, Python}; /// Represents a Python `dict`. /// @@ -239,7 +239,7 @@ impl<'py> PyDictMethods<'py> for Bound<'py, PyDict> { where K: IntoPyObject<'py>, { - fn inner(dict: &Bound<'_, PyDict>, key: &Bound<'_, PyAny>) -> PyResult { + fn inner(dict: &Bound<'_, PyDict>, key: Borrowed<'_, '_, PyAny>) -> PyResult { match unsafe { ffi::PyDict_Contains(dict.as_ptr(), key.as_ptr()) } { 1 => Ok(true), 0 => Ok(false), @@ -250,10 +250,7 @@ impl<'py> PyDictMethods<'py> for Bound<'py, PyDict> { let py = self.py(); inner( self, - &key.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -263,7 +260,7 @@ impl<'py> PyDictMethods<'py> for Bound<'py, PyDict> { { fn inner<'py>( dict: &Bound<'py, PyDict>, - key: &Bound<'_, PyAny>, + key: Borrowed<'_, '_, PyAny>, ) -> PyResult>> { let py = dict.py(); let mut result: *mut ffi::PyObject = std::ptr::null_mut(); @@ -283,10 +280,7 @@ impl<'py> PyDictMethods<'py> for Bound<'py, PyDict> { let py = self.py(); inner( self, - &key.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -297,8 +291,8 @@ impl<'py> PyDictMethods<'py> for Bound<'py, PyDict> { { fn inner( dict: &Bound<'_, PyDict>, - key: &Bound<'_, PyAny>, - value: &Bound<'_, PyAny>, + key: Borrowed<'_, '_, PyAny>, + value: Borrowed<'_, '_, PyAny>, ) -> PyResult<()> { err::error_on_minusone(dict.py(), unsafe { ffi::PyDict_SetItem(dict.as_ptr(), key.as_ptr(), value.as_ptr()) @@ -308,15 +302,8 @@ impl<'py> PyDictMethods<'py> for Bound<'py, PyDict> { let py = self.py(); inner( self, - &key.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), - &value - .into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), + value.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -324,7 +311,7 @@ impl<'py> PyDictMethods<'py> for Bound<'py, PyDict> { where K: IntoPyObject<'py>, { - fn inner(dict: &Bound<'_, PyDict>, key: &Bound<'_, PyAny>) -> PyResult<()> { + fn inner(dict: &Bound<'_, PyDict>, key: Borrowed<'_, '_, PyAny>) -> PyResult<()> { err::error_on_minusone(dict.py(), unsafe { ffi::PyDict_DelItem(dict.as_ptr(), key.as_ptr()) }) @@ -333,10 +320,7 @@ impl<'py> PyDictMethods<'py> for Bound<'py, PyDict> { let py = self.py(); inner( self, - &key.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } diff --git a/src/types/frozenset.rs b/src/types/frozenset.rs index 3c5a62a01d8..954c49b5902 100644 --- a/src/types/frozenset.rs +++ b/src/types/frozenset.rs @@ -1,4 +1,3 @@ -use crate::conversion::IntoPyObject; use crate::types::PyIterator; use crate::{ err::{self, PyErr, PyResult}, @@ -8,7 +7,7 @@ use crate::{ types::any::PyAnyMethods, Bound, PyAny, Python, }; -use crate::{Borrowed, BoundObject}; +use crate::{Borrowed, BoundObject, IntoPyObject, IntoPyObjectExt}; use std::ptr; /// Allows building a Python `frozenset` one item at a time @@ -181,10 +180,7 @@ impl<'py> PyFrozenSetMethods<'py> for Bound<'py, PyFrozenSet> { let py = self.py(); inner( self, - key.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -266,7 +262,7 @@ where let ptr = set.as_ptr(); for e in elements { - let obj = e.into_pyobject(py).map_err(Into::into)?; + let obj = e.into_pyobject_or_pyerr(py)?; err::error_on_minusone(py, unsafe { ffi::PySet_Add(ptr, obj.as_ptr()) })?; } diff --git a/src/types/list.rs b/src/types/list.rs index f00c194739f..af2b557cba9 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -5,7 +5,9 @@ use crate::ffi::{self, Py_ssize_t}; use crate::ffi_ptr_ext::FfiPtrExt; use crate::internal_tricks::get_ssize_index; use crate::types::{PySequence, PyTuple}; -use crate::{Borrowed, Bound, BoundObject, IntoPyObject, PyAny, PyErr, PyObject, Python}; +use crate::{ + Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt, PyAny, PyErr, PyObject, Python, +}; use crate::types::any::PyAnyMethods; use crate::types::sequence::PySequenceMethods; @@ -104,12 +106,7 @@ impl PyList { T: IntoPyObject<'py>, U: ExactSizeIterator, { - let iter = elements.into_iter().map(|e| { - e.into_pyobject(py) - .map(BoundObject::into_any) - .map(BoundObject::into_bound) - .map_err(Into::into) - }); + let iter = elements.into_iter().map(|e| e.into_bound_py_any(py)); try_new_from_iter(py, iter) } @@ -339,14 +336,7 @@ impl<'py> PyListMethods<'py> for Bound<'py, PyList> { } let py = self.py(); - inner( - self, - index, - item.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .into_bound(), - ) + inner(self, index, item.into_bound_py_any(py)?) } /// Deletes the `index`th element of self. @@ -394,10 +384,7 @@ impl<'py> PyListMethods<'py> for Bound<'py, PyList> { let py = self.py(); inner( self, - item.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + item.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -422,10 +409,7 @@ impl<'py> PyListMethods<'py> for Bound<'py, PyList> { inner( self, index, - item.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + item.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } diff --git a/src/types/module.rs b/src/types/module.rs index d3e59c85198..fd7299cb084 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -1,4 +1,3 @@ -use crate::conversion::IntoPyObject; use crate::err::{PyErr, PyResult}; use crate::ffi_ptr_ext::FfiPtrExt; use crate::impl_::callback::IntoPyCallbackOutput; @@ -7,7 +6,10 @@ use crate::pyclass::PyClass; use crate::types::{ any::PyAnyMethods, list::PyListMethods, PyAny, PyCFunction, PyDict, PyList, PyString, }; -use crate::{exceptions, ffi, Borrowed, Bound, BoundObject, Py, PyObject, Python}; +use crate::{ + exceptions, ffi, Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt, Py, PyObject, + Python, +}; use std::ffi::{CStr, CString}; #[cfg(all(not(Py_LIMITED_API), Py_GIL_DISABLED))] use std::os::raw::c_int; @@ -89,7 +91,7 @@ impl PyModule { where N: IntoPyObject<'py, Target = PyString>, { - let name = name.into_pyobject(py).map_err(Into::into)?; + let name = name.into_pyobject_or_pyerr(py)?; unsafe { ffi::PyImport_Import(name.as_ptr()) .assume_owned_or_err(py) @@ -508,12 +510,8 @@ impl<'py> PyModuleMethods<'py> for Bound<'py, PyModule> { let py = self.py(); inner( self, - name.into_pyobject(py).map_err(Into::into)?.as_borrowed(), - value - .into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + name.into_pyobject_or_pyerr(py)?.as_borrowed(), + value.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } diff --git a/src/types/sequence.rs b/src/types/sequence.rs index 0801704f700..bc2643dcf8e 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -9,7 +9,10 @@ use crate::py_result_ext::PyResultExt; use crate::sync::GILOnceCell; use crate::type_object::PyTypeInfo; use crate::types::{any::PyAnyMethods, PyAny, PyList, PyString, PyTuple, PyType}; -use crate::{ffi, Borrowed, BoundObject, FromPyObject, IntoPyObject, Py, PyTypeCheck, Python}; +use crate::{ + ffi, Borrowed, BoundObject, FromPyObject, IntoPyObject, IntoPyObjectExt, Py, PyTypeCheck, + Python, +}; /// Represents a reference to a Python object supporting the sequence protocol. /// @@ -221,10 +224,7 @@ impl<'py> PySequenceMethods<'py> for Bound<'py, PySequence> { inner( self, i, - item.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + item.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -269,11 +269,7 @@ impl<'py> PySequenceMethods<'py> for Bound<'py, PySequence> { let py = self.py(); inner( self, - value - .into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + value.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -294,11 +290,7 @@ impl<'py> PySequenceMethods<'py> for Bound<'py, PySequence> { let py = self.py(); inner( self, - value - .into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + value.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -316,11 +308,7 @@ impl<'py> PySequenceMethods<'py> for Bound<'py, PySequence> { let py = self.py(); inner( self, - value - .into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + value.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } diff --git a/src/types/set.rs b/src/types/set.rs index e7c24f5b1ea..d5e39ebc83d 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -1,4 +1,3 @@ -use crate::conversion::IntoPyObject; use crate::types::PyIterator; #[allow(deprecated)] use crate::ToPyObject; @@ -9,7 +8,7 @@ use crate::{ py_result_ext::PyResultExt, types::any::PyAnyMethods, }; -use crate::{ffi, Borrowed, BoundObject, PyAny, Python}; +use crate::{ffi, Borrowed, BoundObject, IntoPyObject, IntoPyObjectExt, PyAny, Python}; use std::ptr; /// Represents a Python `set`. @@ -161,10 +160,7 @@ impl<'py> PySetMethods<'py> for Bound<'py, PySet> { let py = self.py(); inner( self, - key.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -183,10 +179,7 @@ impl<'py> PySetMethods<'py> for Bound<'py, PySet> { let py = self.py(); inner( self, - key.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -203,10 +196,7 @@ impl<'py> PySetMethods<'py> for Bound<'py, PySet> { let py = self.py(); inner( self, - key.into_pyobject(py) - .map_err(Into::into)? - .into_any() - .as_borrowed(), + key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(), ) } @@ -316,7 +306,7 @@ where let ptr = set.as_ptr(); elements.into_iter().try_for_each(|element| { - let obj = element.into_pyobject(py).map_err(Into::into)?; + let obj = element.into_pyobject_or_pyerr(py)?; err::error_on_minusone(py, unsafe { ffi::PySet_Add(ptr, obj.as_ptr()) }) })?; diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 3a1f92815c2..216a376d833 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -1,6 +1,5 @@ use std::iter::FusedIterator; -use crate::conversion::IntoPyObject; use crate::ffi::{self, Py_ssize_t}; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] @@ -9,7 +8,8 @@ use crate::instance::Borrowed; use crate::internal_tricks::get_ssize_index; use crate::types::{any::PyAnyMethods, sequence::PySequenceMethods, PyList, PySequence}; use crate::{ - exceptions, Bound, BoundObject, FromPyObject, Py, PyAny, PyErr, PyObject, PyResult, Python, + exceptions, Bound, FromPyObject, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyErr, PyObject, + PyResult, Python, }; #[allow(deprecated)] use crate::{IntoPy, ToPyObject}; @@ -99,12 +99,7 @@ impl PyTuple { T: IntoPyObject<'py>, U: ExactSizeIterator, { - let elements = elements.into_iter().map(|e| { - e.into_pyobject(py) - .map(BoundObject::into_any) - .map(BoundObject::into_bound) - .map_err(Into::into) - }); + let elements = elements.into_iter().map(|e| e.into_bound_py_any(py)); try_new_from_iter(py, elements) } @@ -523,14 +518,14 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ #[allow(deprecated)] impl <$($T: ToPyObject),+> ToPyObject for ($($T,)+) { fn to_object(&self, py: Python<'_>) -> PyObject { - array_into_tuple(py, [$(self.$n.to_object(py)),+]).into() + array_into_tuple(py, [$(self.$n.to_object(py).into_bound(py)),+]).into() } } #[allow(deprecated)] impl <$($T: IntoPy),+> IntoPy for ($($T,)+) { fn into_py(self, py: Python<'_>) -> PyObject { - array_into_tuple(py, [$(self.$n.into_py(py)),+]).into() + array_into_tuple(py, [$(self.$n.into_py(py).into_bound(py)),+]).into() } } @@ -543,7 +538,7 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ type Error = PyErr; fn into_pyobject(self, py: Python<'py>) -> Result { - Ok(array_into_tuple(py, [$(self.$n.into_pyobject(py).map_err(Into::into)?.into_any().unbind()),+]).into_bound(py)) + Ok(array_into_tuple(py, [$(self.$n.into_bound_py_any(py)?),+])) } #[cfg(feature = "experimental-inspect")] @@ -562,7 +557,7 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ type Error = PyErr; fn into_pyobject(self, py: Python<'py>) -> Result { - Ok(array_into_tuple(py, [$(self.$n.into_pyobject(py).map_err(Into::into)?.into_any().unbind()),+]).into_bound(py)) + Ok(array_into_tuple(py, [$(self.$n.into_bound_py_any(py)?),+])) } #[cfg(feature = "experimental-inspect")] @@ -574,7 +569,7 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ #[allow(deprecated)] impl <$($T: IntoPy),+> IntoPy> for ($($T,)+) { fn into_py(self, py: Python<'_>) -> Py { - array_into_tuple(py, [$(self.$n.into_py(py)),+]) + array_into_tuple(py, [$(self.$n.into_py(py).into_bound(py)),+]).unbind() } } @@ -600,10 +595,13 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ } }); -fn array_into_tuple(py: Python<'_>, array: [PyObject; N]) -> Py { +fn array_into_tuple<'py, const N: usize>( + py: Python<'py>, + array: [Bound<'py, PyAny>; N], +) -> Bound<'py, PyTuple> { unsafe { let ptr = ffi::PyTuple_New(N.try_into().expect("0 < N <= 12")); - let tup = Py::from_owned_ptr(py, ptr); + let tup = ptr.assume_owned(py).downcast_into_unchecked(); for (index, obj) in array.into_iter().enumerate() { #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] ffi::PyTuple_SET_ITEM(ptr, index as ffi::Py_ssize_t, obj.into_ptr()); diff --git a/src/types/weakref/proxy.rs b/src/types/weakref/proxy.rs index 6b20a29b8c2..5334f0341f1 100644 --- a/src/types/weakref/proxy.rs +++ b/src/types/weakref/proxy.rs @@ -3,7 +3,7 @@ use crate::ffi_ptr_ext::FfiPtrExt; use crate::py_result_ext::PyResultExt; use crate::type_object::PyTypeCheck; use crate::types::any::PyAny; -use crate::{ffi, Bound, BoundObject, IntoPyObject}; +use crate::{ffi, Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt}; use super::PyWeakrefMethods; @@ -152,7 +152,7 @@ impl PyWeakrefProxy { { fn inner<'py>( object: &Bound<'py, PyAny>, - callback: Bound<'py, PyAny>, + callback: Borrowed<'_, 'py, PyAny>, ) -> PyResult> { unsafe { Bound::from_owned_ptr_or_err( @@ -167,10 +167,9 @@ impl PyWeakrefProxy { inner( object, callback - .into_pyobject(py) - .map(BoundObject::into_any) - .map(BoundObject::into_bound) - .map_err(Into::into)?, + .into_pyobject_or_pyerr(py)? + .into_any() + .as_borrowed(), ) } diff --git a/src/types/weakref/reference.rs b/src/types/weakref/reference.rs index dc7ea4a272a..edabb6da935 100644 --- a/src/types/weakref/reference.rs +++ b/src/types/weakref/reference.rs @@ -2,7 +2,7 @@ use crate::err::PyResult; use crate::ffi_ptr_ext::FfiPtrExt; use crate::py_result_ext::PyResultExt; use crate::types::any::PyAny; -use crate::{ffi, Bound, BoundObject, IntoPyObject}; +use crate::{ffi, Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt}; #[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] use crate::type_object::PyTypeCheck; @@ -161,7 +161,7 @@ impl PyWeakrefReference { { fn inner<'py>( object: &Bound<'py, PyAny>, - callback: Bound<'py, PyAny>, + callback: Borrowed<'_, 'py, PyAny>, ) -> PyResult> { unsafe { Bound::from_owned_ptr_or_err( @@ -176,10 +176,9 @@ impl PyWeakrefReference { inner( object, callback - .into_pyobject(py) - .map(BoundObject::into_any) - .map(BoundObject::into_bound) - .map_err(Into::into)?, + .into_pyobject_or_pyerr(py)? + .into_any() + .as_borrowed(), ) } From 46ba6c72e58c14cf80e96dfedac470c8fec994da Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 20 Nov 2024 08:28:25 +0000 Subject: [PATCH 3/6] docs: remove outdated `#[pyclass(text_signature = "...")]` option (#4721) --- guide/pyclass-parameters.md | 1 - 1 file changed, 1 deletion(-) diff --git a/guide/pyclass-parameters.md b/guide/pyclass-parameters.md index 863f447080e..7ebca2ec821 100644 --- a/guide/pyclass-parameters.md +++ b/guide/pyclass-parameters.md @@ -21,7 +21,6 @@ | `set_all` | Generates setters for all fields of the pyclass. | | `str` | Implements `__str__` using the `Display` implementation of the underlying Rust datatype or by passing an optional format string `str=""`. *Note: The optional format string is only allowed for structs. `name` and `rename_all` are incompatible with the optional format string. Additional details can be found in the discussion on this [PR](https://github.com/PyO3/pyo3/pull/4233).* | | `subclass` | Allows other Python classes and `#[pyclass]` to inherit from this class. Enums cannot be subclassed. | -| `text_signature = "(arg1, arg2, ...)"` | Sets the text signature for the Python class' `__new__` method. | | `unsendable` | Required if your struct is not [`Send`][params-3]. Rather than using `unsendable`, consider implementing your struct in a thread-safe way by e.g. substituting [`Rc`][params-4] with [`Arc`][params-5]. By using `unsendable`, your class will panic when accessed by another thread. Also note the Python's GC is multi-threaded and while unsendable classes will not be traversed on foreign threads to avoid UB, this can lead to memory leaks. | | `weakref` | Allows this class to be [weakly referenceable][params-6]. | From a2a57f0d485190d1e6850dc6399b2246bbb63a59 Mon Sep 17 00:00:00 2001 From: Lily Foote Date: Sat, 23 Nov 2024 21:54:57 +0000 Subject: [PATCH 4/6] Fix ambiguous associated item error (#4725) * Add test for ambiguous associated item The `#[pyclass]` macro implements `IntoPyObject` for the annotated enum. When the enum has any of `Error`, `Output` or `Target` as members, this clashes with the associated types of `IntoPyObject`. This also happens when deriving `IntoPyObject` directly. * Fix #4723: ambiguous associated item in #[pyclass] This uses the fix described in https://github.com/rust-lang/rust/issues/57644 Also apply fix to `#[derive(IntoPyObject)]`. --- newsfragments/4725.fixed.md | 1 + pyo3-macros-backend/src/intopyobject.rs | 7 +++++-- pyo3-macros-backend/src/pyclass.rs | 14 ++++++++++---- tests/test_compile_error.rs | 1 + tests/ui/ambiguous_associated_items.rs | 25 +++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 newsfragments/4725.fixed.md create mode 100644 tests/ui/ambiguous_associated_items.rs diff --git a/newsfragments/4725.fixed.md b/newsfragments/4725.fixed.md new file mode 100644 index 00000000000..9ee069c5eb4 --- /dev/null +++ b/newsfragments/4725.fixed.md @@ -0,0 +1 @@ +Fix `ambiguous_associated_items` lint error in `#[pyclass]` and `#[derive(IntoPyObject)]` macros. diff --git a/pyo3-macros-backend/src/intopyobject.rs b/pyo3-macros-backend/src/intopyobject.rs index 4a46c07418f..a60a5486cb8 100644 --- a/pyo3-macros-backend/src/intopyobject.rs +++ b/pyo3-macros-backend/src/intopyobject.rs @@ -512,7 +512,7 @@ impl<'a> Enum<'a> { IntoPyObjectImpl { types: IntoPyObjectTypes::Opaque { target: quote!(#pyo3_path::types::PyAny), - output: quote!(#pyo3_path::Bound<'py, Self::Target>), + output: quote!(#pyo3_path::Bound<'py, >::Target>), error: quote!(#pyo3_path::PyErr), }, body: quote! { @@ -617,7 +617,10 @@ pub fn build_derive_into_pyobject(tokens: &DeriveInput) -> Resu type Output = #output; type Error = #error; - fn into_pyobject(self, py: #pyo3_path::Python<#lt_param>) -> ::std::result::Result { + fn into_pyobject(self, py: #pyo3_path::Python<#lt_param>) -> ::std::result::Result< + ::Output, + ::Error, + > { #body } } diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 87d02c6f878..93596611f18 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1072,10 +1072,13 @@ fn impl_complex_enum( quote! { impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls { type Target = Self; - type Output = #pyo3_path::Bound<'py, Self::Target>; + type Output = #pyo3_path::Bound<'py, >::Target>; type Error = #pyo3_path::PyErr; - fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result { + fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result< + ::Output, + ::Error, + > { match self { #(#match_arms)* } @@ -2161,10 +2164,13 @@ impl<'a> PyClassImplsBuilder<'a> { impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls { type Target = Self; - type Output = #pyo3_path::Bound<'py, Self::Target>; + type Output = #pyo3_path::Bound<'py, >::Target>; type Error = #pyo3_path::PyErr; - fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result { + fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result< + ::Output, + ::Error, + > { #pyo3_path::Bound::new(py, self) } } diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index b6cf5065371..e4e80e90263 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -67,4 +67,5 @@ fn test_compile_errors() { t.compile_fail("tests/ui/duplicate_pymodule_submodule.rs"); #[cfg(all(not(Py_LIMITED_API), Py_3_11))] t.compile_fail("tests/ui/invalid_base_class.rs"); + t.pass("tests/ui/ambiguous_associated_items.rs"); } diff --git a/tests/ui/ambiguous_associated_items.rs b/tests/ui/ambiguous_associated_items.rs new file mode 100644 index 00000000000..f553ba1f33f --- /dev/null +++ b/tests/ui/ambiguous_associated_items.rs @@ -0,0 +1,25 @@ +use pyo3::prelude::*; + +#[pyclass(eq)] +#[derive(PartialEq)] +pub enum SimpleItems { + Error, + Output, + Target, +} + +#[pyclass] +pub enum ComplexItems { + Error(PyObject), + Output(PyObject), + Target(PyObject), +} + +#[derive(IntoPyObject)] +enum DeriveItems { + Error(PyObject), + Output(PyObject), + Target(PyObject), +} + +fn main() {} From f253054a9ee8dbed8aabefea2d05f8543468f4fc Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Sat, 23 Nov 2024 16:30:07 -0700 Subject: [PATCH 5/6] Free-threaded build config fixes (#4719) * update build config logic and library name generation * fix free-threaded windows clippy with --features=abi3 * use constant * add release note * apply my self-review comments * use ensure and error handling instead of panicking * skip abi3 fixup on free-threaded build * don't support PYO3_USE_ABI3_FORWARD_COMPATIBILITY on free-threaded build * don't panic in pyo3-ffi in abi3 check * document lack of limited API support * add is_free_threaded() method to InterpreterConfig * implement David's code review suggestions * remove unused imports --- guide/src/free-threading.md | 28 +++- newsfragments/4719.fixed.md | 2 + pyo3-build-config/src/impl_.rs | 259 +++++++++++++++++++++++++-------- pyo3-ffi/build.rs | 35 +++-- 4 files changed, 245 insertions(+), 79 deletions(-) create mode 100644 newsfragments/4719.fixed.md diff --git a/guide/src/free-threading.md b/guide/src/free-threading.md index d867a707795..f212cb0b9a9 100644 --- a/guide/src/free-threading.md +++ b/guide/src/free-threading.md @@ -108,6 +108,15 @@ using single-phase initialization and the [`sequential`](https://github.com/PyO3/pyo3/tree/main/pyo3-ffi/examples/sequential) example for modules using multi-phase initialization. +If you would like to use conditional compilation to trigger different code paths +under the free-threaded build, you can use the `Py_GIL_DISABLED` attribute once +you have configured your crate to generate the necessary build configuration +data. See [the guide +section](./building-and-distribution/multiple-python-versions.md) for more +details about supporting multiple different Python versions, including the +free-threaded build. + + ## Special considerations for the free-threaded build The free-threaded interpreter does not have a GIL, and this can make interacting @@ -234,7 +243,24 @@ needed. For now you should explicitly add locking, possibly using conditional compilation or using the critical section API to avoid creating deadlocks with the GIL. -## Thread-safe single initialization +### Cannot build extensions using the limited API + +The free-threaded build uses a completely new ABI and there is not yet an +equivalent to the limited API for the free-threaded ABI. That means if your +crate depends on PyO3 using the `abi3` feature or an an `abi3-pyxx` feature, +PyO3 will print a warning and ignore that setting when building extensions using +the free-threaded interpreter. + +This means that if your package makes use of the ABI forward compatibility +provided by the limited API to uploads only one wheel for each release of your +package, you will need to update and tooling or instructions to also upload a +version-specific free-threaded wheel. + +See [the guide section](./building-and-distribution/multiple-python-versions.md) +for more details about supporting multiple different Python versions, including +the free-threaded build. + +### Thread-safe single initialization Until version 0.23, PyO3 provided only [`GILOnceCell`] to enable deadlock-free single initialization of data in contexts that might execute arbitrary Python diff --git a/newsfragments/4719.fixed.md b/newsfragments/4719.fixed.md new file mode 100644 index 00000000000..08cb1c8268e --- /dev/null +++ b/newsfragments/4719.fixed.md @@ -0,0 +1,2 @@ +* Fixed an issue that prevented building free-threaded extensions for crates + that request a specific minimum limited API version. diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index 6d2326429d2..4e5d3c10656 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -176,7 +176,7 @@ impl InterpreterConfig { } // If Py_GIL_DISABLED is set, do not build with limited API support - if self.abi3 && !self.build_flags.0.contains(&BuildFlag::Py_GIL_DISABLED) { + if self.abi3 && !self.is_free_threaded() { out.push("cargo:rustc-cfg=Py_LIMITED_API".to_owned()); } @@ -309,14 +309,14 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED")) // `_d.cp312-win_amd64.pyd` for 3.12 debug build map["ext_suffix"].starts_with("_d."), gil_disabled, - ) + )? } else { default_lib_name_unix( version, implementation, map.get("ld_version").map(String::as_str), gil_disabled, - ) + )? }; let lib_dir = if cfg!(windows) { @@ -394,7 +394,7 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED")) implementation, sysconfigdata.get_value("LDVERSION"), gil_disabled, - )); + )?); let pointer_width = parse_key!(sysconfigdata, "SIZEOF_VOID_P") .map(|bytes_width: u32| bytes_width * 8) .ok(); @@ -660,10 +660,18 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED")) ) } - /// Lowers the configured version to the abi3 version, if set. + pub fn is_free_threaded(&self) -> bool { + self.build_flags.0.contains(&BuildFlag::Py_GIL_DISABLED) + } + + /// Updates configured ABI to build for to the requested abi3 version + /// This is a no-op for platforms where abi3 is not supported fn fixup_for_abi3_version(&mut self, abi3_version: Option) -> Result<()> { - // PyPy doesn't support abi3; don't adjust the version - if self.implementation.is_pypy() || self.implementation.is_graalpy() { + // PyPy, GraalPy, and the free-threaded build don't support abi3; don't adjust the version + if self.implementation.is_pypy() + || self.implementation.is_graalpy() + || self.is_free_threaded() + { return Ok(()); } @@ -691,6 +699,14 @@ pub struct PythonVersion { } impl PythonVersion { + pub const PY313: Self = PythonVersion { + major: 3, + minor: 13, + }; + const PY310: Self = PythonVersion { + major: 3, + minor: 10, + }; const PY37: Self = PythonVersion { major: 3, minor: 7 }; } @@ -1536,7 +1552,7 @@ fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result InterpreterConfig { +fn default_abi3_config(host: &Triple, version: PythonVersion) -> Result { // FIXME: PyPy & GraalPy do not support the Stable ABI. let implementation = PythonImplementation::CPython; let abi3 = true; @@ -1549,12 +1565,12 @@ fn default_abi3_config(host: &Triple, version: PythonVersion) -> InterpreterConf false, false, false, - )) + )?) } else { None }; - InterpreterConfig { + Ok(InterpreterConfig { implementation, version, shared: true, @@ -1566,7 +1582,7 @@ fn default_abi3_config(host: &Triple, version: PythonVersion) -> InterpreterConf build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], - } + }) } /// Detects the cross compilation target interpreter configuration from all @@ -1606,11 +1622,9 @@ fn load_cross_compile_config( Ok(config) } -// Link against python3.lib for the stable ABI on Windows. -// See https://www.python.org/dev/peps/pep-0384/#linkage -// -// This contains only the limited ABI symbols. +// These contains only the limited ABI symbols. const WINDOWS_ABI3_LIB_NAME: &str = "python3"; +const WINDOWS_ABI3_DEBUG_LIB_NAME: &str = "python3_d"; fn default_lib_name_for_target( version: PythonVersion, @@ -1619,16 +1633,9 @@ fn default_lib_name_for_target( target: &Triple, ) -> Option { if target.operating_system == OperatingSystem::Windows { - Some(default_lib_name_windows( - version, - implementation, - abi3, - false, - false, - false, - )) + Some(default_lib_name_windows(version, implementation, abi3, false, false, false).unwrap()) } else if is_linking_libpython_for_target(target) { - Some(default_lib_name_unix(version, implementation, None, false)) + Some(default_lib_name_unix(version, implementation, None, false).unwrap()) } else { None } @@ -1641,27 +1648,35 @@ fn default_lib_name_windows( mingw: bool, debug: bool, gil_disabled: bool, -) -> String { - if debug { +) -> Result { + if debug && version < PythonVersion::PY310 { // CPython bug: linking against python3_d.dll raises error // https://github.com/python/cpython/issues/101614 - if gil_disabled { - format!("python{}{}t_d", version.major, version.minor) + Ok(format!("python{}{}_d", version.major, version.minor)) + } else if abi3 && !(implementation.is_pypy() || implementation.is_graalpy()) { + if debug { + Ok(WINDOWS_ABI3_DEBUG_LIB_NAME.to_owned()) } else { - format!("python{}{}_d", version.major, version.minor) + Ok(WINDOWS_ABI3_LIB_NAME.to_owned()) } - } else if abi3 && !(implementation.is_pypy() || implementation.is_graalpy()) { - WINDOWS_ABI3_LIB_NAME.to_owned() } else if mingw { - if gil_disabled { - panic!("MinGW free-threaded builds are not currently tested or supported") - } + ensure!( + !gil_disabled, + "MinGW free-threaded builds are not currently tested or supported" + ); // https://packages.msys2.org/base/mingw-w64-python - format!("python{}.{}", version.major, version.minor) + Ok(format!("python{}.{}", version.major, version.minor)) } else if gil_disabled { - format!("python{}{}t", version.major, version.minor) + ensure!(version >= PythonVersion::PY313, "Cannot compile C extensions for the free-threaded build on Python versions earlier than 3.13, found {}.{}", version.major, version.minor); + if debug { + Ok(format!("python{}{}t_d", version.major, version.minor)) + } else { + Ok(format!("python{}{}t", version.major, version.minor)) + } + } else if debug { + Ok(format!("python{}{}_d", version.major, version.minor)) } else { - format!("python{}{}", version.major, version.minor) + Ok(format!("python{}{}", version.major, version.minor)) } } @@ -1670,30 +1685,31 @@ fn default_lib_name_unix( implementation: PythonImplementation, ld_version: Option<&str>, gil_disabled: bool, -) -> String { +) -> Result { match implementation { PythonImplementation::CPython => match ld_version { - Some(ld_version) => format!("python{}", ld_version), + Some(ld_version) => Ok(format!("python{}", ld_version)), None => { if version > PythonVersion::PY37 { // PEP 3149 ABI version tags are finally gone if gil_disabled { - format!("python{}.{}t", version.major, version.minor) + ensure!(version >= PythonVersion::PY313, "Cannot compile C extensions for the free-threaded build on Python versions earlier than 3.13, found {}.{}", version.major, version.minor); + Ok(format!("python{}.{}t", version.major, version.minor)) } else { - format!("python{}.{}", version.major, version.minor) + Ok(format!("python{}.{}", version.major, version.minor)) } } else { // Work around https://bugs.python.org/issue36707 - format!("python{}.{}m", version.major, version.minor) + Ok(format!("python{}.{}m", version.major, version.minor)) } } }, PythonImplementation::PyPy => match ld_version { - Some(ld_version) => format!("pypy{}-c", ld_version), - None => format!("pypy{}.{}-c", version.major, version.minor), + Some(ld_version) => Ok(format!("pypy{}-c", ld_version)), + None => Ok(format!("pypy{}.{}-c", version.major, version.minor)), }, - PythonImplementation::GraalPy => "python-native".to_string(), + PythonImplementation::GraalPy => Ok("python-native".to_string()), } } @@ -1863,7 +1879,7 @@ pub fn make_interpreter_config() -> Result { ); }; - let mut interpreter_config = default_abi3_config(&host, abi3_version.unwrap()); + let mut interpreter_config = default_abi3_config(&host, abi3_version.unwrap())?; // Auto generate python3.dll import libraries for Windows targets. #[cfg(feature = "python3-dll-a")] @@ -2200,7 +2216,7 @@ mod tests { let min_version = "3.7".parse().unwrap(); assert_eq!( - default_abi3_config(&host, min_version), + default_abi3_config(&host, min_version).unwrap(), InterpreterConfig { implementation: PythonImplementation::CPython, version: PythonVersion { major: 3, minor: 7 }, @@ -2223,7 +2239,7 @@ mod tests { let min_version = "3.9".parse().unwrap(); assert_eq!( - default_abi3_config(&host, min_version), + default_abi3_config(&host, min_version).unwrap(), InterpreterConfig { implementation: PythonImplementation::CPython, version: PythonVersion { major: 3, minor: 9 }, @@ -2389,9 +2405,19 @@ mod tests { false, false, false, - ), + ) + .unwrap(), "python39", ); + assert!(super::default_lib_name_windows( + PythonVersion { major: 3, minor: 9 }, + CPython, + false, + false, + false, + true, + ) + .is_err()); assert_eq!( super::default_lib_name_windows( PythonVersion { major: 3, minor: 9 }, @@ -2400,7 +2426,8 @@ mod tests { false, false, false, - ), + ) + .unwrap(), "python3", ); assert_eq!( @@ -2411,7 +2438,8 @@ mod tests { true, false, false, - ), + ) + .unwrap(), "python3.9", ); assert_eq!( @@ -2422,7 +2450,8 @@ mod tests { true, false, false, - ), + ) + .unwrap(), "python3", ); assert_eq!( @@ -2433,7 +2462,8 @@ mod tests { false, false, false, - ), + ) + .unwrap(), "python39", ); assert_eq!( @@ -2444,10 +2474,11 @@ mod tests { false, true, false, - ), + ) + .unwrap(), "python39_d", ); - // abi3 debug builds on windows use version-specific lib + // abi3 debug builds on windows use version-specific lib on 3.9 and older // to workaround https://github.com/python/cpython/issues/101614 assert_eq!( super::default_lib_name_windows( @@ -2457,9 +2488,81 @@ mod tests { false, true, false, - ), + ) + .unwrap(), "python39_d", ); + assert_eq!( + super::default_lib_name_windows( + PythonVersion { + major: 3, + minor: 10 + }, + CPython, + true, + false, + true, + false, + ) + .unwrap(), + "python3_d", + ); + // Python versions older than 3.13 don't support gil_disabled + assert!(super::default_lib_name_windows( + PythonVersion { + major: 3, + minor: 12, + }, + CPython, + false, + false, + false, + true, + ) + .is_err()); + // mingw and free-threading are incompatible (until someone adds support) + assert!(super::default_lib_name_windows( + PythonVersion { + major: 3, + minor: 12, + }, + CPython, + false, + true, + false, + true, + ) + .is_err()); + assert_eq!( + super::default_lib_name_windows( + PythonVersion { + major: 3, + minor: 13 + }, + CPython, + false, + false, + false, + true, + ) + .unwrap(), + "python313t", + ); + assert_eq!( + super::default_lib_name_windows( + PythonVersion { + major: 3, + minor: 13 + }, + CPython, + false, + false, + true, + true, + ) + .unwrap(), + "python313t_d", + ); } #[test] @@ -2472,7 +2575,8 @@ mod tests { CPython, None, false - ), + ) + .unwrap(), "python3.7m", ); // Defaults to pythonX.Y for CPython 3.8+ @@ -2482,7 +2586,8 @@ mod tests { CPython, None, false - ), + ) + .unwrap(), "python3.8", ); assert_eq!( @@ -2491,7 +2596,8 @@ mod tests { CPython, None, false - ), + ) + .unwrap(), "python3.9", ); // Can use ldversion to override for CPython @@ -2501,13 +2607,15 @@ mod tests { CPython, Some("3.7md"), false - ), + ) + .unwrap(), "python3.7md", ); // PyPy 3.9 includes ldversion assert_eq!( - super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, PyPy, None, false), + super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, PyPy, None, false) + .unwrap(), "pypy3.9-c", ); @@ -2517,9 +2625,36 @@ mod tests { PyPy, Some("3.9d"), false - ), + ) + .unwrap(), "pypy3.9d-c", ); + + // free-threading adds a t suffix + assert_eq!( + super::default_lib_name_unix( + PythonVersion { + major: 3, + minor: 13 + }, + CPython, + None, + true + ) + .unwrap(), + "python3.13t", + ); + // 3.12 and older are incompatible with gil_disabled + assert!(super::default_lib_name_unix( + PythonVersion { + major: 3, + minor: 12, + }, + CPython, + None, + true, + ) + .is_err()); } #[test] diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs index 931838b5e5d..ea023de75fa 100644 --- a/pyo3-ffi/build.rs +++ b/pyo3-ffi/build.rs @@ -4,7 +4,7 @@ use pyo3_build_config::{ cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config, InterpreterConfig, PythonVersion, }, - warn, BuildFlag, PythonImplementation, + warn, PythonImplementation, }; /// Minimum Python version PyO3 supports. @@ -56,15 +56,22 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { interpreter_config.version, versions.min, ); - ensure!( - interpreter_config.version <= versions.max || env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").map_or(false, |os_str| os_str == "1"), - "the configured Python interpreter version ({}) is newer than PyO3's maximum supported version ({})\n\ - = help: please check if an updated version of PyO3 is available. Current version: {}\n\ - = help: set PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 to suppress this check and build anyway using the stable ABI", - interpreter_config.version, - versions.max, - std::env::var("CARGO_PKG_VERSION").unwrap(), - ); + if interpreter_config.version > versions.max { + ensure!(!interpreter_config.is_free_threaded(), + "The configured Python interpreter version ({}) is newer than PyO3's maximum supported version ({})\n\ + = help: please check if an updated version of PyO3 is available. Current version: {}\n\ + = help: The free-threaded build of CPython does not support the limited API so this check cannot be suppressed.", + interpreter_config.version, versions.max, std::env::var("CARGO_PKG_VERSION").unwrap() + ); + ensure!(env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").map_or(false, |os_str| os_str == "1"), + "the configured Python interpreter version ({}) is newer than PyO3's maximum supported version ({})\n\ + = help: please check if an updated version of PyO3 is available. Current version: {}\n\ + = help: set PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 to suppress this check and build anyway using the stable ABI", + interpreter_config.version, + versions.max, + std::env::var("CARGO_PKG_VERSION").unwrap(), + ); + } } PythonImplementation::PyPy => { let versions = SUPPORTED_VERSIONS_PYPY; @@ -107,14 +114,10 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { if interpreter_config.abi3 { match interpreter_config.implementation { PythonImplementation::CPython => { - if interpreter_config - .build_flags - .0 - .contains(&BuildFlag::Py_GIL_DISABLED) - { + if interpreter_config.is_free_threaded() { warn!( "The free-threaded build of CPython does not yet support abi3 so the build artifacts will be version-specific." - ) + ) } } PythonImplementation::PyPy => warn!( From ad989b25e77f5006aff4aff773a7d368e669f2b7 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Mon, 25 Nov 2024 19:01:33 +0000 Subject: [PATCH 6/6] release: 0.23.2 --- CHANGELOG.md | 15 ++++++++++++++- Cargo.toml | 8 ++++---- README.md | 4 ++-- examples/decorator/.template/pre-script.rhai | 2 +- .../maturin-starter/.template/pre-script.rhai | 2 +- examples/plugin/.template/pre-script.rhai | 2 +- .../.template/pre-script.rhai | 2 +- examples/word-count/.template/pre-script.rhai | 2 +- newsfragments/4708.added.md | 1 - newsfragments/4719.fixed.md | 2 -- newsfragments/4725.fixed.md | 1 - pyo3-build-config/Cargo.toml | 2 +- pyo3-ffi/Cargo.toml | 4 ++-- pyo3-ffi/README.md | 4 ++-- pyo3-macros-backend/Cargo.toml | 6 +++--- pyo3-macros/Cargo.toml | 4 ++-- pyproject.toml | 2 +- tests/ui/reject_generics.stderr | 4 ++-- 18 files changed, 38 insertions(+), 29 deletions(-) delete mode 100644 newsfragments/4708.added.md delete mode 100644 newsfragments/4719.fixed.md delete mode 100644 newsfragments/4725.fixed.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bfce433293..1ab62499596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,18 @@ To see unreleased changes, please see the [CHANGELOG on the main branch guide](h +## [0.23.2] - 2024-11-25 + +### Added + +- Add `IntoPyObjectExt` trait. [#4708](https://github.com/PyO3/pyo3/pull/4708) + +### Fixed + +- Fix compile failures when building for free-threaded Python when the `abi3` or `abi3-pyxx` features are enabled. [#4719](https://github.com/PyO3/pyo3/pull/4719) +- Fix `ambiguous_associated_items` lint error in `#[pyclass]` and `#[derive(IntoPyObject)]` macros. [#4725](https://github.com/PyO3/pyo3/pull/4725) + + ## [0.23.1] - 2024-11-16 Re-release of 0.23.0 with fixes to docs.rs build. @@ -2000,7 +2012,8 @@ Yanked - Initial release -[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.23.1...HEAD +[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.23.2...HEAD +[0.23.2]: https://github.com/pyo3/pyo3/compare/v0.23.1...v0.23.2 [0.23.1]: https://github.com/pyo3/pyo3/compare/v0.23.0...v0.23.1 [0.23.0]: https://github.com/pyo3/pyo3/compare/v0.22.5...v0.23.0 [0.22.5]: https://github.com/pyo3/pyo3/compare/v0.22.4...v0.22.5 diff --git a/Cargo.toml b/Cargo.toml index 3eca038e054..778a8f7df2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3" -version = "0.23.1" +version = "0.23.2" description = "Bindings to Python interpreter" authors = ["PyO3 Project and Contributors "] readme = "README.md" @@ -21,10 +21,10 @@ memoffset = "0.9" once_cell = "1.13" # ffi bindings to the python interpreter, split into a separate crate so they can be used independently -pyo3-ffi = { path = "pyo3-ffi", version = "=0.23.1" } +pyo3-ffi = { path = "pyo3-ffi", version = "=0.23.2" } # support crates for macros feature -pyo3-macros = { path = "pyo3-macros", version = "=0.23.1", optional = true } +pyo3-macros = { path = "pyo3-macros", version = "=0.23.2", optional = true } indoc = { version = "2.0.1", optional = true } unindent = { version = "0.2.1", optional = true } @@ -66,7 +66,7 @@ static_assertions = "1.1.0" uuid = {version = "1.10.0", features = ["v4"] } [build-dependencies] -pyo3-build-config = { path = "pyo3-build-config", version = "=0.23.1", features = ["resolve-config"] } +pyo3-build-config = { path = "pyo3-build-config", version = "=0.23.2", features = ["resolve-config"] } [features] default = ["macros"] diff --git a/README.md b/README.md index c0a46af44d6..58c6ce17c9d 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ name = "string_sum" crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.23.1", features = ["extension-module"] } +pyo3 = { version = "0.23.2", features = ["extension-module"] } ``` **`src/lib.rs`** @@ -140,7 +140,7 @@ Start a new project with `cargo new` and add `pyo3` to the `Cargo.toml` like th ```toml [dependencies.pyo3] -version = "0.23.1" +version = "0.23.2" features = ["auto-initialize"] ``` diff --git a/examples/decorator/.template/pre-script.rhai b/examples/decorator/.template/pre-script.rhai index db0f2e8b002..56353bbc3e2 100644 --- a/examples/decorator/.template/pre-script.rhai +++ b/examples/decorator/.template/pre-script.rhai @@ -1,4 +1,4 @@ -variable::set("PYO3_VERSION", "0.23.1"); +variable::set("PYO3_VERSION", "0.23.2"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/pyproject.toml", "pyproject.toml"); file::delete(".template"); diff --git a/examples/maturin-starter/.template/pre-script.rhai b/examples/maturin-starter/.template/pre-script.rhai index db0f2e8b002..56353bbc3e2 100644 --- a/examples/maturin-starter/.template/pre-script.rhai +++ b/examples/maturin-starter/.template/pre-script.rhai @@ -1,4 +1,4 @@ -variable::set("PYO3_VERSION", "0.23.1"); +variable::set("PYO3_VERSION", "0.23.2"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/pyproject.toml", "pyproject.toml"); file::delete(".template"); diff --git a/examples/plugin/.template/pre-script.rhai b/examples/plugin/.template/pre-script.rhai index 90eb43e817c..d8bba8b5fa7 100644 --- a/examples/plugin/.template/pre-script.rhai +++ b/examples/plugin/.template/pre-script.rhai @@ -1,4 +1,4 @@ -variable::set("PYO3_VERSION", "0.23.1"); +variable::set("PYO3_VERSION", "0.23.2"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/plugin_api/Cargo.toml", "plugin_api/Cargo.toml"); file::delete(".template"); diff --git a/examples/setuptools-rust-starter/.template/pre-script.rhai b/examples/setuptools-rust-starter/.template/pre-script.rhai index eeb279d1497..4d41dacca0c 100644 --- a/examples/setuptools-rust-starter/.template/pre-script.rhai +++ b/examples/setuptools-rust-starter/.template/pre-script.rhai @@ -1,4 +1,4 @@ -variable::set("PYO3_VERSION", "0.23.1"); +variable::set("PYO3_VERSION", "0.23.2"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/setup.cfg", "setup.cfg"); file::delete(".template"); diff --git a/examples/word-count/.template/pre-script.rhai b/examples/word-count/.template/pre-script.rhai index db0f2e8b002..56353bbc3e2 100644 --- a/examples/word-count/.template/pre-script.rhai +++ b/examples/word-count/.template/pre-script.rhai @@ -1,4 +1,4 @@ -variable::set("PYO3_VERSION", "0.23.1"); +variable::set("PYO3_VERSION", "0.23.2"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/pyproject.toml", "pyproject.toml"); file::delete(".template"); diff --git a/newsfragments/4708.added.md b/newsfragments/4708.added.md deleted file mode 100644 index c8c91d16221..00000000000 --- a/newsfragments/4708.added.md +++ /dev/null @@ -1 +0,0 @@ -Add `IntoPyObjectExt` trait. diff --git a/newsfragments/4719.fixed.md b/newsfragments/4719.fixed.md deleted file mode 100644 index 08cb1c8268e..00000000000 --- a/newsfragments/4719.fixed.md +++ /dev/null @@ -1,2 +0,0 @@ -* Fixed an issue that prevented building free-threaded extensions for crates - that request a specific minimum limited API version. diff --git a/newsfragments/4725.fixed.md b/newsfragments/4725.fixed.md deleted file mode 100644 index 9ee069c5eb4..00000000000 --- a/newsfragments/4725.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix `ambiguous_associated_items` lint error in `#[pyclass]` and `#[derive(IntoPyObject)]` macros. diff --git a/pyo3-build-config/Cargo.toml b/pyo3-build-config/Cargo.toml index 29cecb0cf95..cb2c6c6eebd 100644 --- a/pyo3-build-config/Cargo.toml +++ b/pyo3-build-config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-build-config" -version = "0.23.1" +version = "0.23.2" description = "Build configuration for the PyO3 ecosystem" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] diff --git a/pyo3-ffi/Cargo.toml b/pyo3-ffi/Cargo.toml index 3b7a63fe3d9..76fa9e4b8e1 100644 --- a/pyo3-ffi/Cargo.toml +++ b/pyo3-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-ffi" -version = "0.23.1" +version = "0.23.2" description = "Python-API bindings for the PyO3 ecosystem" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] @@ -41,7 +41,7 @@ generate-import-lib = ["pyo3-build-config/python3-dll-a"] paste = "1" [build-dependencies] -pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.1", features = ["resolve-config"] } +pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.2", features = ["resolve-config"] } [lints] workspace = true diff --git a/pyo3-ffi/README.md b/pyo3-ffi/README.md index 097822709bd..d80dad93b3d 100644 --- a/pyo3-ffi/README.md +++ b/pyo3-ffi/README.md @@ -41,13 +41,13 @@ name = "string_sum" crate-type = ["cdylib"] [dependencies.pyo3-ffi] -version = "0.23.1" +version = "0.23.2" features = ["extension-module"] [build-dependencies] # This is only necessary if you need to configure your build based on # the Python version or the compile-time configuration for the interpreter. -pyo3_build_config = "0.23.1" +pyo3_build_config = "0.23.2" ``` If you need to use conditional compilation based on Python version or how diff --git a/pyo3-macros-backend/Cargo.toml b/pyo3-macros-backend/Cargo.toml index 864d0b3712a..d6daa874361 100644 --- a/pyo3-macros-backend/Cargo.toml +++ b/pyo3-macros-backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-macros-backend" -version = "0.23.1" +version = "0.23.2" description = "Code generation for PyO3 package" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] @@ -16,7 +16,7 @@ edition = "2021" [dependencies] heck = "0.5" proc-macro2 = { version = "1.0.60", default-features = false } -pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.1", features = ["resolve-config"] } +pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.2", features = ["resolve-config"] } quote = { version = "1", default-features = false } [dependencies.syn] @@ -25,7 +25,7 @@ default-features = false features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"] [build-dependencies] -pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.1" } +pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.2" } [lints] workspace = true diff --git a/pyo3-macros/Cargo.toml b/pyo3-macros/Cargo.toml index 18b79161f25..2db1c442d97 100644 --- a/pyo3-macros/Cargo.toml +++ b/pyo3-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-macros" -version = "0.23.1" +version = "0.23.2" description = "Proc macros for PyO3 package" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] @@ -21,7 +21,7 @@ experimental-async = ["pyo3-macros-backend/experimental-async"] proc-macro2 = { version = "1.0.60", default-features = false } quote = "1" syn = { version = "2", features = ["full", "extra-traits"] } -pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.23.1" } +pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.23.2" } [lints] workspace = true diff --git a/pyproject.toml b/pyproject.toml index 211a374db59..e3a55bb6d66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ [tool.towncrier] filename = "CHANGELOG.md" -version = "0.23.1" +version = "0.23.2" start_string = "\n" template = ".towncrier.template.md" title_format = "## [{version}] - {project_date}" diff --git a/tests/ui/reject_generics.stderr b/tests/ui/reject_generics.stderr index 5adb8eaca9e..1a811ddafb2 100644 --- a/tests/ui/reject_generics.stderr +++ b/tests/ui/reject_generics.stderr @@ -1,10 +1,10 @@ -error: #[pyclass] cannot have generic parameters. For an explanation, see https://pyo3.rs/v0.23.1/class.html#no-generic-parameters +error: #[pyclass] cannot have generic parameters. For an explanation, see https://pyo3.rs/v0.23.2/class.html#no-generic-parameters --> tests/ui/reject_generics.rs:4:25 | 4 | struct ClassWithGenerics { | ^ -error: #[pyclass] cannot have lifetime parameters. For an explanation, see https://pyo3.rs/v0.23.1/class.html#no-lifetime-parameters +error: #[pyclass] cannot have lifetime parameters. For an explanation, see https://pyo3.rs/v0.23.2/class.html#no-lifetime-parameters --> tests/ui/reject_generics.rs:9:27 | 9 | struct ClassWithLifetimes<'a> {