From 23a481eeab6d49c64c253030e614a4371e042af2 Mon Sep 17 00:00:00 2001 From: Thomas Tanon Date: Sat, 1 Aug 2026 09:19:20 +0200 Subject: [PATCH 01/13] Fix stub generation of field getters (#6276) * Method return type: allow to built it from other traits * Fix stub generation of field getters * Fix UI test --------- Co-authored-by: David Hewitt --- newsfragments/6276.fixed.md | 1 + pyo3-macros-backend/src/introspection.rs | 10 ++--- pyo3-macros-backend/src/py_expr.rs | 25 ++++++++++- pyo3-macros-backend/src/pyclass.rs | 14 +++---- pyo3-macros-backend/src/pyfunction.rs | 12 ++++-- pyo3-macros-backend/src/pyimpl.rs | 8 +++- src/impl_/introspection.rs | 20 +++++++++ tests/test_getter_setter.rs | 41 ++++++++++++++++++- tests/ui/invalid_property_args.inspect.stderr | 16 +++----- tests/ui/invalid_property_args.rs | 2 +- 10 files changed, 115 insertions(+), 34 deletions(-) create mode 100644 newsfragments/6276.fixed.md diff --git a/newsfragments/6276.fixed.md b/newsfragments/6276.fixed.md new file mode 100644 index 00000000000..9243663afed --- /dev/null +++ b/newsfragments/6276.fixed.md @@ -0,0 +1 @@ +Fix stubs generation for field getters (`#[pyo3(get)]`) when `IntoPyObject` is only implemented on references of the field type \ No newline at end of file diff --git a/pyo3-macros-backend/src/introspection.rs b/pyo3-macros-backend/src/introspection.rs index c597f11ec56..05422d92148 100644 --- a/pyo3-macros-backend/src/introspection.rs +++ b/pyo3-macros-backend/src/introspection.rs @@ -21,7 +21,7 @@ use std::fmt::Write; use std::hash::{Hash, Hasher}; use std::mem::take; use std::sync::atomic::{AtomicUsize, Ordering}; -use syn::{Attribute, Ident, ReturnType, Type, TypePath}; +use syn::{Attribute, Ident, Type, TypePath}; static GLOBAL_COUNTER_FOR_UNIQUE_NAMES: AtomicUsize = AtomicUsize::new(0); @@ -103,7 +103,7 @@ pub fn function_introspection_code( name: &str, signature: &FunctionSignature<'_>, first_argument: Option<&'static str>, - returns: ReturnType, + returns: PyExpr, decorators: impl IntoIterator, is_async: bool, is_returning_not_implemented_on_extraction_error: bool, @@ -131,11 +131,7 @@ pub fn function_introspection_code( { returns.as_type_hint().into() } else { - match returns { - ReturnType::Default => PyExpr::builtin("None"), - ReturnType::Type(_, ty) => PyExpr::from_return_type(*ty, parent), - } - .into() + returns.into() }, ), ]); diff --git a/pyo3-macros-backend/src/py_expr.rs b/pyo3-macros-backend/src/py_expr.rs index d18556cc784..893addcc8b2 100644 --- a/pyo3-macros-backend/src/py_expr.rs +++ b/pyo3-macros-backend/src/py_expr.rs @@ -16,6 +16,8 @@ pub enum PyExpr { FromPyObjectType(Type), /// The Python type hint of a IntoPyObject implementation IntoPyObjectType(Type), + /// The Python type hint of a IntoPyObject implementation on the ref type or the base type + IntoPyObjectMaybeRefType(Type), /// The Python type matching the given Rust type given as a function argument ArgumentType(Type), /// The Python type matching the given Rust type given as a function returned value @@ -66,7 +68,7 @@ pub enum PyConstant { } impl PyExpr { - /// Build from a builtins name like `None` + /// Build from a builtins name like `str` pub fn builtin(name: impl Into>) -> Self { Self::Name { id: name.into() } } @@ -93,6 +95,13 @@ impl PyExpr { Self::IntoPyObjectType(clean_type(t, self_type)) } + /// The type hint of a `IntoPyObject` implementation used by a field getter + /// + /// If self_type is set, self_type will replace Self in the given type + pub fn from_into_py_object_maybe_ref(t: Type, self_type: Option<&Type>) -> Self { + Self::IntoPyObjectMaybeRefType(clean_type(t, self_type)) + } + /// The type hint of the Rust type used as a function argument /// /// If self_type is set, self_type will replace Self in the given type @@ -174,6 +183,11 @@ impl PyExpr { Self::Constant(PyConstant::Ellipsis) } + /// `None` + pub fn none() -> Self { + Self::Constant(PyConstant::None) + } + pub fn to_introspection_token_stream(&self, pyo3_crate_path: &PyO3CratePath) -> TokenStream { match self { Self::FromPyObjectType(t) => { @@ -182,6 +196,15 @@ impl PyExpr { Self::IntoPyObjectType(t) => { quote! { <#t as #pyo3_crate_path::IntoPyObject<'_>>::OUTPUT_TYPE } } + Self::IntoPyObjectMaybeRefType(t) => { + quote! {{ + #[allow(unused_imports)] + use #pyo3_crate_path::impl_::pyclass::Probe as _; + <#t as #pyo3_crate_path::impl_::introspection::PyIntoPyObjectMaybeRefType<{ + #pyo3_crate_path::impl_::pyclass::IsIntoPyObjectRef::<#t>::VALUE + }>>::OUTPUT_TYPE + }} + } Self::ArgumentType(t) => { quote! { <#t as #pyo3_crate_path::impl_::extract_argument::PyFunctionArgument< diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 9ad86ecf736..a9c55ca13c0 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1800,7 +1800,6 @@ impl FunctionIntrospectionData<'_> { .python_signature .make_all_parameters_positional_only(); } - let returns = self.returns; self.names .iter() .flat_map(|name| { @@ -1810,7 +1809,7 @@ impl FunctionIntrospectionData<'_> { name, &signature, Some("self"), - parse_quote!(-> #returns), + PyExpr::from_return_type(self.returns.clone(), Some(cls)), [], false, self.is_returning_not_implemented_on_extraction_error, @@ -2212,19 +2211,20 @@ fn descriptors_to_items( #[cfg(feature = "experimental-inspect")] { // We generate introspection data - let return_type = &field.ty; + let parent = parse_quote!(#cls); + let return_type = field.ty.clone(); getter.add_introspection(function_introspection_code( &ctx.pyo3_path, None, &field_python_name(field, options.name.as_ref(), renaming_rule)?, &FunctionSignature::from_arguments(vec![]), Some("self"), - parse_quote!(-> #return_type), + PyExpr::from_into_py_object_maybe_ref(return_type, Some(&parent)), vec![PyExpr::builtin("property")], false, false, - utils::get_doc(&field.attrs, None).as_ref(), - Some(&parse_quote!(#cls)), + get_doc(&field.attrs, None).as_ref(), + Some(&parent), )); } items.push(getter); @@ -2261,7 +2261,7 @@ fn descriptors_to_items( annotation: None, })]), Some("self"), - syn::ReturnType::Default, + PyExpr::none(), vec![PyExpr::attribute( PyExpr::attribute( PyExpr::from_type( diff --git a/pyo3-macros-backend/src/pyfunction.rs b/pyo3-macros-backend/src/pyfunction.rs index cc45dcb1042..0f15ff10fe3 100644 --- a/pyo3-macros-backend/src/pyfunction.rs +++ b/pyo3-macros-backend/src/pyfunction.rs @@ -3,6 +3,8 @@ use crate::combine_errors::CombineErrors; #[cfg(feature = "experimental-inspect")] use crate::introspection::{function_introspection_code, introspection_id_const}; #[cfg(feature = "experimental-inspect")] +use crate::py_expr::PyExpr; +#[cfg(feature = "experimental-inspect")] use crate::utils::get_doc; use crate::utils::Ctx; use crate::{ @@ -21,8 +23,9 @@ use std::ffi::CString; use std::iter::empty; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -use syn::LitCStr; -use syn::{ext::IdentExt, spanned::Spanned, LitStr, Path, Result, Token}; +#[cfg(feature = "experimental-inspect")] +use syn::ReturnType; +use syn::{ext::IdentExt, spanned::Spanned, LitCStr, LitStr, Path, Result, Token}; mod signature; @@ -409,7 +412,10 @@ pub fn impl_wrap_pyfunction( &spec.python_name.to_string(), &spec.signature, None, - func.sig.output.clone(), + match &func.sig.output { + ReturnType::Type(_, t) => PyExpr::from_return_type((**t).clone(), None), + ReturnType::Default => PyExpr::none(), + }, empty(), func.sig.asyncness.is_some(), false, diff --git a/pyo3-macros-backend/src/pyimpl.rs b/pyo3-macros-backend/src/pyimpl.rs index b0fa7820201..471d1bb18e7 100644 --- a/pyo3-macros-backend/src/pyimpl.rs +++ b/pyo3-macros-backend/src/pyimpl.rs @@ -492,9 +492,13 @@ pub fn method_introspection_code( } let return_type = if spec.python_name == "__new__" { // Hack to return Self while implementing IntoPyObject - parse_quote!(-> #pyo3_path::PyClassGuard) + // TODO: use typing.Self? + PyExpr::from_return_type(parse_quote!(#pyo3_path::PyClassGuard), Some(parent)) } else { - spec.output.clone() + match spec.output.clone() { + ReturnType::Type(_, t) => PyExpr::from_return_type(*t, Some(parent)), + ReturnType::Default => PyExpr::none(), + } }; function_introspection_code( pyo3_path, diff --git a/src/impl_/introspection.rs b/src/impl_/introspection.rs index 90618902bd3..7695122eb67 100644 --- a/src/impl_/introspection.rs +++ b/src/impl_/introspection.rs @@ -27,6 +27,26 @@ impl PyReturnType for Result { const OUTPUT_TYPE: PyStaticExpr = T::OUTPUT_TYPE; } +#[diagnostic::on_unimplemented( + message = "`{Self}` cannot be converted to a Python object", + label = "required by `#[pyo3(get)]` to create a readable property from a field of type `{Self}`", + note = "implement `IntoPyObject` for `&{Self}` or `IntoPyObject + Clone` for `{Self}` to define the conversion" +)] +pub trait PyIntoPyObjectMaybeRefType { + const OUTPUT_TYPE: PyStaticExpr; +} + +impl<'a, 'py, T: 'a> PyIntoPyObjectMaybeRefType for T +where + &'a T: IntoPyObject<'py>, +{ + const OUTPUT_TYPE: PyStaticExpr = <&T as IntoPyObject<'_>>::OUTPUT_TYPE; +} + +impl<'py, T: IntoPyObject<'py>> PyIntoPyObjectMaybeRefType for T { + const OUTPUT_TYPE: PyStaticExpr = >::OUTPUT_TYPE; +} + #[repr(C)] pub struct SerializedIntrospectionFragment { pub length: u32, diff --git a/tests/test_getter_setter.rs b/tests/test_getter_setter.rs index 582cfd89c5f..39533b6f4dc 100644 --- a/tests/test_getter_setter.rs +++ b/tests/test_getter_setter.rs @@ -1,11 +1,11 @@ #![cfg(feature = "macros")] -use std::cell::Cell; - use pyo3::prelude::*; use pyo3::py_run; use pyo3::types::PyString; use pyo3::types::{IntoPyDict, PyList}; +use std::cell::Cell; +use std::convert::Infallible; mod test_utils; @@ -317,3 +317,40 @@ fn test_optional_setter() { ); }) } + +#[test] +fn test_ref_only_getter() { + #[derive(Clone)] + struct RefOnly; + + impl<'py> IntoPyObject<'py> for &RefOnly { + type Target = PyString; + type Output = Bound<'py, PyString>; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(PyString::new(py, "value")) + } + } + + #[pyclass] + struct Container { + #[pyo3(get)] + value: RefOnly, + } +} + +#[test] +fn test_unit_getter() { + #[derive(Clone)] + #[pyclass] + struct Container { + #[pyo3(get)] + value: (), + } + + Python::attach(|py| { + let instance = Py::new(py, Container { value: () }).unwrap(); + py_run!(py, instance, "assert instance.value is ()"); + }) +} diff --git a/tests/ui/invalid_property_args.inspect.stderr b/tests/ui/invalid_property_args.inspect.stderr index 919e39d8bbd..581c5952f50 100644 --- a/tests/ui/invalid_property_args.inspect.stderr +++ b/tests/ui/invalid_property_args.inspect.stderr @@ -46,12 +46,14 @@ error: `name` is useless without `get` or `set` 51 | struct NameWithoutGetSet(#[pyo3(name = "value")] i32); | ^^^^^^^^^^^^^^ -error[E0277]: the trait bound `PhantomData: pyo3::impl_::introspection::return_type::Sealed` is not satisfied +error[E0277]: `PhantomData` cannot be converted to a Python object --> tests/ui/invalid_property_args.rs:57:12 | 57 | value: ::std::marker::PhantomData, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoPyObject<'_>` is not implemented for `PhantomData` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by `#[pyo3(get)]` to create a readable property from a field of type `PhantomData` | + = help: the trait `IntoPyObject<'_>` is not implemented for `PhantomData` + = note: implement `IntoPyObject` for `&PhantomData` or `IntoPyObject + Clone` for `PhantomData` to define the conversion = help: the following other types implement trait `IntoPyObject<'py>`: &&'a T &&OsStr @@ -62,15 +64,7 @@ error[E0277]: the trait bound `PhantomData: pyo3::impl_::introspection::ret &'a (T0, T1, T2, T3) &'a (T0, T1, T2, T3, T4) and $N others - = note: required for `PhantomData` to implement `pyo3::impl_::introspection::return_type::Sealed` -note: required by a bound in `pyo3::impl_::introspection::PyReturnType::OUTPUT_TYPE` - --> src/impl_/introspection.rs - | - | pub trait PyReturnType: return_type::Sealed { - | ^^^^^^^^^^^^^^^^^^^ required by this bound in `PyReturnType::OUTPUT_TYPE` - | /// The function return type - | const OUTPUT_TYPE: PyStaticExpr; - | ----------- required by a bound in this associated constant + = note: required for `PhantomData` to implement `pyo3::impl_::introspection::PyIntoPyObjectMaybeRefType` error[E0277]: `PhantomData` cannot be converted to a Python object --> tests/ui/invalid_property_args.rs:57:12 diff --git a/tests/ui/invalid_property_args.rs b/tests/ui/invalid_property_args.rs index c387baa1248..e58c2cd17be 100644 --- a/tests/ui/invalid_property_args.rs +++ b/tests/ui/invalid_property_args.rs @@ -56,7 +56,7 @@ struct InvalidGetterType { #[pyo3(get)] value: ::std::marker::PhantomData, //~^ ERROR: `PhantomData` cannot be converted to a Python object - //~[inspect]| ERROR: the trait bound `PhantomData: pyo3::impl_::introspection::return_type::Sealed` is not satisfied + //~[inspect]| ERROR: `PhantomData` cannot be converted to a Python object } fn main() {} From a09305482deffaa0341472a73474c1b9001ea1af Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 7 Jul 2026 19:44:27 +0100 Subject: [PATCH 02/13] provide an opt-out of raw-dylib (#6185) * provide an opt-out of raw-dylib * newsfragments * remove dead constants * only emit link alias on windows * fixup expected lib name check * use explicit "lib" prefix on mingw * test fixups * Update pyo3-build-config/src/impl_.rs Co-authored-by: chiri --------- Co-authored-by: chiri --- Architecture.md | 1 + Cargo.toml | 12 ++- guide/src/building-and-distribution.md | 22 ++--- guide/src/features.md | 2 - newsfragments/6185.fixed.md | 1 + newsfragments/6185.packaging.md | 1 + noxfile.py | 4 +- pyo3-build-config/Cargo.toml | 4 - pyo3-build-config/src/impl_.rs | 77 ++++++++--------- pyo3-build-config/src/lib.rs | 28 ------ pyo3-ffi/Cargo.toml | 6 +- pyo3-ffi/build.rs | 113 ++++++++++++++++++++----- pyo3-ffi/src/impl_/macros.rs | 7 +- pytests/src/lib.rs | 1 + 14 files changed, 162 insertions(+), 117 deletions(-) create mode 100644 newsfragments/6185.fixed.md create mode 100644 newsfragments/6185.packaging.md diff --git a/Architecture.md b/Architecture.md index a78c0bff9c5..82872c752ae 100644 --- a/Architecture.md +++ b/Architecture.md @@ -166,6 +166,7 @@ Some of the functionality of `pyo3-build-config`: cfg with the target DLL name, and the `extern_libpython!` macro expands to the appropriate `#[link(name = "...", kind = "raw-dylib")]` attribute. This enables cross compiling Python extensions for Windows without having to install any Windows Python libraries. + This can be opted out by setting `PYO3_USE_RAW_DYLIB` to anything other than "1". diff --git a/Cargo.toml b/Cargo.toml index 4eb623f8ad5..df0759f60e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -237,7 +237,17 @@ unsafe_op_in_unsafe_fn = "warn" level = "warn" check-cfg = [ 'cfg(pyo3_disable_reference_pool)', - 'cfg(pyo3_leak_on_drop_without_reference_pool)' + 'cfg(pyo3_leak_on_drop_without_reference_pool)', + 'cfg(pyo3_use_raw_dylib)', + # this horrible hard-coded list should be kept in sync with the list of possible + # cfgs in pyo3-ffi/src/impl_/macros.rs + # + # for library names outside of this set we don't support raw-dylib and require a + # proper import library + # + # maybe in the future this list will not be necessary, see + # internals.rust-lang.org/t/support-renames-with-link-name-kind-raw-dylib/24415 + 'cfg(pyo3_dll, values("python3", "python3_d", "python3t", "python3t_d", "python38", "python38_d", "python39", "python39_d", "python310", "python310_d", "python311", "python311_d", "python312", "python312_d", "python313", "python313_d", "python313t", "python313t_d", "python314", "python314_d", "python314t", "python314t_d", "python315", "python315_d", "python315t", "python315t_d", "python316", "python316_d", "python316t", "python316t_d", "libpypy3.11-c"))', ] diff --git a/guide/src/building-and-distribution.md b/guide/src/building-and-distribution.md index 8c9b29c9bf4..78b33fb7e6d 100644 --- a/guide/src/building-and-distribution.md +++ b/guide/src/building-and-distribution.md @@ -49,7 +49,6 @@ Caused by: cargo:rustc-check-cfg=cfg(Py_3_14) cargo:rustc-check-cfg=cfg(Py_3_15) cargo:rustc-check-cfg=cfg(Py_3_16) - cargo:rustc-check-cfg=cfg(pyo3_dll, values("python3", "python3_d", "python3t", "python3t_d", "python38", "python38_d", "python39", "python39_d", "python310", "python310_d", "python311", "python311_d", "python312", "python312_d", "python313", "python313_d", "python313t", "python313t_d", "python314", "python314_d", "python314t", "python314t_d", "python315", "python315_d", "python315t", "python315t_d", "python316", "python316_d", "python316t", "python316t_d", "libpypy3.11-c")) cargo:rerun-if-env-changed=PYO3_CONFIG_FILE cargo:rerun-if-env-changed=PYO3_CROSS cargo:rerun-if-env-changed=PYO3_CROSS_LIB_DIR @@ -240,6 +239,16 @@ This should only be set when building a library for distribution. > > Projects are encouraged to migrate off the feature, as it caused [major development pain](faq.md#i-cant-run-cargo-test-or-i-cant-build-in-a-cargo-workspace-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror) due to the lack of linking. +### The `PYO3_USE_RAW_DYLIB` environment variable + +When targeting Windows, PyO3 will attempt to use [`raw-dylib` linking](https://doc.rust-lang.org/reference/items/external-blocks.html#dylib-versus-raw-dylib) to avoid the need for users to provide an actual import library to link against. + +On occasion the full Python import library may be needed (e.g. mixed C/Rust projects where the C code uses symbols not defined by `pyo3-ffi`). +In these cases, setting `PYO3_USE_RAW_DYLIB=0` can be used to disable `raw-dylib` linking. + +> [!NOTE] +> Historically PyO3 used a `generate-import-lib` feature which needed external machinery to achieve the same result of `raw-dylib` linking. + ### `Py_LIMITED_API`/`abi3`/`abi3t` By default, Python extension modules can only be used with the same Python version they were compiled against. @@ -399,8 +408,7 @@ When cross-compiling, PyO3's build script cannot execute the target Python inter - `PYO3_CROSS`: If present this variable forces PyO3 to configure as a cross-compilation. - `PYO3_CROSS_LIB_DIR`: This variable can be set to the directory containing the target's libpython DSO and the associated `_sysconfigdata*.py` file for Unix-like targets. - This variable is only needed when the output binary must link to libpython explicitly (e.g. when targeting Android or embedding a Python interpreter), or when it is absolutely required to get the interpreter configuration from `_sysconfigdata*.py`. - On Windows, this variable is not needed because PyO3 uses `raw-dylib` linking. + This variable is only needed when the output binary must link to libpython explicitly (e.g. when targeting Android, Windows when `raw-dylib` linking is unavailable, or embedding a Python interpreter), or when it is absolutely required to get the interpreter configuration from `_sysconfigdata*.py`. - `PYO3_CROSS_PYTHON_VERSION`: Major and minor version (e.g. 3.9) of the target Python installation. This variable is only needed if PyO3 cannot determine the version to target from `abi3-py3*` features, or if `PYO3_CROSS_LIB_DIR` is not set, or if there are multiple versions of Python present in `PYO3_CROSS_LIB_DIR`. - `PYO3_CROSS_PYTHON_IMPLEMENTATION`: Python implementation name ("CPython" or "PyPy") of the target Python installation. @@ -423,14 +431,6 @@ export PYO3_CROSS_LIB_DIR="/home/pyo3/cross/sysroot/usr/lib" cargo build --target armv7-unknown-linux-gnueabihf ``` -Or another example building for Windows (no `PYO3_CROSS_LIB_DIR` needed thanks to `raw-dylib`): - -```sh -export PYO3_CROSS_PYTHON_VERSION=3.9 - -cargo build --target x86_64-pc-windows-gnu -``` - Any of the `abi3-py3*` features can be enabled instead of setting `PYO3_CROSS_PYTHON_VERSION` in the above examples. `PYO3_CROSS_LIB_DIR` can often be omitted when cross compiling extension modules for Unix, macOS, and Windows targets. diff --git a/guide/src/features.md b/guide/src/features.md index 084b09c1fe2..ce60be8e457 100644 --- a/guide/src/features.md +++ b/guide/src/features.md @@ -323,5 +323,3 @@ See the [building and distribution](building-and-distribution.md#the-extension-m ### `generate-import-lib` This feature is deprecated and has no effect. -PyO3 now uses Rust's `raw-dylib` linking feature to link against the Python DLL on Windows, eliminating the need for import library (`.lib`) files entirely. -Cross-compiling for Windows targets works without any additional setup. diff --git a/newsfragments/6185.fixed.md b/newsfragments/6185.fixed.md new file mode 100644 index 00000000000..2666d7806e7 --- /dev/null +++ b/newsfragments/6185.fixed.md @@ -0,0 +1 @@ +Fix PyO3 0.29 regression with failure to link under Cygwin / MSYS2. diff --git a/newsfragments/6185.packaging.md b/newsfragments/6185.packaging.md new file mode 100644 index 00000000000..a064fa03aec --- /dev/null +++ b/newsfragments/6185.packaging.md @@ -0,0 +1 @@ +Add `PYO3_USE_RAW_DYLIB=0` opt-out of `raw-dylib` linking for Windows. diff --git a/noxfile.py b/noxfile.py index c383dfe9eb2..dff06ff6fdf 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1433,7 +1433,7 @@ def _check_raw_dylib_macro(session: nox.Session): # Build the set of DLL names that default_lib_name_windows can produce expected_dlls = {"python3", "python3_d"} - for minor in range(min_minor, max_minor + 1): + for minor in range(min_minor, max_minor + 2): # allow prerelease of next version expected_dlls.add(f"python3{minor}") expected_dlls.add(f"python3{minor}_d") if minor >= 13: @@ -1452,7 +1452,7 @@ def _check_raw_dylib_macro(session: nox.Session): # Parse the DLL name list in the extern_libpython!(@impl ...) invocation lib_rs = (PYO3_DIR / "pyo3-ffi" / "src" / "impl_" / "macros.rs").read_text() - found_dlls = set(re.findall(r'"((?:python|libpypy)[^"]+)"', lib_rs)) + found_dlls = set(re.findall(r'"((?:python(?!XY)|libpypy)[^"]+)"', lib_rs)) missing = expected_dlls - found_dlls extra = found_dlls - expected_dlls diff --git a/pyo3-build-config/Cargo.toml b/pyo3-build-config/Cargo.toml index 523c45034fe..9d196a46536 100644 --- a/pyo3-build-config/Cargo.toml +++ b/pyo3-build-config/Cargo.toml @@ -22,9 +22,5 @@ default = [] # deprecated resolve-config = [] - -# deprecated extension-module = [] - -# deprecated: no longer needed, raw-dylib is used instead generate-import-lib = [] diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index 83e75863877..21eefad8331 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -25,15 +25,6 @@ use crate::{ /// Minimum Python version PyO3 supports. pub(crate) const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 8 }; -pub(crate) const MINIMUM_SUPPORTED_VERSION_PYPY: PythonVersion = PythonVersion { - major: 3, - minor: 11, -}; -pub(crate) const MAXIMUM_SUPPORTED_VERSION_PYPY: PythonVersion = PythonVersion { - major: 3, - minor: 11, -}; - pub(crate) const MINIMUM_SUPPORTED_VERSION_ABI3T: PythonVersion = PythonVersion { major: 3, minor: 15, @@ -2392,6 +2383,15 @@ fn default_lib_name_for_target(abi: PythonAbi, target: &Triple) -> String { } fn default_lib_name_windows(abi: PythonAbi, mingw: bool, debug: bool) -> Result { + // mingw formats lib names like unix, and uses a "lib" prefix. We could let the linker + // handle "lib" prefix, but that means the `raw-dylib` name is incorrect (where the + // "lib" prefix is not automatically added). + if mingw { + let mut lib_name = default_lib_name_unix(abi, true, None)?; + lib_name.insert_str(0, "lib"); + return Ok(lib_name); + } + if abi.implementation.is_pypy() { // PyPy on Windows ships `libpypy3.X-c.dll` (e.g. `libpypy3.11-c.dll`), // not CPython's `pythonXY.dll`. With raw-dylib linking we need the real @@ -2419,13 +2419,6 @@ fn default_lib_name_windows(abi: PythonAbi, mingw: bool, debug: bool) -> Result< lib_name = lib_name.replace("python3", "python3t"); } Ok(lib_name) - } else if mingw { - ensure!( - !abi.kind.is_free_threaded(), - "MinGW free-threaded builds are not currently tested or supported" - ); - // https://packages.msys2.org/base/mingw-w64-python - Ok(format!("python{}.{}", abi.version.major, abi.version.minor)) } else if abi.kind().is_free_threaded() { #[expect(deprecated, reason = "using constant internally")] { @@ -2449,28 +2442,36 @@ fn default_lib_name_windows(abi: PythonAbi, mingw: bool, debug: bool) -> Result< } } -fn default_lib_name_unix(abi: PythonAbi, cygwin: bool, ld_version: Option<&str>) -> Result { +fn default_lib_name_unix( + abi: PythonAbi, + use_stable_abi_lib: bool, + ld_version: Option<&str>, +) -> Result { match abi.implementation { PythonImplementation::CPython => match ld_version { Some(ld_version) => Ok(format!("python{ld_version}")), - None => { - if cygwin && matches!(abi.kind, PythonAbiKind::Stable(StableAbi::Abi3)) { + None => match abi.kind { + PythonAbiKind::Stable(StableAbi::Abi3) if use_stable_abi_lib => { Ok("python3".to_string()) - } else if cygwin && matches!(abi.kind, PythonAbiKind::Stable(StableAbi::Abi3t)) { + } + PythonAbiKind::Stable(StableAbi::Abi3t) if use_stable_abi_lib => { Ok("python3t".to_string()) - } else if abi.kind.is_free_threaded() { - #[expect(deprecated, reason = "using constant internally")] - { - ensure!(abi.version >= PythonVersion::PY313, "Cannot compile extensions for the free-threaded build on Python versions earlier than 3.13, found {}.{}", abi.version.major, abi.version.minor); + } + _ => { + if abi.kind.is_free_threaded() { + #[expect(deprecated, reason = "using constant internally")] + { + ensure!(abi.version >= PythonVersion::PY313, "Cannot compile extensions for the free-threaded build on Python versions earlier than 3.13, found {}.{}", abi.version.major, abi.version.minor); + } + Ok(format!( + "python{}.{}t", + abi.version.major, abi.version.minor + )) + } else { + Ok(format!("python{}.{}", abi.version.major, abi.version.minor)) } - Ok(format!( - "python{}.{}t", - abi.version.major, abi.version.minor - )) - } else { - Ok(format!("python{}.{}", abi.version.major, abi.version.minor)) } - } + }, }, PythonImplementation::PyPy => match ld_version { Some(ld_version) => Ok(format!("pypy{ld_version}-c")), @@ -3365,7 +3366,7 @@ mod tests { false, ) .unwrap(), - "python3.9", + "libpython3.9", ); assert_eq!( super::default_lib_name_windows( @@ -3377,7 +3378,7 @@ mod tests { false, ) .unwrap(), - "python3", + "libpython3", ); assert_eq!( super::default_lib_name_windows( @@ -3441,16 +3442,6 @@ mod tests { .unwrap(), "python3_d", ); - // mingw and free-threading are incompatible (until someone adds support) - assert!(super::default_lib_name_windows( - PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY313) - .free_threaded() - .finalize() - .unwrap(), - true, - false, - ) - .is_err()); assert_eq!( super::default_lib_name_windows( PythonAbiBuilder::new(PythonImplementation::CPython, PythonVersion::PY313) diff --git a/pyo3-build-config/src/lib.rs b/pyo3-build-config/src/lib.rs index 26a0b0b773f..ce0eb28a588 100644 --- a/pyo3-build-config/src/lib.rs +++ b/pyo3-build-config/src/lib.rs @@ -164,34 +164,6 @@ pub fn print_expected_cfgs() { for i in impl_::MINIMUM_SUPPORTED_VERSION.minor..=impl_::STABLE_ABI_MAX_MINOR + 1 { println!("cargo:rustc-check-cfg=cfg(Py_3_{i})"); } - - // pyo3_dll cfg for raw-dylib linking on Windows - let mut dll_names = vec![ - "python3".to_string(), - "python3_d".to_string(), - "python3t".to_string(), - "python3t_d".to_string(), - ]; - for i in impl_::MINIMUM_SUPPORTED_VERSION.minor..=impl_::STABLE_ABI_MAX_MINOR + 1 { - dll_names.push(format!("python3{i}")); - dll_names.push(format!("python3{i}_d")); - if i >= 13 { - dll_names.push(format!("python3{i}t")); - dll_names.push(format!("python3{i}t_d")); - } - } - // PyPy DLL names (libpypy3.X-c.dll) - for i in - impl_::MINIMUM_SUPPORTED_VERSION_PYPY.minor..=impl_::MAXIMUM_SUPPORTED_VERSION_PYPY.minor - { - dll_names.push(format!("libpypy3.{i}-c")); - } - let values = dll_names - .iter() - .map(|n| format!("\"{n}\"")) - .collect::>() - .join(", "); - println!("cargo:rustc-check-cfg=cfg(pyo3_dll, values({values}))"); } /// Private exports used in PyO3's build.rs diff --git a/pyo3-ffi/Cargo.toml b/pyo3-ffi/Cargo.toml index abfbca8724b..057fc65216e 100644 --- a/pyo3-ffi/Cargo.toml +++ b/pyo3-ffi/Cargo.toml @@ -19,9 +19,6 @@ libc = "0.2.62" default = [] -# deprecated -extension-module = ["pyo3-build-config/extension-module"] - # Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more. abi3 = [] @@ -40,7 +37,8 @@ abi3-py315 = ["abi3"] abi3t-py315 = ["abi3t"] -# deprecated: no longer needed, raw-dylib is used instead +# deprecated +extension-module = ["pyo3-build-config/extension-module"] generate-import-lib = ["pyo3-build-config/generate-import-lib"] [dev-dependencies] diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs index c978ad05077..7ec773cf33d 100644 --- a/pyo3-ffi/build.rs +++ b/pyo3-ffi/build.rs @@ -191,15 +191,79 @@ fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result Ok(()) } +/// `raw-dylib` currently does not support arbitrary names +/// (see https://internals.rust-lang.org/t/support-renames-with-link-name-kind-raw-dylib/24415) +/// so if the lib name is not one of the known subset, we must fall back to full linking. +fn lib_name_is_known_for_raw_dylib(lib_name: &str) -> bool { + // pyo3_dll cfg for raw-dylib linking on Windows + if matches!( + lib_name, + "python3" | "python3_d" | "python3t" | "python3t_d" + ) { + return true; + } + + // support raw-dylib linking for all CPython versions supported, plus the next prerelease + for i in SUPPORTED_VERSIONS_CPYTHON.min.minor..=SUPPORTED_VERSIONS_CPYTHON.max.minor + 1 { + if lib_name == format!("python3{i}") || lib_name == format!("python3{i}_d") { + return true; + } + if i >= 13 && (lib_name == format!("python3{i}t") || lib_name == format!("python3{i}t_d")) { + return true; + } + } + // PyPy DLL names (libpypy3.X-c.dll) + for i in SUPPORTED_VERSIONS_PYPY.min.minor..=SUPPORTED_VERSIONS_PYPY.max.minor { + if lib_name == format!("libpypy3.{i}-c") { + return true; + } + } + + false +} + +/// Whether to use raw-dylib linking. +/// +/// Currently, this only applies if all of the following are true: +/// - The target OS is Windows. +/// - The Python library name is one of the [known subset][lib_name_is_known_for_raw_dylib]. +/// - The `PYO3_USE_RAW_DYLIB` environment variable is not set, or is set to `1`. +/// +/// NB in some cases (e.g. mixed C / Rust builds) it might be necessary to link the full Python +/// library rather than rely on the symbols which PyO3 defines as raw-dylib, which is why +/// we have the opt-out env var. +fn should_use_raw_dylib_linking(lib_name: &str) -> bool { + let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap(); + if target_os != "windows" { + return false; + } + + match ( + lib_name_is_known_for_raw_dylib(lib_name), + env_var("PYO3_USE_RAW_DYLIB"), + ) { + (true, None) => true, + (true, Some(os_str)) if os_str == "1" => true, + (false, Some(os_str)) if os_str == "1" => { + warn!( + "PYO3_USE_RAW_DYLIB is set to 1 but the Python library name is not recognized. \ + Falling back to full linking." + ); + false + } + _ => false, + } +} + fn emit_link_config(build_config: &BuildConfig) -> Result<()> { - let interpreter_config = &build_config.interpreter_config; let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap(); + let interpreter_config = &build_config.interpreter_config; let lib_name = interpreter_config .lib_name() .ok_or("attempted to link to Python shared library but config does not contain lib_name")?; - if target_os == "windows" { + if should_use_raw_dylib_linking(lib_name) { // Use raw-dylib linking: emit a cfg so that `extern_libpython!` picks the // right `#[link(name = "...", kind = "raw-dylib")]` attribute at compile time. // This eliminates the need for import libraries (.lib files) entirely. @@ -207,27 +271,36 @@ fn emit_link_config(build_config: &BuildConfig) -> Result<()> { // Note: raw-dylib is inherently dynamic linking. Static embedding of the // Python interpreter on Windows is not supported by this path (and is not // officially supported by CPython on Windows). + println!("cargo:rustc-cfg=pyo3_use_raw_dylib"); println!("cargo:rustc-cfg=pyo3_dll=\"{lib_name}\""); - } else { - println!( - "cargo:rustc-link-lib={link_model}{lib_name}", - link_model = if interpreter_config.shared() { - "" - } else { - "static=" - }, - ); + return Ok(()); + } - if let Some(lib_dir) = interpreter_config.lib_dir() { - println!("cargo:rustc-link-search=native={lib_dir}"); - } else if matches!(build_config.source, BuildConfigSource::CrossCompile) { - warn!( - "The output binary will link to libpython, \ - but PYO3_CROSS_LIB_DIR environment variable is not set. \ - Ensure that the target Python library directory is \ - in the rustc native library search path." - ); + println!( + "cargo:rustc-link-lib={link_model}{alias}{lib_name}", + link_model = if interpreter_config.shared() { + "" + } else { + "static=" + }, + // on windows we emit `#[link(name = "pythonXY")]` attributes + // and need this alias here to get the right name for the final link + alias = if target_os == "windows" { + "pythonXY:" + } else { + "" } + ); + + if let Some(lib_dir) = interpreter_config.lib_dir() { + println!("cargo:rustc-link-search=native={lib_dir}"); + } else if matches!(build_config.source, BuildConfigSource::CrossCompile) { + warn!( + "The output binary will link to libpython, \ + but PYO3_CROSS_LIB_DIR environment variable is not set. \ + Ensure that the target Python library directory is \ + in the rustc native library search path." + ); } Ok(()) diff --git a/pyo3-ffi/src/impl_/macros.rs b/pyo3-ffi/src/impl_/macros.rs index b48eceea15f..1ef2ce99091 100644 --- a/pyo3-ffi/src/impl_/macros.rs +++ b/pyo3-ffi/src/impl_/macros.rs @@ -269,10 +269,12 @@ macro_rules! extern_libpython { "python313", "python313_d", "python314", "python314_d", "python315", "python315_d", + "python316", "python316_d", // free-threaded builds (3.13+) "python313t", "python313t_d", "python314t", "python314t_d", "python315t", "python315t_d", + "python316t", "python316t_d", // PyPy (DLL is libpypy3.X-c.dll, not pythonXY.dll) "libpypy3.11-c", ); @@ -287,11 +289,12 @@ macro_rules! extern_libpython { // separate cfg_attr arms per architecture. (@impl $abi:literal { $($body:tt)* } $($dll:literal),* $(,)?) => { $( - #[cfg_attr(all(windows, target_arch = "x86", pyo3_dll = $dll), + #[cfg_attr(all(windows, pyo3_use_raw_dylib, target_arch = "x86", pyo3_dll = $dll), link(name = $dll, kind = "raw-dylib", import_name_type = "undecorated"))] - #[cfg_attr(all(windows, not(target_arch = "x86"), pyo3_dll = $dll), + #[cfg_attr(all(windows, pyo3_use_raw_dylib, not(target_arch = "x86"), pyo3_dll = $dll), link(name = $dll, kind = "raw-dylib"))] )* + #[cfg_attr(all(windows, not(pyo3_use_raw_dylib)), link(name = "pythonXY"))] extern $abi { extern_libpython_items! { $($body)* } } diff --git a/pytests/src/lib.rs b/pytests/src/lib.rs index b28d85d9838..5771f79f1ab 100644 --- a/pytests/src/lib.rs +++ b/pytests/src/lib.rs @@ -55,6 +55,7 @@ mod pyo3_pytests { sys_modules.set_item("pyo3_pytests.awaitable", m.getattr("awaitable")?)?; sys_modules.set_item("pyo3_pytests.buf_and_str", m.getattr("buf_and_str")?)?; sys_modules.set_item("pyo3_pytests.comparisons", m.getattr("comparisons")?)?; + #[cfg(not(Py_LIMITED_API))] sys_modules.set_item("pyo3_pytests.datetime", m.getattr("datetime")?)?; sys_modules.set_item("pyo3_pytests.dict_iter", m.getattr("dict_iter")?)?; sys_modules.set_item("pyo3_pytests.enums", m.getattr("enums")?)?; From 908518f0b47ec96bc2c3eade8a78635715be75bf Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 4 Aug 2026 21:55:13 +0100 Subject: [PATCH 03/13] fix deprecation warning for feature removed on `main` --- tests/test_getter_setter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_getter_setter.rs b/tests/test_getter_setter.rs index 39533b6f4dc..8c2465e7ca2 100644 --- a/tests/test_getter_setter.rs +++ b/tests/test_getter_setter.rs @@ -343,7 +343,7 @@ fn test_ref_only_getter() { #[test] fn test_unit_getter() { #[derive(Clone)] - #[pyclass] + #[pyclass(skip_from_py_object)] struct Container { #[pyo3(get)] value: (), From 6ac44358d872cda8595ae21bf6e33ca5c8efabe9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 09:03:16 +0200 Subject: [PATCH 04/13] build(deps): bump CodSpeedHQ/action from 4 to 5.0.1 (#6286) Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4 to 5.0.1. - [Release notes](https://github.com/codspeedhq/action/releases) - [Changelog](https://github.com/CodSpeedHQ/action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codspeedhq/action/compare/v4...v5.0.1) --- updated-dependencies: - dependency-name: CodSpeedHQ/action dependency-version: 5.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml index 7a1f92b024b..3c562cc76d4 100644 --- a/.github/workflows/benches.yml +++ b/.github/workflows/benches.yml @@ -46,7 +46,7 @@ jobs: tool: cargo-codspeed - name: Run the benchmarks - uses: CodSpeedHQ/action@v4 + uses: CodSpeedHQ/action@v5.0.1 with: run: uvx nox -s codspeed token: ${{ secrets.CODSPEED_TOKEN }} From c3852c82099c2278236f947c16483fdb1d727ffc Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 5 Aug 2026 06:06:56 +0100 Subject: [PATCH 05/13] unblock CI via a uv constraint (#6295) --- .github/uv-constraints.txt | 2 ++ .github/workflows/benches.yml | 1 + .github/workflows/build.yml | 21 ++++++-------- .github/workflows/changelog.yml | 4 +-- .github/workflows/ci.yml | 43 +++++++++++++++-------------- .github/workflows/netlify-build.yml | 9 ++---- .github/workflows/release.yml | 3 ++ 7 files changed, 42 insertions(+), 41 deletions(-) create mode 100644 .github/uv-constraints.txt diff --git a/.github/uv-constraints.txt b/.github/uv-constraints.txt new file mode 100644 index 00000000000..cb0664b25d6 --- /dev/null +++ b/.github/uv-constraints.txt @@ -0,0 +1,2 @@ +# see https://github.com/kislyuk/argcomplete/issues/559 +argcomplete<3.7.1; python_version < "3.10" diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml index 3c562cc76d4..6dfb55b08a9 100644 --- a/.github/workflows/benches.yml +++ b/.github/workflows/benches.yml @@ -11,6 +11,7 @@ concurrency: cancel-in-progress: true env: + UV_CONSTRAINT: ${{ github.workspace }}/.github/uv-constraints.txt UV_PYTHON: "3.14t" jobs: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1f1a047521f..a20ea87a0ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,6 +28,7 @@ on: env: NOX_DEFAULT_VENV_BACKEND: uv + UV_CONSTRAINT: ${{ github.workspace }}/.github/uv-constraints.txt jobs: build: @@ -49,17 +50,13 @@ jobs: # PyPy can have FFI changes within Python versions, which creates pain in CI check-latest: ${{ startsWith(inputs.python-version, 'pypy') }} - # workaround for the above, only available for 3.9 - - if: ${{ inputs.os == 'macos-latest' && contains(fromJSON('["3.9"]'), inputs.python-version) && inputs.python-architecture == 'x64' }} - name: Set up Python ${{ inputs.python-version }} + - name: Set up uv uses: astral-sh/setup-uv@v7 with: - python-version: cpython-${{ inputs.python-version }}-macos-x86-64 + # workaround for the above, only available for 3.9 + python-version: ${{ inputs.os == 'macos-latest' && contains(fromJSON('["3.9"]'), inputs.python-version) && inputs.python-architecture == 'x64' && format('cpython-{0}-macos-x86-64', inputs.python-version) || '' }} save-cache: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - - name: Install nox - run: python -m pip install --upgrade pip && pip install nox[uv] - - name: Install Rust toolchain uses: dtolnay/rust-toolchain@master with: @@ -91,7 +88,7 @@ jobs: - if: inputs.rust == inputs.MSRV name: Prepare MSRV package versions - run: nox -s set-msrv-package-versions + run: uvx nox -s set-msrv-package-versions - if: inputs.rust != 'stable' name: Ignore changed error messages for ui tests (still run for coverage) @@ -113,20 +110,20 @@ jobs: - name: Run pyo3-ffi-check # TODO: investigate graalpy failures if: ${{ endsWith(inputs.python-version, '-dev') || (steps.ffi-changes.outputs.changed == 'true' && inputs.rust == 'stable' && !startsWith(inputs.python-version, 'graalpy')) }} - run: nox -s ffi-check + run: uvx nox -s ffi-check - uses: ./.github/actions/prepare-coverage if: ${{ inputs.os != 'windows-11-arm' }} # https://github.com/rust-lang/rust/issues/150123 - name: Build docs - run: nox -s docs + run: uvx nox -s docs - name: Run Rust tests - run: nox -s test-rust + run: uvx nox -s test-rust - name: Test python examples and tests shell: bash - run: nox -s test-py + run: uvx nox -s test-py env: CARGO_TARGET_DIR: ${{ github.workspace }}/target diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 0ef6b0654bd..2938aa128b1 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -13,5 +13,5 @@ jobs: - uses: actions/setup-python@v6 with: python-version: '3.14' - - run: python -m pip install --upgrade pip && pip install nox[uv] - - run: nox -s check-changelog + - uses: astral-sh/setup-uv@v7 + - run: uvx nox -s check-changelog diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6ee83d48b3..e3e1d250dd1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,7 @@ concurrency: env: CARGO_TERM_COLOR: always NOX_DEFAULT_VENV_BACKEND: uv + UV_CONSTRAINT: ${{ github.workspace }}/.github/uv-constraints.txt UV_PYTHON: 3.14 jobs: @@ -23,20 +24,20 @@ jobs: - uses: actions/setup-python@v6 with: python-version: "3.14" - - run: python -m pip install --upgrade pip && pip install nox[uv] + - uses: astral-sh/setup-uv@v7 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - name: Check python formatting and lints (ruff) - run: nox -s ruff + run: uvx nox -s ruff - name: Check rust formatting (rustfmt) - run: nox -s rustfmt + run: uvx nox -s rustfmt - name: Check markdown formatting (rumdl) - run: nox -s rumdl + run: uvx nox -s rumdl - name: Check `required-features` in Cargo.toml - run: nox -s check-test-features + run: uvx nox -s check-test-features - name: Spell check - run: nox -s typos + run: uvx nox -s typos resolve: runs-on: ubuntu-latest @@ -95,12 +96,12 @@ jobs: - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - - run: python -m pip install --upgrade pip && pip install nox[uv] + - uses: astral-sh/setup-uv@v7 # This is a smoke test to confirm that CI will run on MSRV (including dev dependencies) - name: Check with MSRV package versions run: | - nox -s set-msrv-package-versions - nox -s check-all + uvx nox -s set-msrv-package-versions + uvx nox -s check-all env: CARGO_BUILD_TARGET: x86_64-unknown-linux-gnu @@ -447,8 +448,8 @@ jobs: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@valgrind - - run: python -m pip install --upgrade pip && pip install nox[uv] - - run: nox -s test-rust -- release skip-full + - uses: astral-sh/setup-uv@v7 + - run: uvx nox -s test-rust -- release skip-full env: CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: valgrind --leak-check=no --error-exitcode=1 RUST_BACKTRACE: 1 @@ -470,8 +471,8 @@ jobs: with: components: rust-src - uses: taiki-e/install-action@cargo-careful - - run: python -m pip install --upgrade pip && pip install nox[uv] - - run: nox -s test-rust -- careful skip-full + - uses: astral-sh/setup-uv@v7 + - run: uvx nox -s test-rust -- careful skip-full env: RUST_BACKTRACE: 1 UI_TEST: skip @@ -661,8 +662,8 @@ jobs: - uses: taiki-e/install-action@v2 with: tool: cargo-hack,cargo-minimal-versions - - run: python3 -m pip install --upgrade pip && pip install nox[uv] - - run: python3 -m nox -s check-feature-powerset -- ${{ matrix.rust != 'stable' && 'minimal-versions' || '' }} + - uses: astral-sh/setup-uv@v7 + - run: uvx nox -s check-feature-powerset -- ${{ matrix.rust != 'stable' && 'minimal-versions' || '' }} test-cross-compilation: needs: [fmt, resolve] @@ -816,8 +817,8 @@ jobs: - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - - run: python -m pip install --upgrade pip && pip install nox[uv] - - run: nox -s test-introspection + - uses: astral-sh/setup-uv@v7 + - run: uvx nox -s test-introspection env: CARGO_BUILD_TARGET: ${{ matrix.platform.rust-target }} @@ -831,8 +832,8 @@ jobs: - uses: actions/setup-python@v6 with: python-version: "3.14" - - run: python -m pip install --upgrade pip && pip install nox[uv] - - run: nox -s test-introspection + - uses: astral-sh/setup-uv@v7 + - run: uvx nox -s test-introspection pytests-type-checking: needs: [fmt] @@ -850,8 +851,8 @@ jobs: - uses: actions/setup-python@v6 with: python-version: "3.14" - - run: python -m pip install --upgrade pip && pip install nox[uv] - - run: nox -s ${{matrix.checker}} + - uses: astral-sh/setup-uv@v7 + - run: uvx nox -s ${{matrix.checker}} working-directory: pytests conclusion: diff --git a/.github/workflows/netlify-build.yml b/.github/workflows/netlify-build.yml index 0eea2be60b0..bafeeff0382 100644 --- a/.github/workflows/netlify-build.yml +++ b/.github/workflows/netlify-build.yml @@ -23,6 +23,7 @@ jobs: - uses: actions/setup-python@v6 with: python-version: "3.14" + - uses: astral-sh/setup-uv@v7 - uses: dtolnay/rust-toolchain@nightly @@ -47,9 +48,7 @@ jobs: # This builds the book in target/guide/. - name: Build the guide - run: | - python -m pip install --upgrade pip && pip install nox[uv] - nox -s ${{ github.event_name == 'release' && 'build-guide' || 'check-guide' }} + run: uvx nox -s ${{ github.event_name == 'release' && 'build-guide' || 'check-guide' }} env: PYO3_VERSION_TAG: ${{ github.event_name == 'release' && steps.prepare_tag.outputs.tag_name || 'main' }} # allows lychee to get better rate limits from github @@ -79,9 +78,7 @@ jobs: echo "PYO3_VERSION=${PYO3_VERSION}" >> $GITHUB_ENV - name: Build the site - run: | - python -m pip install --upgrade pip && pip install nox[uv] towncrier requests - nox -s build-netlify-site -- ${{ (github.ref != 'refs/heads/main' && '--preview') || '' }} + run: uvx --with requests nox -s build-netlify-site -- ${{ (github.ref != 'refs/heads/main' && '--preview') || '' }} # Upload the built site as an artifact for deploy workflow to consume - name: Upload Build Artifact diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a84dc721e87..4886fca7cb7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,9 @@ on: version: description: The version to build +env: + UV_CONSTRAINT: ${{ github.workspace }}/.github/uv-constraints.txt + jobs: release: permissions: From b4fb96b56de76897d4c9ad22ca705a1782390653 Mon Sep 17 00:00:00 2001 From: Ben Beasley Date: Wed, 5 Aug 2026 07:00:06 +0100 Subject: [PATCH 06/13] Restore pyo3-introspection license files (#6289) These were deleted in https://github.com/PyO3/pyo3/pull/6130 since `maturin sdist` was reportedly not following symlinks. However, the chosen licenses require their texts to be distributed with the software, so this commit restores them. Using duplicate copies of the top-level license files rather than symlinks sidesteps any possible publication difficulties. --- pyo3-introspection/LICENSE-APACHE | 178 ++++++++++++++++++++++++++++++ pyo3-introspection/LICENSE-MIT | 25 +++++ 2 files changed, 203 insertions(+) create mode 100644 pyo3-introspection/LICENSE-APACHE create mode 100644 pyo3-introspection/LICENSE-MIT diff --git a/pyo3-introspection/LICENSE-APACHE b/pyo3-introspection/LICENSE-APACHE new file mode 100644 index 00000000000..72207b851d3 --- /dev/null +++ b/pyo3-introspection/LICENSE-APACHE @@ -0,0 +1,178 @@ +Copyright (c) 2017-present PyO3 Project and Contributors. https://github.com/PyO3 + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/pyo3-introspection/LICENSE-MIT b/pyo3-introspection/LICENSE-MIT new file mode 100644 index 00000000000..cd0ad009a8b --- /dev/null +++ b/pyo3-introspection/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2023-present PyO3 Project and Contributors. https://github.com/PyO3 + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. From abb64e8740e6cc5fd7a2ac5ae7651602152466ee Mon Sep 17 00:00:00 2001 From: ImFeH2 Date: Wed, 5 Aug 2026 14:00:21 +0800 Subject: [PATCH 07/13] fix: pass class to classmethod magic methods (#6283) * fix: pass class to classmethod magic methods * docs: add newsfragment for PR 6283 --- newsfragments/6283.fixed.md | 1 + pyo3-macros-backend/src/method.rs | 33 +++++++++++++++- pyo3-macros-backend/src/pyfunction.rs | 3 +- pyo3-macros-backend/src/pymethod.rs | 22 +++++++++-- tests/test_methods.rs | 57 +++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 newsfragments/6283.fixed.md diff --git a/newsfragments/6283.fixed.md b/newsfragments/6283.fixed.md new file mode 100644 index 00000000000..66a372d899d --- /dev/null +++ b/newsfragments/6283.fixed.md @@ -0,0 +1 @@ +Fix `#[classmethod]` magic methods receiving the instance instead of its type when invoked through a type slot. diff --git a/pyo3-macros-backend/src/method.rs b/pyo3-macros-backend/src/method.rs index 65b3727cc07..c40be23b910 100644 --- a/pyo3-macros-backend/src/method.rs +++ b/pyo3-macros-backend/src/method.rs @@ -235,6 +235,12 @@ pub enum FnType { ClassAttribute, } +#[derive(Clone, Copy, Debug)] +pub(crate) enum ClassMethodReceiver { + Class, + Instance, +} + impl FnType { pub fn skip_first_rust_argument_in_python_signature(&self) -> bool { match self { @@ -264,6 +270,7 @@ impl FnType { cls: Option<&syn::Type>, error_mode: ExtractErrorMode, self_conversion: SelfConversionPolicy, + class_method_receiver: ClassMethodReceiver, holders: &mut Holders, ctx: &Ctx, ) -> Option { @@ -282,10 +289,32 @@ impl FnType { let py = syn::Ident::new("py", Span::call_site()); let slf: Ident = syn::Ident::new("_slf", Span::call_site()); let pyo3_path = pyo3_path.to_tokens_spanned(*span); + let class_method_receiver = match class_method_receiver { + ClassMethodReceiver::Class => quote! { #slf.cast() }, + ClassMethodReceiver::Instance => { + let type_check = match self_conversion.0 { + SelfConversionPolicyInner::Trusted => quote! {}, + SelfConversionPolicyInner::Checked => { + let cls = cls.expect("no class given for a class method"); + let type_check = error_mode.handle_error( + quote_spanned! { *span => + #pyo3_path::Bound::ref_from_ptr(#py, &#slf).cast::<#cls>() + }, + ctx, + ); + quote! { #type_check; } + } + }; + quote! {{ + #type_check + #pyo3_path::ffi::Py_TYPE(#slf).cast() + }} + } + }; let ret = quote_spanned! { *span => #[allow(clippy::useless_conversion, reason = "#[classmethod] accepts anything which implements `From<&Bound>`")] ::std::convert::Into::into( - #pyo3_path::Bound::ref_from_ptr(#py, &#slf.cast()) + #pyo3_path::Bound::ref_from_ptr(#py, &#class_method_receiver) .cast_unchecked::<#pyo3_path::types::PyType>() ) }; @@ -779,6 +808,7 @@ impl<'a> FnSpec<'a> { cls: Option<&syn::Type>, convention: CallingConvention, self_conversion: SelfConversionPolicy, + class_method_receiver: ClassMethodReceiver, ctx: &Ctx, ) -> Result { let Ctx { @@ -805,6 +835,7 @@ impl<'a> FnSpec<'a> { cls, ExtractErrorMode::Raise, self_conversion, + class_method_receiver, &mut holders, ctx, ); diff --git a/pyo3-macros-backend/src/pyfunction.rs b/pyo3-macros-backend/src/pyfunction.rs index 0f15ff10fe3..759ed8d8081 100644 --- a/pyo3-macros-backend/src/pyfunction.rs +++ b/pyo3-macros-backend/src/pyfunction.rs @@ -12,7 +12,7 @@ use crate::{ self, get_pyo3_options, take_attributes, take_pyo3_options, CrateAttribute, FromPyWithAttribute, NameAttribute, TextSignatureAttribute, }, - method::{self, CallingConvention, FnArg, SelfConversionPolicy}, + method::{self, CallingConvention, ClassMethodReceiver, FnArg, SelfConversionPolicy}, pymethod::check_generic, }; use proc_macro2::{Span, TokenStream}; @@ -442,6 +442,7 @@ pub fn impl_wrap_pyfunction( None, calling_convention, SelfConversionPolicy::checked(), + ClassMethodReceiver::Class, ctx, )?; let methoddef = spec.get_methoddef( diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 099a138e6b0..885e8f73640 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -4,7 +4,9 @@ use std::ffi::CString; use crate::attributes::{FromPyWithAttribute, NameAttribute, RenamingRule}; #[cfg(feature = "experimental-inspect")] use crate::introspection::unique_element_id; -use crate::method::{CallingConvention, ExtractErrorMode, PyArg, SelfConversionPolicy}; +use crate::method::{ + CallingConvention, ClassMethodReceiver, ExtractErrorMode, PyArg, SelfConversionPolicy, +}; use crate::params::{impl_arg_params, impl_regular_arg_param, Holders}; use crate::pyfunction::WarningFactory; use crate::utils::PythonDoc; @@ -409,6 +411,7 @@ pub fn impl_py_method_def( // instance of the owning type before reaching the C function. The // trusted path is therefore valid. unsafe { SelfConversionPolicy::trusted() }, + ClassMethodReceiver::Class, ctx, )?; let methoddef = spec.get_methoddef( @@ -436,6 +439,7 @@ fn impl_call_slot(cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx) -> Result ClassMethodReceiver::Class, + SlotCallingConvention::TpInit | SlotCallingConvention::FixedArguments(_) => { + ClassMethodReceiver::Instance + } + }, + holders, + ctx, + ); let rust_name = spec.name; let warnings = spec.warnings.build_py_warning(ctx); diff --git a/tests/test_methods.rs b/tests/test_methods.rs index c0fec231f36..ee7af17945c 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -123,6 +123,63 @@ fn class_method() { }); } +#[test] +fn class_method_magic_methods() { + #[pyclass(subclass)] + struct ClassMethodMagic; + + #[pymethods] + impl ClassMethodMagic { + #[new] + fn new() -> Self { + Self + } + + #[classmethod] + fn __len__(cls: &Bound<'_, PyType>) -> usize { + if cls.is_exact_instance_of::() { + 42 + } else { + 0 + } + } + + #[classmethod] + fn __call__<'py>(cls: &Bound<'py, PyType>) -> Bound<'py, PyType> { + cls.clone() + } + + #[classmethod] + fn __add__<'py>(cls: &Bound<'py, PyType>, _other: &Bound<'_, PyAny>) -> Bound<'py, PyType> { + cls.clone() + } + } + + Python::attach(|py| { + let cls = py.get_type::(); + py_run!( + py, + cls, + r#" +class Subclass(cls): + pass + +obj = Subclass() +assert len(obj) == 42 +assert obj() is Subclass +assert obj + None is Subclass + +try: + 1 + obj +except TypeError: + pass +else: + raise AssertionError("the forward operator accepted the wrong receiver") +"# + ); + }); +} + #[pyclass] struct ClassMethodWithArgs {} From 3d9068fa0b4cdba2054139291fb942e92d33dd43 Mon Sep 17 00:00:00 2001 From: WaterWhisperer Date: Wed, 5 Aug 2026 14:00:40 +0800 Subject: [PATCH 08/13] fix: skip libpython rpath args on Windows and Cygwin (#6284) --- newsfragments/6284.fixed.md | 1 + pyo3-build-config/src/lib.rs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 newsfragments/6284.fixed.md diff --git a/newsfragments/6284.fixed.md b/newsfragments/6284.fixed.md new file mode 100644 index 00000000000..a6fa55fc1c2 --- /dev/null +++ b/newsfragments/6284.fixed.md @@ -0,0 +1 @@ +Fix `pyo3_build_config::add_libpython_rpath_link_args` emitting Unix-style rpath linker arguments on Windows and Cygwin. diff --git a/pyo3-build-config/src/lib.rs b/pyo3-build-config/src/lib.rs index ce0eb28a588..b39915bad30 100644 --- a/pyo3-build-config/src/lib.rs +++ b/pyo3-build-config/src/lib.rs @@ -296,10 +296,12 @@ pub mod pyo3_build_script_impl { target.architecture, Architecture::Wasm32 | Architecture::Wasm64 ); - let is_emscripten = target.operating_system == target_lexicon::OperatingSystem::Emscripten; + let is_emscripten = target.operating_system == OperatingSystem::Emscripten; + let is_cygwin = target.operating_system == OperatingSystem::Cygwin; + let is_windows = target.operating_system == OperatingSystem::Windows; // webassembly targets generally don't support rpath, emscripten is the only exception currently aware of: // https://github.com/emscripten-core/emscripten/issues/22126 - if is_linking_libpython && (!is_wasm || is_emscripten) { + if is_linking_libpython && !is_windows && !is_cygwin && (!is_wasm || is_emscripten) { if let Some(lib_dir) = interpreter_config.lib_dir() { println!("cargo:rustc-link-arg=-Wl,-rpath,{lib_dir}"); } From 83c620436dac9a4e9a8e84c77280c6a43af1dfc7 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 5 Aug 2026 09:43:59 +0100 Subject: [PATCH 09/13] fix refcount leak in `initialize_tp_dict` (#6297) * fix refcount leak in `initialize_tp_dict` * newsfragment --- newsfragments/6297.fixed.md | 1 + src/impl_/pyclass/lazy_type_object.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 newsfragments/6297.fixed.md diff --git a/newsfragments/6297.fixed.md b/newsfragments/6297.fixed.md new file mode 100644 index 00000000000..edd5add7f7e --- /dev/null +++ b/newsfragments/6297.fixed.md @@ -0,0 +1 @@ +Fix reference count leak of `#[classattr]` values created from `fn` items. diff --git a/src/impl_/pyclass/lazy_type_object.rs b/src/impl_/pyclass/lazy_type_object.rs index 63d92cd10b1..56baec702ae 100644 --- a/src/impl_/pyclass/lazy_type_object.rs +++ b/src/impl_/pyclass/lazy_type_object.rs @@ -244,7 +244,7 @@ fn initialize_tp_dict( // the POV of other threads. for (key, val) in items { crate::err::error_on_minusone(py, unsafe { - ffi::PyObject_SetAttrString(type_object, key.as_ptr(), val.into_ptr()) + ffi::PyObject_SetAttrString(type_object, key.as_ptr(), val.as_ptr()) })?; } Ok(()) From c8a61e12435718309535166f0808f33373de5633 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 5 Aug 2026 11:41:11 +0100 Subject: [PATCH 10/13] fix missing trailing nul on Python 3.9 `#[pyclass]` docstrings (#6296) * fix missing trailing nul on Python 3.9 `#[pyclass]` docstrings * newsfragment * use to_bytes_with_nul Co-authored-by: Lily --------- Co-authored-by: Lily --- newsfragments/6296.fixed.md | 1 + src/pyclass/create_type_object.rs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 newsfragments/6296.fixed.md diff --git a/newsfragments/6296.fixed.md b/newsfragments/6296.fixed.md new file mode 100644 index 00000000000..c36ae6e3a56 --- /dev/null +++ b/newsfragments/6296.fixed.md @@ -0,0 +1 @@ +Fix missing trailing nul in Python 3.9 `#[pyclass]` docstrings. diff --git a/src/pyclass/create_type_object.rs b/src/pyclass/create_type_object.rs index 47a07a2ba85..b01e98cd8a9 100644 --- a/src/pyclass/create_type_object.rs +++ b/src/pyclass/create_type_object.rs @@ -361,8 +361,7 @@ impl PyTypeBuilder { } fn type_doc(mut self, type_doc: &'static CStr) -> Self { - let slice = type_doc.to_bytes(); - if !slice.is_empty() { + if !type_doc.is_empty() { unsafe { self.push_slot(ffi::Py_tp_doc, type_doc.as_ptr() as *mut c_char) } #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))] @@ -374,6 +373,8 @@ impl PyTypeBuilder { self.cleanup .push(Box::new(move |_self, type_object| unsafe { ffi::PyObject_Free((*type_object).tp_doc as _); + + let slice = type_doc.to_bytes_with_nul(); let data = ffi::PyMem_Malloc(slice.len()); data.copy_from(slice.as_ptr() as _, slice.len()); (*type_object).tp_doc = data as _; From 399e52dd227bf0ffa94d07169d9454d2afba9823 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 5 Aug 2026 11:45:48 +0100 Subject: [PATCH 11/13] fix double-decref in PyPy in instance dealloc (#6294) * fix double-decref in PyPy in instance dealloc * newsfragment --- newsfragments/6294.fixed.md | 1 + pytests/src/exception.rs | 17 +++++++++++++++++ pytests/stubs/exception.pyi | 6 +++++- pytests/tests/test_exception.py | 9 +++++++++ src/pycell/impl_.rs | 20 +++++++++++++++++--- 5 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 newsfragments/6294.fixed.md create mode 100644 pytests/tests/test_exception.py diff --git a/newsfragments/6294.fixed.md b/newsfragments/6294.fixed.md new file mode 100644 index 00000000000..7bd94c0a043 --- /dev/null +++ b/newsfragments/6294.fixed.md @@ -0,0 +1 @@ +Fix PyO3 0.29.1 regression on PyPy causing crashes when deallocating `#[pyclass]` instances. diff --git a/pytests/src/exception.rs b/pytests/src/exception.rs index 9347fbb262b..58d882319b1 100644 --- a/pytests/src/exception.rs +++ b/pytests/src/exception.rs @@ -4,6 +4,19 @@ use pyo3::prelude::*; create_exception!(pytests.exception, MyValueError, PyValueError); +#[cfg(any(not(Py_LIMITED_API), Py_3_12))] +#[pyclass(extends = PyValueError)] +pub struct MyValueErrorClass; + +#[cfg(any(not(Py_LIMITED_API), Py_3_12))] +#[pymethods] +impl MyValueErrorClass { + #[new] + fn new() -> PyClassInitializer { + PyClassInitializer::from(MyValueErrorClass) + } +} + #[pymodule(gil_used = false)] pub mod exception { use pyo3::exceptions::PyValueError; @@ -12,6 +25,10 @@ pub mod exception { #[pymodule_export] use super::MyValueError; + #[cfg(any(not(Py_LIMITED_API), Py_3_12))] + #[pymodule_export] + use super::MyValueErrorClass; + #[pyfunction] fn raise_my_value_error() -> PyResult<()> { Err(MyValueError::new_err("error")) diff --git a/pytests/stubs/exception.pyi b/pytests/stubs/exception.pyi index 47e86d914c5..5c41b1904a7 100644 --- a/pytests/stubs/exception.pyi +++ b/pytests/stubs/exception.pyi @@ -1,5 +1,9 @@ from _typeshed import Incomplete -from typing import Any +from typing import Any, final + +@final +class MyValueErrorClass(ValueError): + def __new__(cls, /) -> MyValueErrorClass: ... def raise_my_value_error() -> None: ... def return_my_value_error() -> Any: ... diff --git a/pytests/tests/test_exception.py b/pytests/tests/test_exception.py new file mode 100644 index 00000000000..843d5ee5539 --- /dev/null +++ b/pytests/tests/test_exception.py @@ -0,0 +1,9 @@ +from pyo3_pytests import exception + + +def test_pypy_exception_dealloc(): + # See https://github.com/pypy/pypy/issues/5555 + # - we had an issue caused by https://github.com/PyO3/pyo3/pull/6224 + for _ in range(10_000): + instance = exception.MyValueErrorClass() + del instance diff --git a/src/pycell/impl_.rs b/src/pycell/impl_.rs index a5b13017c37..27cd1b94e33 100644 --- a/src/pycell/impl_.rs +++ b/src/pycell/impl_.rs @@ -14,7 +14,7 @@ use crate::internal::get_slot::{TP_DEALLOC, TP_FREE}; use crate::sync::PyOnceLock; use crate::type_object::{PyLayout, PySizedLayout, PyTypeInfo}; use crate::types::PyType; -use crate::{ffi, Bound, PyClass, Python}; +use crate::{ffi, PyClass, Python}; use crate::types::PyTypeMethods; @@ -270,8 +270,18 @@ unsafe fn tp_dealloc(slf: *mut ffi::PyObject, type_obj: &crate::Bound<'_, PyType // as if it was an owned pointer. In this way, when the bound is dropped, // it will decref the type object. debug_assert!(ffi::PyType_HasFeature(actual_type_ptr, ffi::Py_TPFLAGS_HEAPTYPE) != 0); - let actual_type = Bound::from_owned_ptr(py, actual_type_ptr as *mut ffi::PyObject) - .cast_into_unchecked::(); + let actual_type = cfg_select! { + not(PyPy) => crate::Bound::from_owned_ptr(py, actual_type_ptr as *mut ffi::PyObject) + .cast_into_unchecked::(), + // See https://github.com/pypy/pypy/issues/5555 - it seems that PyPy does not + // support the CPython semantics properly, so we avoid taking ownership of the + // type object on PyPy. + // + // TODO: If the PyPy bug is fixed we should remove this workaround and just create + // a `Bound` as above. + PyPy => crate::Borrowed::from_ptr(py, actual_type_ptr as *mut ffi::PyObject) + .cast_unchecked::(), + }; // For `#[pyclass]` types which inherit from PyAny, we can just call tp_free #[cfg(not(RustPython))] @@ -305,6 +315,10 @@ unsafe fn tp_dealloc(slf: *mut ffi::PyObject, type_obj: &crate::Bound<'_, PyType // Cause the reference to the type to be decrefed for heap types, which // is necessary to avoid a reference leak. + #[cfg_attr( + PyPy, + expect(dropping_copy_types, reason = "see PyPy workaround for decref above") + )] drop(actual_type); } } From 0e608fa3618cefce44f2098642bfb85523da89d2 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 5 Aug 2026 15:25:45 +0100 Subject: [PATCH 12/13] fix backports.zoneinfo for uv install --- .github/workflows/build.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a20ea87a0ed..5e5482361ea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,6 +43,7 @@ jobs: # installs using setup-python do not work for arm macOS 3.9 and below - if: ${{ !(inputs.os == 'macos-latest' && contains(fromJSON('["3.8", "3.9"]'), inputs.python-version) && inputs.python-architecture == 'x64') }} name: Set up Python ${{ inputs.python-version }} + id: setup-python uses: actions/setup-python@v6 with: python-version: ${{ inputs.python-version }} @@ -50,6 +51,13 @@ jobs: # PyPy can have FFI changes within Python versions, which creates pain in CI check-latest: ${{ startsWith(inputs.python-version, 'pypy') }} + - name: Install zoneinfo backport for Python 3.8 + id: zoneinfo-backport + if: inputs.python-version == '3.8' + run: | + python -m pip install backports.zoneinfo + python -c "import os, site; open(os.environ['GITHUB_OUTPUT'], 'a').write(f'pythonpath={site.getsitepackages()[0]}\n')" + - name: Set up uv uses: astral-sh/setup-uv@v7 with: @@ -74,10 +82,6 @@ jobs: run: | echo "CARGO_BUILD_TARGET=i686-pc-windows-msvc" >> $GITHUB_ENV - - name: Install zoneinfo backport for Python 3.8 - if: contains(fromJSON('["3.8"]'), inputs.python-version) - run: python -m pip install backports.zoneinfo - - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} @@ -120,6 +124,9 @@ jobs: - name: Run Rust tests run: uvx nox -s test-rust + env: + PYO3_PYTHON: ${{ steps.setup-python.outputs.python-path || 'python' }} + PYTHONPATH: ${{ steps.zoneinfo-backport.outputs.pythonpath }} - name: Test python examples and tests shell: bash From f76da5c88bf164d865cc0aad40282794aad53ca7 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 5 Aug 2026 13:36:09 +0100 Subject: [PATCH 13/13] release: 0.29.2 --- CHANGELOG.md | 19 ++++++++++++++++++- 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 +- guide/src/building-and-distribution.md | 4 ++-- newsfragments/6185.fixed.md | 1 - newsfragments/6185.packaging.md | 1 - newsfragments/6276.fixed.md | 1 - newsfragments/6283.fixed.md | 1 - newsfragments/6284.fixed.md | 1 - newsfragments/6294.fixed.md | 1 - newsfragments/6296.fixed.md | 1 - newsfragments/6297.fixed.md | 1 - pyo3-build-config/Cargo.toml | 2 +- pyo3-ffi/Cargo.toml | 4 ++-- pyo3-ffi/README.md | 4 ++-- pyo3-introspection/Cargo.toml | 2 +- pyo3-macros-backend/Cargo.toml | 2 +- pyo3-macros/Cargo.toml | 4 ++-- pyproject.toml | 2 +- tests/ui/base/Cargo.toml | 2 +- tests/ui/reject_generics.stderr | 4 ++-- 26 files changed, 44 insertions(+), 35 deletions(-) delete mode 100644 newsfragments/6185.fixed.md delete mode 100644 newsfragments/6185.packaging.md delete mode 100644 newsfragments/6276.fixed.md delete mode 100644 newsfragments/6283.fixed.md delete mode 100644 newsfragments/6284.fixed.md delete mode 100644 newsfragments/6294.fixed.md delete mode 100644 newsfragments/6296.fixed.md delete mode 100644 newsfragments/6297.fixed.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e688ead935..9c69a4f7119 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,22 @@ To see unreleased changes, please see the [CHANGELOG on the main branch guide](h +## [0.29.2] - 2026-08-05 + +### Packaging + +- Add `PYO3_USE_RAW_DYLIB=0` opt-out of `raw-dylib` linking for Windows. [#6185](https://github.com/PyO3/pyo3/pull/6185) + +### Fixed + +- Fix PyO3 0.29 regression with failure to link under Cygwin / MSYS2. [#6185](https://github.com/PyO3/pyo3/pull/6185) +- Fix stubs generation for field getters (`#[pyo3(get)]`) when `IntoPyObject` is only implemented on references of the field type. [#6276](https://github.com/PyO3/pyo3/pull/6276) +- Fix `#[classmethod]` magic methods receiving the instance instead of its type when invoked through a type slot. [#6283](https://github.com/PyO3/pyo3/pull/6283) +- Fix `pyo3_build_config::add_libpython_rpath_link_args` emitting Unix-style rpath linker arguments on Windows and Cygwin. [#6284](https://github.com/PyO3/pyo3/pull/6284) +- Fix PyO3 0.29.1 regression on PyPy causing crashes when deallocating `#[pyclass]` instances. [#6294](https://github.com/PyO3/pyo3/pull/6294) +- Fix missing trailing nul in Python 3.9 `#[pyclass]` docstrings. [#6296](https://github.com/PyO3/pyo3/pull/6296) +- Fix reference count leak of `#[classattr]` values created from `fn` items. [#6297](https://github.com/PyO3/pyo3/pull/6297) + ## [0.29.1] - 2026-08-02 ### Changed @@ -2682,7 +2698,8 @@ Yanked - Initial release -[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.29.1...HEAD +[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.29.2...HEAD +[0.29.1]: https://github.com/pyo3/pyo3/compare/v0.29.1...v0.29.2 [0.29.1]: https://github.com/pyo3/pyo3/compare/v0.29.0...v0.29.1 [0.29.0]: https://github.com/pyo3/pyo3/compare/v0.28.3...v0.29.0 [0.28.3]: https://github.com/pyo3/pyo3/compare/v0.28.2...v0.28.3 diff --git a/Cargo.toml b/Cargo.toml index df0759f60e2..3f0f05432e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3" -version = "0.29.1" +version = "0.29.2" description = "Bindings to Python interpreter" authors = ["PyO3 Project and Contributors "] readme = "README.md" @@ -36,10 +36,10 @@ libc = "0.2.62" once_cell = "1.21" # ffi bindings to the python interpreter, split into a separate crate so they can be used independently -pyo3-ffi = { path = "pyo3-ffi", version = "=0.29.1" } +pyo3-ffi = { path = "pyo3-ffi", version = "=0.29.2" } # support crate for macros feature -pyo3-macros = { path = "pyo3-macros", version = "=0.29.1", optional = true } +pyo3-macros = { path = "pyo3-macros", version = "=0.29.2", optional = true } # support crate for multiple-pymethods feature inventory = { version = "0.3.5", optional = true } @@ -94,7 +94,7 @@ regex = "1.12.3" ctrlc = "3.5.2" [build-dependencies] -pyo3-build-config = { path = "pyo3-build-config", version = "=0.29.1" } +pyo3-build-config = { path = "pyo3-build-config", version = "=0.29.2" } [features] default = ["macros"] diff --git a/README.md b/README.md index c0b59450f39..c4b6e39544b 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ name = "string_sum" crate-type = ["cdylib"] [dependencies] -pyo3 = "0.29.1" +pyo3 = "0.29.2" ``` **`src/lib.rs`** @@ -139,7 +139,7 @@ Start a new project with `cargo new` and add `pyo3` to the `Cargo.toml` like th ```toml [dependencies.pyo3] -version = "0.29.1" +version = "0.29.2" # Enabling this cargo feature will cause PyO3 to start a Python interpreter on first call to `Python::attach` features = ["auto-initialize"] ``` diff --git a/examples/decorator/.template/pre-script.rhai b/examples/decorator/.template/pre-script.rhai index e7a86412b03..76ae8c8c984 100644 --- a/examples/decorator/.template/pre-script.rhai +++ b/examples/decorator/.template/pre-script.rhai @@ -1,4 +1,4 @@ -variable::set("PYO3_VERSION", "0.29.1"); +variable::set("PYO3_VERSION", "0.29.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 e7a86412b03..76ae8c8c984 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.29.1"); +variable::set("PYO3_VERSION", "0.29.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 b83111fc0c3..1adfabc600a 100644 --- a/examples/plugin/.template/pre-script.rhai +++ b/examples/plugin/.template/pre-script.rhai @@ -1,4 +1,4 @@ -variable::set("PYO3_VERSION", "0.29.1"); +variable::set("PYO3_VERSION", "0.29.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 692d05408c7..bdbddf6b757 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.29.1"); +variable::set("PYO3_VERSION", "0.29.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 e7a86412b03..76ae8c8c984 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.29.1"); +variable::set("PYO3_VERSION", "0.29.2"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/pyproject.toml", "pyproject.toml"); file::delete(".template"); diff --git a/guide/src/building-and-distribution.md b/guide/src/building-and-distribution.md index 78b33fb7e6d..809c4fc9e40 100644 --- a/guide/src/building-and-distribution.md +++ b/guide/src/building-and-distribution.md @@ -28,8 +28,8 @@ An example output of doing this is shown below: ```console $ PYO3_PRINT_CONFIG=1 cargo build - Compiling pyo3-ffi v0.29.1 (/Users/goldbaum/Documents/pyo3/pyo3-ffi) -error: failed to run custom build command for `pyo3-ffi v0.29.1 (/Users/goldbaum/Documents/pyo3/pyo3-ffi)` + Compiling pyo3-ffi v0.29.2 (/Users/goldbaum/Documents/pyo3/pyo3-ffi) +error: failed to run custom build command for `pyo3-ffi v0.29.2 (/Users/goldbaum/Documents/pyo3/pyo3-ffi)` Caused by: process didn't exit successfully: `/Users/goldbaum/Documents/pyo3/target/debug/build/pyo3-ffi-71f0882ba738a1f0/build-script-build` (exit status: 101) diff --git a/newsfragments/6185.fixed.md b/newsfragments/6185.fixed.md deleted file mode 100644 index 2666d7806e7..00000000000 --- a/newsfragments/6185.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix PyO3 0.29 regression with failure to link under Cygwin / MSYS2. diff --git a/newsfragments/6185.packaging.md b/newsfragments/6185.packaging.md deleted file mode 100644 index a064fa03aec..00000000000 --- a/newsfragments/6185.packaging.md +++ /dev/null @@ -1 +0,0 @@ -Add `PYO3_USE_RAW_DYLIB=0` opt-out of `raw-dylib` linking for Windows. diff --git a/newsfragments/6276.fixed.md b/newsfragments/6276.fixed.md deleted file mode 100644 index 9243663afed..00000000000 --- a/newsfragments/6276.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix stubs generation for field getters (`#[pyo3(get)]`) when `IntoPyObject` is only implemented on references of the field type \ No newline at end of file diff --git a/newsfragments/6283.fixed.md b/newsfragments/6283.fixed.md deleted file mode 100644 index 66a372d899d..00000000000 --- a/newsfragments/6283.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix `#[classmethod]` magic methods receiving the instance instead of its type when invoked through a type slot. diff --git a/newsfragments/6284.fixed.md b/newsfragments/6284.fixed.md deleted file mode 100644 index a6fa55fc1c2..00000000000 --- a/newsfragments/6284.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix `pyo3_build_config::add_libpython_rpath_link_args` emitting Unix-style rpath linker arguments on Windows and Cygwin. diff --git a/newsfragments/6294.fixed.md b/newsfragments/6294.fixed.md deleted file mode 100644 index 7bd94c0a043..00000000000 --- a/newsfragments/6294.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix PyO3 0.29.1 regression on PyPy causing crashes when deallocating `#[pyclass]` instances. diff --git a/newsfragments/6296.fixed.md b/newsfragments/6296.fixed.md deleted file mode 100644 index c36ae6e3a56..00000000000 --- a/newsfragments/6296.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix missing trailing nul in Python 3.9 `#[pyclass]` docstrings. diff --git a/newsfragments/6297.fixed.md b/newsfragments/6297.fixed.md deleted file mode 100644 index edd5add7f7e..00000000000 --- a/newsfragments/6297.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix reference count leak of `#[classattr]` values created from `fn` items. diff --git a/pyo3-build-config/Cargo.toml b/pyo3-build-config/Cargo.toml index 9d196a46536..82589143dfc 100644 --- a/pyo3-build-config/Cargo.toml +++ b/pyo3-build-config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-build-config" -version = "0.29.1" +version = "0.29.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 057fc65216e..8fc25ab766e 100644 --- a/pyo3-ffi/Cargo.toml +++ b/pyo3-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-ffi" -version = "0.29.1" +version = "0.29.2" description = "Python-API bindings for the PyO3 ecosystem" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] @@ -45,7 +45,7 @@ generate-import-lib = ["pyo3-build-config/generate-import-lib"] paste = "1" [build-dependencies] -pyo3-build-config = { path = "../pyo3-build-config", version = "=0.29.1" } +pyo3-build-config = { path = "../pyo3-build-config", version = "=0.29.2" } [lints] workspace = true diff --git a/pyo3-ffi/README.md b/pyo3-ffi/README.md index 72b59cb6b97..8962c3ac32f 100644 --- a/pyo3-ffi/README.md +++ b/pyo3-ffi/README.md @@ -41,12 +41,12 @@ name = "string_sum" crate-type = ["cdylib"] [dependencies] -pyo3-ffi = "0.29.1" +pyo3-ffi = "0.29.2" [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.29.1" +pyo3_build_config = "0.29.2" ``` If you need to use conditional compilation based on Python version or how diff --git a/pyo3-introspection/Cargo.toml b/pyo3-introspection/Cargo.toml index 3d8f363b4fa..c6bfb618e54 100644 --- a/pyo3-introspection/Cargo.toml +++ b/pyo3-introspection/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-introspection" -version = "0.29.1" +version = "0.29.2" description = "Introspect dynamic libraries built with PyO3 to get metadata about the exported Python types" authors = ["PyO3 Project and Contributors "] homepage = "https://github.com/pyo3/pyo3" diff --git a/pyo3-macros-backend/Cargo.toml b/pyo3-macros-backend/Cargo.toml index 35de7109471..3d4b66a1bfe 100644 --- a/pyo3-macros-backend/Cargo.toml +++ b/pyo3-macros-backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-macros-backend" -version = "0.29.1" +version = "0.29.2" description = "Code generation for PyO3 package" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] diff --git a/pyo3-macros/Cargo.toml b/pyo3-macros/Cargo.toml index 1aef98e6717..bbafe620015 100644 --- a/pyo3-macros/Cargo.toml +++ b/pyo3-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-macros" -version = "0.29.1" +version = "0.29.2" description = "Proc macros for PyO3 package" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] @@ -23,7 +23,7 @@ experimental-inspect = ["pyo3-macros-backend/experimental-inspect"] 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.29.1" } +pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.29.2" } [lints] workspace = true diff --git a/pyproject.toml b/pyproject.toml index 09ec325199d..fca94abdfe7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ dynamic = ["version"] [tool.towncrier] filename = "CHANGELOG.md" -version = "0.29.1" +version = "0.29.2" start_string = "\n" template = ".towncrier.template.md" title_format = "## [{version}] - {project_date}" diff --git a/tests/ui/base/Cargo.toml b/tests/ui/base/Cargo.toml index 6658ab67c38..0077c74f9cd 100644 --- a/tests/ui/base/Cargo.toml +++ b/tests/ui/base/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -pyo3 = { version = "0.29.1", default-features = false, path = "../../../" } +pyo3 = { version = "0.29.2", default-features = false, path = "../../../" } [features] macros = ["pyo3/macros"] diff --git a/tests/ui/reject_generics.stderr b/tests/ui/reject_generics.stderr index dda2cc0ef2d..b804326d86a 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.29.1/class.html#no-generic-parameters +error: #[pyclass] cannot have generic parameters. For an explanation, see https://pyo3.rs/v0.29.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.29.1/class.html#no-lifetime-parameters +error: #[pyclass] cannot have lifetime parameters. For an explanation, see https://pyo3.rs/v0.29.2/class.html#no-lifetime-parameters --> tests/ui/reject_generics.rs:10:27 | 10 | struct ClassWithLifetimes<'a> {