From 3713834a4c3a94a827a881331b09f9df01328d8d Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Sun, 7 Jul 2024 07:53:43 +0100 Subject: [PATCH 01/14] simplify implementation of `Py::clone_ref` (#4313) --- src/instance.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/instance.rs b/src/instance.rs index 4703dd12a94..cc1ae684e44 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1329,8 +1329,11 @@ impl Py { /// # } /// ``` #[inline] - pub fn clone_ref(&self, py: Python<'_>) -> Py { - unsafe { Py::from_borrowed_ptr(py, self.0.as_ptr()) } + pub fn clone_ref(&self, _py: Python<'_>) -> Py { + unsafe { + ffi::Py_INCREF(self.as_ptr()); + Self::from_non_null(self.0) + } } /// Drops `self` and immediately decreases its reference count. From 380b2ac59a85f2d267ec6f019468c78a34f85c39 Mon Sep 17 00:00:00 2001 From: Sede Soukossi <4968379+styvane@users.noreply.github.com> Date: Mon, 8 Jul 2024 22:30:44 +0200 Subject: [PATCH 02/14] docs: Fix mismatch return value, remove redundant error propagation, and additional fixes (#4318) * Fix mismatch return value, remove redundant error propagation, and fix typo * Update guide/src/function/signature.md Co-authored-by: Lily Foote --------- Co-authored-by: Lily Foote --- guide/src/ecosystem/async-await.md | 9 +++------ guide/src/function.md | 12 ++++-------- guide/src/function/signature.md | 3 +-- guide/src/getting-started.md | 3 +-- guide/src/module.md | 12 ++++-------- 5 files changed, 13 insertions(+), 26 deletions(-) diff --git a/guide/src/ecosystem/async-await.md b/guide/src/ecosystem/async-await.md index 9c0ad19bdef..0128efbc8a3 100644 --- a/guide/src/ecosystem/async-await.md +++ b/guide/src/ecosystem/async-await.md @@ -129,9 +129,7 @@ fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>> { #[pymodule] fn my_async_module(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(rust_sleep, m)?)?; - - Ok(()) + m.add_function(wrap_pyfunction!(rust_sleep, m)?) } ``` @@ -152,8 +150,7 @@ fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>>> { #[pymodule] fn my_async_module(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(rust_sleep, m)?)?; - Ok(()) + m.add_function(wrap_pyfunction!(rust_sleep, m)?) } ``` @@ -257,7 +254,7 @@ async def py_sleep(): await_coro(py_sleep()) ``` -If for you wanted to pass a callable function to the `#[pyfunction]` instead, (i.e. the last line becomes `await_coro(py_sleep))`, then the above example needs to be tweaked to first call the callable to get the coroutine: +If you wanted to pass a callable function to the `#[pyfunction]` instead, (i.e. the last line becomes `await_coro(py_sleep))`, then the above example needs to be tweaked to first call the callable to get the coroutine: ```rust #[pyfunction] diff --git a/guide/src/function.md b/guide/src/function.md index 86ac4c89b46..5e6cfaba773 100644 --- a/guide/src/function.md +++ b/guide/src/function.md @@ -14,8 +14,7 @@ fn double(x: usize) -> usize { #[pymodule] fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(double, m)?)?; - Ok(()) + m.add_function(wrap_pyfunction!(double, m)?) } ``` @@ -56,8 +55,7 @@ The `#[pyo3]` attribute can be used to modify properties of the generated Python #[pymodule] fn module_with_functions(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(no_args_py, m)?)?; - Ok(()) + m.add_function(wrap_pyfunction!(no_args_py, m)?) } # Python::with_gil(|py| { @@ -113,8 +111,7 @@ The `#[pyo3]` attribute can be used on individual arguments to modify properties use pyo3::prelude::*; fn get_length(obj: &Bound<'_, PyAny>) -> PyResult { - let length = obj.len()?; - Ok(length) + obj.len() } #[pyfunction] @@ -204,8 +201,7 @@ fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> { x * 2 } - m.add_function(wrap_pyfunction!(double, m)?)?; - Ok(()) + m.add_function(wrap_pyfunction!(double, m)?) } ``` diff --git a/guide/src/function/signature.md b/guide/src/function/signature.md index 69949220be6..a9be5983422 100644 --- a/guide/src/function/signature.md +++ b/guide/src/function/signature.md @@ -22,8 +22,7 @@ fn num_kwds(kwds: Option<&Bound<'_, PyDict>>) -> usize { #[pymodule] fn module_with_functions(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(num_kwds, m)?).unwrap(); - Ok(()) + m.add_function(wrap_pyfunction!(num_kwds, m)?) } ``` diff --git a/guide/src/getting-started.md b/guide/src/getting-started.md index ede48d50c33..e2cc040bbd7 100644 --- a/guide/src/getting-started.md +++ b/guide/src/getting-started.md @@ -159,8 +159,7 @@ fn sum_as_string(a: usize, b: usize) -> PyResult { /// import the module. #[pymodule] fn pyo3_example(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; - Ok(()) + m.add_function(wrap_pyfunction!(sum_as_string, m)?) } ``` diff --git a/guide/src/module.md b/guide/src/module.md index 2c4039a6e76..a2cb8b37a05 100644 --- a/guide/src/module.md +++ b/guide/src/module.md @@ -13,8 +13,7 @@ fn double(x: usize) -> usize { /// This module is implemented in Rust. #[pymodule] fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(double, m)?)?; - Ok(()) + m.add_function(wrap_pyfunction!(double, m)?) } ``` @@ -35,8 +34,7 @@ fn double(x: usize) -> usize { #[pymodule] #[pyo3(name = "custom_name")] fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(double, m)?)?; - Ok(()) + m.add_function(wrap_pyfunction!(double, m)?) } ``` @@ -80,8 +78,7 @@ fn parent_module(m: &Bound<'_, PyModule>) -> PyResult<()> { fn register_child_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()> { let child_module = PyModule::new_bound(parent_module.py(), "child_module")?; child_module.add_function(wrap_pyfunction!(func, &child_module)?)?; - parent_module.add_submodule(&child_module)?; - Ok(()) + parent_module.add_submodule(&child_module) } #[pyfunction] @@ -143,8 +140,7 @@ mod my_extension { #[pymodule_init] fn init(m: &Bound<'_, PyModule>) -> PyResult<()> { // Arbitrary code to run at the module initialization - m.add("double2", m.getattr("double")?)?; - Ok(()) + m.add("double2", m.getattr("double")?) } } # } From dd4373fb077a1045b51ecadce3f16bd97b8561fd Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 9 Jul 2024 12:53:11 +0100 Subject: [PATCH 03/14] docs: improve signposting to bound and traits (#4325) * docs: improve signposting to bound and traits * update UI tests --- src/instance.rs | 14 +++++++++++++- src/lib.rs | 5 ++++- src/types/any.rs | 25 +++++++------------------ src/types/boolobject.rs | 6 ++++++ src/types/bytearray.rs | 6 ++++++ src/types/bytes.rs | 10 ++++++++-- src/types/capsule.rs | 5 +++++ src/types/code.rs | 3 +++ src/types/complex.rs | 6 ++++++ src/types/datetime.rs | 23 +++++++++++++++++++---- src/types/dict.rs | 6 ++++++ src/types/ellipsis.rs | 3 +++ src/types/float.rs | 10 ++++++++-- src/types/frame.rs | 3 +++ src/types/frozenset.rs | 8 +++++++- src/types/function.rs | 6 ++++++ src/types/iterator.rs | 3 +++ src/types/list.rs | 6 ++++++ src/types/mapping.rs | 6 ++++++ src/types/memoryview.rs | 3 +++ src/types/module.rs | 6 ++++++ src/types/none.rs | 3 +++ src/types/notimplemented.rs | 3 +++ src/types/num.rs | 3 +++ src/types/pysuper.rs | 3 ++- src/types/sequence.rs | 6 ++++++ src/types/set.rs | 8 +++++++- src/types/slice.rs | 6 ++++++ src/types/string.rs | 7 ++++--- src/types/traceback.rs | 6 ++++++ src/types/tuple.rs | 6 +++++- src/types/typeobject.rs | 9 ++++++++- tests/ui/invalid_pyfunctions.stderr | 6 +++--- 33 files changed, 190 insertions(+), 39 deletions(-) diff --git a/src/instance.rs b/src/instance.rs index cc1ae684e44..499f751027c 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -65,7 +65,19 @@ pub unsafe trait PyNativeType: Sized { } } -/// A GIL-attached equivalent to `Py`. +/// A GIL-attached equivalent to [`Py`]. +/// +/// This type can be thought of as equivalent to the tuple `(Py, Python<'py>)`. By having the `'py` +/// lifetime of the [`Python<'py>`] token, this ties the lifetime of the [`Bound<'py, T>`] smart pointer +/// to the lifetime of the GIL and allows PyO3 to call Python APIs at maximum efficiency. +/// +/// To access the object in situations where the GIL is not held, convert it to [`Py`] +/// using [`.unbind()`][Bound::unbind]. This includes situations where the GIL is temporarily +/// released, such as [`Python::allow_threads`](crate::Python::allow_threads)'s closure. +/// +/// See +#[doc = concat!("[the guide](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/types.html#boundpy-t)")] +/// for more detail. #[repr(transparent)] pub struct Bound<'py, T>(Python<'py>, ManuallyDrop>); diff --git a/src/lib.rs b/src/lib.rs index a9c5bd0b731..ef905ec356c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -461,7 +461,6 @@ pub mod marshal; #[macro_use] pub mod sync; pub mod panic; -pub mod prelude; pub mod pybacked; pub mod pycell; pub mod pyclass; @@ -495,6 +494,10 @@ mod macros; #[cfg(feature = "experimental-inspect")] pub mod inspect; +// Putting the declaration of prelude at the end seems to help encourage rustc and rustdoc to prefer using +// other paths to the same items. (e.g. `pyo3::types::PyAnyMethods` instead of `pyo3::prelude::PyAnyMethods`). +pub mod prelude; + /// Ths module only contains re-exports of pyo3 deprecation warnings and exists /// purely to make compiler error messages nicer. /// diff --git a/src/types/any.rs b/src/types/any.rs index c8c6d67e534..fdeab37712d 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -19,26 +19,15 @@ use std::os::raw::c_int; /// Represents any Python object. /// -/// It currently only appears as a *reference*, `&PyAny`, -/// with a lifetime that represents the scope during which the GIL is held. +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyAny>`][Bound]. /// -/// `PyAny` has some interesting properties, which it shares -/// with the other [native Python types](crate::types): +/// For APIs available on all Python objects, see the [`PyAnyMethods`] trait which is implemented for +/// [`Bound<'py, PyAny>`][Bound]. /// -/// - It can only be obtained and used while the GIL is held, -/// therefore its API does not require a [`Python<'py>`](crate::Python) token. -/// - It can't be used in situations where the GIL is temporarily released, -/// such as [`Python::allow_threads`](crate::Python::allow_threads)'s closure. -/// - The underlying Python object, if mutable, can be mutated through any reference. -/// - It can be converted to the GIL-independent [`Py`]`<`[`PyAny`]`>`, -/// allowing it to outlive the GIL scope. However, using [`Py`]`<`[`PyAny`]`>`'s API -/// *does* require a [`Python<'py>`](crate::Python) token. -/// -/// It can be cast to a concrete type with PyAny::downcast (for native Python types only) -/// and FromPyObject::extract. See their documentation for more information. -/// -/// See [the guide](https://pyo3.rs/latest/types.html) for an explanation -/// of the different Python object types. +/// See +#[doc = concat!("[the guide](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/types.html#concrete-python-types)")] +/// for an explanation of the different Python object types. #[repr(transparent)] pub struct PyAny(UnsafeCell); diff --git a/src/types/boolobject.rs b/src/types/boolobject.rs index ee19797d66e..9ac66529e9a 100644 --- a/src/types/boolobject.rs +++ b/src/types/boolobject.rs @@ -11,6 +11,12 @@ use crate::{ use super::any::PyAnyMethods; /// Represents a Python `bool`. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyBool>`][Bound]. +/// +/// For APIs available on `bool` objects, see the [`PyBoolMethods`] trait which is implemented for +/// [`Bound<'py, PyBool>`][Bound]. #[repr(transparent)] pub struct PyBool(PyAny); diff --git a/src/types/bytearray.rs b/src/types/bytearray.rs index c411e830340..57376069355 100644 --- a/src/types/bytearray.rs +++ b/src/types/bytearray.rs @@ -9,6 +9,12 @@ use crate::{AsPyPointer, PyNativeType}; use std::slice; /// Represents a Python `bytearray`. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyByteArray>`][Bound]. +/// +/// For APIs available on `bytearray` objects, see the [`PyByteArrayMethods`] trait which is implemented for +/// [`Bound<'py, PyByteArray>`][Bound]. #[repr(transparent)] pub struct PyByteArray(PyAny); diff --git a/src/types/bytes.rs b/src/types/bytes.rs index 0513f4cec8c..512c835f87a 100644 --- a/src/types/bytes.rs +++ b/src/types/bytes.rs @@ -12,10 +12,16 @@ use std::str; /// /// This type is immutable. /// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyBytes>`][Bound]. +/// +/// For APIs available on `bytes` objects, see the [`PyBytesMethods`] trait which is implemented for +/// [`Bound<'py, PyBytes>`][Bound]. +/// /// # Equality /// -/// For convenience, [`Bound<'py, PyBytes>`] implements [`PartialEq<[u8]>`] to allow comparing the -/// data in the Python bytes to a Rust `[u8]`. +/// For convenience, [`Bound<'py, PyBytes>`][Bound] implements [`PartialEq<[u8]>`][PartialEq] to allow comparing the +/// data in the Python bytes to a Rust `[u8]` byte slice. /// /// This is not always the most appropriate way to compare Python bytes, as Python bytes subclasses /// may have different equality semantics. In situations where subclasses overriding equality might be diff --git a/src/types/capsule.rs b/src/types/capsule.rs index 9b9445cdffe..815b70ebc41 100644 --- a/src/types/capsule.rs +++ b/src/types/capsule.rs @@ -15,6 +15,11 @@ use std::os::raw::{c_char, c_int, c_void}; /// > in one module available to other modules, so the regular import mechanism can /// > be used to access C APIs defined in dynamically loaded modules. /// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyCapsule>`][Bound]. +/// +/// For APIs available on capsule objects, see the [`PyCapsuleMethods`] trait which is implemented for +/// [`Bound<'py, PyCapsule>`][Bound]. /// /// # Example /// ``` diff --git a/src/types/code.rs b/src/types/code.rs index f60e7783aa4..04e1efb9fe7 100644 --- a/src/types/code.rs +++ b/src/types/code.rs @@ -2,6 +2,9 @@ use crate::ffi; use crate::PyAny; /// Represents a Python code object. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyCode>`][crate::Bound]. #[repr(transparent)] pub struct PyCode(PyAny); diff --git a/src/types/complex.rs b/src/types/complex.rs index 5ec9e2f00a4..887bc12e438 100644 --- a/src/types/complex.rs +++ b/src/types/complex.rs @@ -7,6 +7,12 @@ use std::os::raw::c_double; /// Represents a Python [`complex`](https://docs.python.org/3/library/functions.html#complex) object. /// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyComplex>`][Bound]. +/// +/// For APIs available on `complex` objects, see the [`PyComplexMethods`] trait which is implemented for +/// [`Bound<'py, PyComplex>`][Bound]. +/// /// Note that `PyComplex` supports only basic operations. For advanced operations /// consider using [num-complex](https://docs.rs/num-complex)'s [`Complex`] type instead. /// This optional dependency can be activated with the `num-complex` feature flag. diff --git a/src/types/datetime.rs b/src/types/datetime.rs index cdf3b011e6c..c07b6be9b2b 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -191,7 +191,10 @@ pub trait PyTzInfoAccess<'py> { fn get_tzinfo_bound(&self) -> Option>; } -/// Bindings around `datetime.date` +/// Bindings around `datetime.date`. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyDate>`][Bound]. #[repr(transparent)] pub struct PyDate(PyAny); pyobject_native_type!( @@ -279,7 +282,10 @@ impl PyDateAccess for Bound<'_, PyDate> { } } -/// Bindings for `datetime.datetime` +/// Bindings for `datetime.datetime`. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyDateTime>`][Bound]. #[repr(transparent)] pub struct PyDateTime(PyAny); pyobject_native_type!( @@ -578,7 +584,10 @@ impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyDateTime> { } } -/// Bindings for `datetime.time` +/// Bindings for `datetime.time`. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyTime>`][Bound]. #[repr(transparent)] pub struct PyTime(PyAny); pyobject_native_type!( @@ -781,6 +790,9 @@ impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyTime> { /// Bindings for `datetime.tzinfo`. /// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyTzInfo>`][Bound]. +/// /// This is an abstract base class and cannot be constructed directly. /// For concrete time zone implementations, see [`timezone_utc_bound`] and /// the [`zoneinfo` module](https://docs.python.org/3/library/zoneinfo.html). @@ -834,7 +846,10 @@ pub(crate) fn timezone_from_offset<'py>( } } -/// Bindings for `datetime.timedelta` +/// Bindings for `datetime.timedelta`. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyDelta>`][Bound]. #[repr(transparent)] pub struct PyDelta(PyAny); pyobject_native_type!( diff --git a/src/types/dict.rs b/src/types/dict.rs index 850e468c672..50d00477c2e 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -11,6 +11,12 @@ use crate::PyNativeType; use crate::{ffi, Python, ToPyObject}; /// Represents a Python `dict`. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyDict>`][Bound]. +/// +/// For APIs available on `dict` objects, see the [`PyDictMethods`] trait which is implemented for +/// [`Bound<'py, PyDict>`][Bound]. #[repr(transparent)] pub struct PyDict(PyAny); diff --git a/src/types/ellipsis.rs b/src/types/ellipsis.rs index cbeaf489c17..1dc7da08b55 100644 --- a/src/types/ellipsis.rs +++ b/src/types/ellipsis.rs @@ -4,6 +4,9 @@ use crate::{ }; /// Represents the Python `Ellipsis` object. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyEllipsis>`][Bound]. #[repr(transparent)] pub struct PyEllipsis(PyAny); diff --git a/src/types/float.rs b/src/types/float.rs index 8499d1e54aa..1e6cbe51eaf 100644 --- a/src/types/float.rs +++ b/src/types/float.rs @@ -11,9 +11,15 @@ use std::os::raw::c_double; /// Represents a Python `float` object. /// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyFloat>`][Bound]. +/// +/// For APIs available on `float` objects, see the [`PyFloatMethods`] trait which is implemented for +/// [`Bound<'py, PyFloat>`][Bound]. +/// /// You can usually avoid directly working with this type -/// by using [`ToPyObject`] and [`extract`](PyAnyMethods::extract) -/// with `f32`/`f64`. +/// by using [`ToPyObject`] and [`extract`][PyAnyMethods::extract] +/// with [`f32`]/[`f64`]. #[repr(transparent)] pub struct PyFloat(PyAny); diff --git a/src/types/frame.rs b/src/types/frame.rs index 0ab873b8003..8d88d4754ae 100644 --- a/src/types/frame.rs +++ b/src/types/frame.rs @@ -2,6 +2,9 @@ use crate::ffi; use crate::PyAny; /// Represents a Python frame. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyFrame>`][crate::Bound]. #[repr(transparent)] pub struct PyFrame(PyAny); diff --git a/src/types/frozenset.rs b/src/types/frozenset.rs index 78cbf01df67..4ec83915c70 100644 --- a/src/types/frozenset.rs +++ b/src/types/frozenset.rs @@ -56,7 +56,13 @@ impl<'py> PyFrozenSetBuilder<'py> { } } -/// Represents a Python `frozenset` +/// Represents a Python `frozenset`. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyFrozenSet>`][Bound]. +/// +/// For APIs available on `frozenset` objects, see the [`PyFrozenSetMethods`] trait which is implemented for +/// [`Bound<'py, PyFrozenSet>`][Bound]. #[repr(transparent)] pub struct PyFrozenSet(PyAny); diff --git a/src/types/function.rs b/src/types/function.rs index c2eec04d42f..62a2b30263b 100644 --- a/src/types/function.rs +++ b/src/types/function.rs @@ -16,6 +16,9 @@ use std::cell::UnsafeCell; use std::ffi::CStr; /// Represents a builtin Python function object. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyCFunction>`][Bound]. #[repr(transparent)] pub struct PyCFunction(PyAny); @@ -241,6 +244,9 @@ struct ClosureDestructor { unsafe impl Send for ClosureDestructor {} /// Represents a Python function object. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyFunction>`][Bound]. #[repr(transparent)] #[cfg(all(not(Py_LIMITED_API), not(all(PyPy, not(Py_3_8)))))] pub struct PyFunction(PyAny); diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 1835f484adf..38f3131be90 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -7,6 +7,9 @@ use crate::{AsPyPointer, PyDowncastError, PyNativeType}; /// A Python iterator object. /// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyIterator>`][Bound]. +/// /// # Examples /// /// ```rust diff --git a/src/types/list.rs b/src/types/list.rs index 0d911e03199..9cfd574cd0f 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -14,6 +14,12 @@ use crate::types::any::PyAnyMethods; use crate::types::sequence::PySequenceMethods; /// Represents a Python `list`. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyList>`][Bound]. +/// +/// For APIs available on `list` objects, see the [`PyListMethods`] trait which is implemented for +/// [`Bound<'py, PyDict>`][Bound]. #[repr(transparent)] pub struct PyList(PyAny); diff --git a/src/types/mapping.rs b/src/types/mapping.rs index aea2b484c3b..82e6b326810 100644 --- a/src/types/mapping.rs +++ b/src/types/mapping.rs @@ -11,6 +11,12 @@ use crate::{err::PyDowncastError, PyNativeType}; use crate::{ffi, Py, PyTypeCheck, Python, ToPyObject}; /// Represents a reference to a Python object supporting the mapping protocol. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyMapping>`][Bound]. +/// +/// For APIs available on mapping objects, see the [`PyMappingMethods`] trait which is implemented for +/// [`Bound<'py, PyMapping>`][Bound]. #[repr(transparent)] pub struct PyMapping(PyAny); pyobject_native_type_named!(PyMapping); diff --git a/src/types/memoryview.rs b/src/types/memoryview.rs index 320b3f9f70b..bff1fdcd4c9 100644 --- a/src/types/memoryview.rs +++ b/src/types/memoryview.rs @@ -6,6 +6,9 @@ use crate::{ffi, Bound, PyAny}; use crate::{AsPyPointer, PyNativeType}; /// Represents a Python `memoryview`. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyMemoryView>`][Bound]. #[repr(transparent)] pub struct PyMemoryView(PyAny); diff --git a/src/types/module.rs b/src/types/module.rs index e866ec9cb48..5438c22f681 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -15,6 +15,12 @@ use {super::PyStringMethods, crate::PyNativeType}; /// Represents a Python [`module`][1] object. /// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyModule>`][Bound]. +/// +/// For APIs available on `module` objects, see the [`PyModuleMethods`] trait which is implemented for +/// [`Bound<'py, PyModule>`][Bound]. +/// /// As with all other Python objects, modules are first class citizens. /// This means they can be passed to or returned from functions, /// created dynamically, assigned to variables and so forth. diff --git a/src/types/none.rs b/src/types/none.rs index 0ab1570b92d..78f14be2b25 100644 --- a/src/types/none.rs +++ b/src/types/none.rs @@ -5,6 +5,9 @@ use crate::{ }; /// Represents the Python `None` object. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyNone>`][Bound]. #[repr(transparent)] pub struct PyNone(PyAny); diff --git a/src/types/notimplemented.rs b/src/types/notimplemented.rs index 7fad1220b26..6d1808070a6 100644 --- a/src/types/notimplemented.rs +++ b/src/types/notimplemented.rs @@ -4,6 +4,9 @@ use crate::{ }; /// Represents the Python `NotImplemented` object. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyNotImplemented>`][Bound]. #[repr(transparent)] pub struct PyNotImplemented(PyAny); diff --git a/src/types/num.rs b/src/types/num.rs index 924d4b2c593..b43dddbef88 100644 --- a/src/types/num.rs +++ b/src/types/num.rs @@ -2,6 +2,9 @@ use crate::{ffi, PyAny}; /// Represents a Python `int` object. /// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyLong>`][crate::Bound]. +/// /// You can usually avoid directly working with this type /// by using [`ToPyObject`](crate::conversion::ToPyObject) /// and [`extract`](super::PyAnyMethods::extract) diff --git a/src/types/pysuper.rs b/src/types/pysuper.rs index 7c4d781525a..bd9d042a1f0 100644 --- a/src/types/pysuper.rs +++ b/src/types/pysuper.rs @@ -6,7 +6,8 @@ use crate::{PyAny, PyResult}; /// Represents a Python `super` object. /// -/// This type is immutable. +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PySuper>`][Bound]. #[repr(transparent)] pub struct PySuper(PyAny); diff --git a/src/types/sequence.rs b/src/types/sequence.rs index a5765ebc8b2..39de9efb272 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -14,6 +14,12 @@ use crate::{err::PyDowncastError, PyNativeType}; use crate::{ffi, FromPyObject, Py, PyTypeCheck, Python, ToPyObject}; /// Represents a reference to a Python object supporting the sequence protocol. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PySequence>`][Bound]. +/// +/// For APIs available on sequence objects, see the [`PySequenceMethods`] trait which is implemented for +/// [`Bound<'py, PySequence>`][Bound]. #[repr(transparent)] pub struct PySequence(PyAny); pyobject_native_type_named!(PySequence); diff --git a/src/types/set.rs b/src/types/set.rs index 1bc4c86be51..9dc44745df2 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -11,7 +11,13 @@ use crate::{ use crate::{ffi, PyAny, PyObject, Python, ToPyObject}; use std::ptr; -/// Represents a Python `set` +/// Represents a Python `set`. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PySet>`][Bound]. +/// +/// For APIs available on `set` objects, see the [`PySetMethods`] trait which is implemented for +/// [`Bound<'py, PySet>`][Bound]. #[repr(transparent)] pub struct PySet(PyAny); diff --git a/src/types/slice.rs b/src/types/slice.rs index 7daa2c030b6..dafe5053059 100644 --- a/src/types/slice.rs +++ b/src/types/slice.rs @@ -8,6 +8,12 @@ use crate::{Bound, PyAny, PyObject, Python, ToPyObject}; /// Represents a Python `slice`. /// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PySlice>`][Bound]. +/// +/// For APIs available on `slice` objects, see the [`PySliceMethods`] trait which is implemented for +/// [`Bound<'py, PySlice>`][Bound]. +/// /// Only `isize` indices supported at the moment by the `PySlice` object. #[repr(transparent)] pub struct PySlice(PyAny); diff --git a/src/types/string.rs b/src/types/string.rs index 828e0024bda..fafeac83091 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -123,10 +123,11 @@ impl<'a> PyStringData<'a> { /// Represents a Python `string` (a Unicode string object). /// -/// This type is only seen inside PyO3's smart pointers as [`Py`], [`Bound<'py, PyString>`], -/// and [`Borrowed<'a, 'py, PyString>`]. +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyString>`][Bound]. /// -/// All functionality on this type is implemented through the [`PyStringMethods`] trait. +/// For APIs available on `str` objects, see the [`PyStringMethods`] trait which is implemented for +/// [`Bound<'py, PyString>`][Bound]. /// /// # Equality /// diff --git a/src/types/traceback.rs b/src/types/traceback.rs index dbbdb6a85ea..5e3496145e2 100644 --- a/src/types/traceback.rs +++ b/src/types/traceback.rs @@ -5,6 +5,12 @@ use crate::PyNativeType; use crate::{ffi, Bound, PyAny}; /// Represents a Python traceback. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyTraceback>`][Bound]. +/// +/// For APIs available on traceback objects, see the [`PyTracebackMethods`] trait which is implemented for +/// [`Bound<'py, PyTraceback>`][Bound]. #[repr(transparent)] pub struct PyTraceback(PyAny); diff --git a/src/types/tuple.rs b/src/types/tuple.rs index fcf931c1d8a..aacc1efe431 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -52,7 +52,11 @@ fn new_from_iter<'py>( /// Represents a Python `tuple` object. /// -/// This type is immutable. +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyTuple>`][Bound]. +/// +/// For APIs available on `tuple` objects, see the [`PyTupleMethods`] trait which is implemented for +/// [`Bound<'py, PyTuple>`][Bound]. #[repr(transparent)] pub struct PyTuple(PyAny); diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index 9638a2731a3..dbe4c3efd10 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -9,7 +9,14 @@ use crate::PyNativeType; use crate::{ffi, Bound, PyAny, PyTypeInfo, Python}; use super::PyString; -/// Represents a reference to a Python `type object`. + +/// Represents a reference to a Python `type` object. +/// +/// Values of this type are accessed via PyO3's smart pointers, e.g. as +/// [`Py`][crate::Py] or [`Bound<'py, PyType>`][Bound]. +/// +/// For APIs available on `type` objects, see the [`PyTypeMethods`] trait which is implemented for +/// [`Bound<'py, PyType>`][Bound]. #[repr(transparent)] pub struct PyType(PyAny); diff --git a/tests/ui/invalid_pyfunctions.stderr b/tests/ui/invalid_pyfunctions.stderr index 0f94ef17254..9a42c59366e 100644 --- a/tests/ui/invalid_pyfunctions.stderr +++ b/tests/ui/invalid_pyfunctions.stderr @@ -47,11 +47,11 @@ error: expected `&PyModule` or `Py` as first argument with `pass_modul 32 | fn pass_module_but_no_arguments<'py>() {} | ^^ -error[E0277]: the trait bound `&str: From>` is not satisfied +error[E0277]: the trait bound `&str: From>` is not satisfied --> tests/ui/invalid_pyfunctions.rs:36:14 | 36 | _string: &str, - | ^ the trait `From>` is not implemented for `&str`, which is required by `BoundRef<'_, '_, pyo3::prelude::PyModule>: Into<_>` + | ^ the trait `From>` is not implemented for `&str`, which is required by `BoundRef<'_, '_, pyo3::types::PyModule>: Into<_>` | = help: the following other types implement trait `From`: > @@ -60,4 +60,4 @@ error[E0277]: the trait bound `&str: From>> >> > - = note: required for `BoundRef<'_, '_, pyo3::prelude::PyModule>` to implement `Into<&str>` + = note: required for `BoundRef<'_, '_, pyo3::types::PyModule>` to implement `Into<&str>` From 000243dae1ed07d7e0438e6b2a7892bfea1f5613 Mon Sep 17 00:00:00 2001 From: Giovanni Barillari Date: Wed, 10 Jul 2024 11:16:23 +0200 Subject: [PATCH 04/14] Add Granian to example projects (#4329) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d5be31967a7..fd4acdd4154 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,7 @@ about this topic. - [fastuuid](https://github.com/thedrow/fastuuid/) _Python bindings to Rust's UUID library._ - [feos](https://github.com/feos-org/feos) _Lightning fast thermodynamic modeling in Rust with fully developed Python interface._ - [forust](https://github.com/jinlow/forust) _A lightweight gradient boosted decision tree library written in Rust._ +- [granian](https://github.com/emmett-framework/granian) _A Rust HTTP server for Python applications._ - [greptimedb](https://github.com/GreptimeTeam/greptimedb/tree/main/src/script) _Support [Python scripting](https://docs.greptime.com/user-guide/python-scripts/overview) in the database_ - [haem](https://github.com/BooleanCat/haem) _A Python library for working on Bioinformatics problems._ - [html-py-ever](https://github.com/PyO3/setuptools-rust/tree/main/examples/html-py-ever) _Using [html5ever](https://github.com/servo/html5ever) through [kuchiki](https://github.com/kuchiki-rs/kuchiki) to speed up html parsing and css-selecting._ From 047764c196c4df27594d3e7a59361bc8ad2dfc64 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 10 Jul 2024 12:36:31 +0100 Subject: [PATCH 05/14] ci: check minimal-versions on MSRV feature powerset (#4273) * ci: check minimal-versions on MSRV feature powerset * fix msrv arg * bump num-bigint floor to 0.4.2 * try fix build syntax --- .github/workflows/build.yml | 9 ++++--- .github/workflows/ci.yml | 52 +++++++++++++++++++++++++++---------- Cargo.toml | 8 +++--- noxfile.py | 10 ++++--- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e952592da3a..077ca08cf4e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,9 @@ on: rust-target: required: true type: string + MSRV: + required: true + type: string jobs: build: @@ -51,9 +54,9 @@ jobs: name: Prepare LD_LIBRARY_PATH (Ubuntu only) run: echo LD_LIBRARY_PATH=${pythonLocation}/lib >> $GITHUB_ENV - - if: inputs.rust == '1.63.0' - name: Prepare minimal package versions (MSRV only) - run: nox -s set-minimal-package-versions + - if: inputs.rust == inputs.MSRV + name: Prepare MSRV package versions + run: nox -s set-msrv-package-versions - if: inputs.rust != 'stable' name: Ignore changed error messages when using trybuild diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8379232b7fb..c8f4b5dcc67 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,18 @@ jobs: - name: Check rust formatting (rustfmt) run: nox -s rustfmt + resolve: + runs-on: ubuntu-latest + outputs: + MSRV: ${{ steps.resolve-msrv.outputs.MSRV }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - name: resolve MSRV + id: resolve-msrv + run: + echo MSRV=`python -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["package"]["rust-version"])'` >> $GITHUB_OUTPUT + semver-checks: if: github.ref != 'refs/heads/main' needs: [fmt] @@ -41,13 +53,13 @@ jobs: - uses: obi1kenobi/cargo-semver-checks-action@v2 check-msrv: - needs: [fmt] + needs: [fmt, resolve] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.63.0 + toolchain: ${{ needs.resolve.outputs.MSRV }} targets: x86_64-unknown-linux-gnu components: rust-src - uses: actions/setup-python@v5 @@ -57,9 +69,11 @@ jobs: with: save-if: ${{ github.event_name != 'merge_group' }} - run: python -m pip install --upgrade pip && pip install nox - - name: Prepare minimal package versions - run: nox -s set-minimal-package-versions - - run: nox -s check-all + # 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 env: CARGO_BUILD_TARGET: x86_64-unknown-linux-gnu @@ -141,7 +155,7 @@ jobs: build-pr: if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-build-full') && github.event_name == 'pull_request' }} name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} rust-${{ matrix.rust }} - needs: [fmt] + needs: [fmt, resolve] uses: ./.github/workflows/build.yml with: os: ${{ matrix.platform.os }} @@ -149,6 +163,7 @@ jobs: python-architecture: ${{ matrix.platform.python-architecture }} rust: ${{ matrix.rust }} rust-target: ${{ matrix.platform.rust-target }} + MSRV: ${{ needs.resolve.outputs.MSRV }} secrets: inherit strategy: # If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present @@ -198,7 +213,7 @@ jobs: build-full: if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} rust-${{ matrix.rust }} - needs: [fmt] + needs: [fmt, resolve] uses: ./.github/workflows/build.yml with: os: ${{ matrix.platform.os }} @@ -206,6 +221,7 @@ jobs: python-architecture: ${{ matrix.platform.python-architecture }} rust: ${{ matrix.rust }} rust-target: ${{ matrix.platform.rust-target }} + MSRV: ${{ needs.resolve.outputs.MSRV }} secrets: inherit strategy: # If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present @@ -251,7 +267,7 @@ jobs: ] include: # Test minimal supported Rust version - - rust: 1.63.0 + - rust: ${{ needs.resolve.outputs.MSRV }} python-version: "3.12" platform: { @@ -487,21 +503,31 @@ jobs: - run: python3 -m nox -s test-version-limits check-feature-powerset: - needs: [fmt] + needs: [fmt, resolve] if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} runs-on: ubuntu-latest + name: check-feature-powerset ${{ matrix.rust }} + strategy: + # run on stable and MSRV to check that all combinations of features are expected to build fine on our supported + # range of compilers + matrix: + rust: ["stable"] + include: + - rust: ${{ needs.resolve.outputs.MSRV }} steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.event_name != 'merge_group' }} - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@master with: - components: rust-src - - uses: taiki-e/install-action@cargo-hack + toolchain: stable + - uses: taiki-e/install-action@v2 + with: + tool: cargo-hack,cargo-minimal-versions - run: python3 -m pip install --upgrade pip && pip install nox - - run: python3 -m nox -s check-feature-powerset + - run: python3 -m nox -s check-feature-powerset -- ${{ matrix.rust != 'stable' && 'minimal-versions' || '' }} test-cross-compilation: needs: [fmt] diff --git a/Cargo.toml b/Cargo.toml index 364fbac81c1..617400c9637 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ rust-version = "1.63" cfg-if = "1.0" libc = "0.2.62" memoffset = "0.9" -once_cell = "1.13.0" +once_cell = "1.13" # ffi bindings to the python interpreter, split into a separate crate so they can be used independently pyo3-ffi = { path = "pyo3-ffi", version = "=0.22.1" } @@ -32,17 +32,17 @@ unindent = { version = "0.2.1", optional = true } inventory = { version = "0.3.0", optional = true } # crate integrations that can be added using the eponymous features -anyhow = { version = "1.0", optional = true } +anyhow = { version = "1.0.1", optional = true } chrono = { version = "0.4.25", default-features = false, optional = true } chrono-tz = { version = ">= 0.6, < 0.10", default-features = false, optional = true } either = { version = "1.9", optional = true } eyre = { version = ">= 0.4, < 0.7", optional = true } hashbrown = { version = ">= 0.9, < 0.15", optional = true } indexmap = { version = ">= 1.6, < 3", optional = true } -num-bigint = { version = "0.4", optional = true } +num-bigint = { version = "0.4.2", optional = true } num-complex = { version = ">= 0.2, < 0.5", optional = true } num-rational = {version = "0.4.1", optional = true } -rust_decimal = { version = "1.0.0", default-features = false, optional = true } +rust_decimal = { version = "1.15", default-features = false, optional = true } serde = { version = "1.0", optional = true } smallvec = { version = "1.0", optional = true } diff --git a/noxfile.py b/noxfile.py index 96bd587bee8..6b2411d5f17 100644 --- a/noxfile.py +++ b/noxfile.py @@ -543,8 +543,8 @@ def check_changelog(session: nox.Session): print(fragment.name) -@nox.session(name="set-minimal-package-versions", venv_backend="none") -def set_minimal_package_versions(session: nox.Session): +@nox.session(name="set-msrv-package-versions", venv_backend="none") +def set_msrv_package_versions(session: nox.Session): from collections import defaultdict if toml is None: @@ -708,10 +708,14 @@ def check_feature_powerset(session: nox.Session): rust_flags = env.get("RUSTFLAGS", "") env["RUSTFLAGS"] = f"{rust_flags} -Dwarnings" + subcommand = "hack" + if "minimal-versions" in session.posargs: + subcommand = "minimal-versions" + comma_join = ",".join _run_cargo( session, - "hack", + subcommand, "--feature-powerset", '--optional-deps=""', f'--skip="{comma_join(features_to_skip)}"', From f93229380681c731a6ef81f7d2e5d6021c66026d Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 10 Jul 2024 13:30:01 +0100 Subject: [PATCH 06/14] docs: use versioned links from docs to guide (#4331) * use versioned links from docs to guide * use standard python version in `guide-build` ci job --- .github/workflows/gh-pages.yml | 2 +- guide/src/building-and-distribution.md | 2 +- noxfile.py | 12 ++++++++++-- pyo3-build-config/src/lib.rs | 4 +++- pyo3-ffi/src/lib.rs | 5 ++--- pyo3-macros-backend/src/pyclass.rs | 14 ++++++++------ pyo3-macros/src/lib.rs | 24 ++++++++++++------------ src/instance.rs | 3 ++- src/lib.rs | 10 +++++----- src/sync.rs | 4 +++- src/types/module.rs | 4 ++-- tests/ui/reject_generics.stderr | 4 ++-- 12 files changed, 51 insertions(+), 37 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 3237440a99a..7c10a075db4 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -22,7 +22,7 @@ jobs: tag_name: ${{ steps.prepare_tag.outputs.tag_name }} steps: - uses: actions/checkout@v4 - + - uses: actions/setup-python@v5 - uses: dtolnay/rust-toolchain@nightly - name: Setup mdBook diff --git a/guide/src/building-and-distribution.md b/guide/src/building-and-distribution.md index 33280a5a180..c137a1a3995 100644 --- a/guide/src/building-and-distribution.md +++ b/guide/src/building-and-distribution.md @@ -163,7 +163,7 @@ fn main() { For more discussion on and workarounds for MacOS linking problems [see this issue](https://github.com/PyO3/pyo3/issues/1800#issuecomment-906786649). -Finally, don't forget that on MacOS the `extension-module` feature will cause `cargo test` to fail without the `--no-default-features` flag (see [the FAQ](https://pyo3.rs/main/faq.html#i-cant-run-cargo-test-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror)). +Finally, don't forget that on MacOS the `extension-module` feature will cause `cargo test` to fail without the `--no-default-features` flag (see [the FAQ](https://pyo3.rs/main/faq.html#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)). ### The `extension-module` feature diff --git a/noxfile.py b/noxfile.py index 6b2411d5f17..4757a282e84 100644 --- a/noxfile.py +++ b/noxfile.py @@ -394,8 +394,17 @@ def check_guide(session: nox.Session): docs(session) session.posargs.extend(posargs) + if toml is None: + session.error("requires Python 3.11 or `toml` to be installed") + pyo3_version = toml.loads((PYO3_DIR / "Cargo.toml").read_text())["package"][ + "version" + ] + remaps = { f"file://{PYO3_GUIDE_SRC}/([^/]*/)*?%7B%7B#PYO3_DOCS_URL}}}}": f"file://{PYO3_DOCS_TARGET}", + f"https://pyo3.rs/v{pyo3_version}": f"file://{PYO3_GUIDE_TARGET}", + "https://pyo3.rs/main/": f"file://{PYO3_GUIDE_TARGET}/", + "https://pyo3.rs/latest/": f"file://{PYO3_GUIDE_TARGET}/", "%7B%7B#PYO3_DOCS_VERSION}}": "latest", } remap_args = [] @@ -416,8 +425,7 @@ def check_guide(session: nox.Session): session, "lychee", str(PYO3_DOCS_TARGET), - f"--remap=https://pyo3.rs/main/ file://{PYO3_GUIDE_TARGET}/", - f"--remap=https://pyo3.rs/latest/ file://{PYO3_GUIDE_TARGET}/", + *remap_args, f"--exclude=file://{PYO3_DOCS_TARGET}", "--exclude=http://www.adobe.com/", *session.posargs, diff --git a/pyo3-build-config/src/lib.rs b/pyo3-build-config/src/lib.rs index a2d4298c524..2da3e56d3b6 100644 --- a/pyo3-build-config/src/lib.rs +++ b/pyo3-build-config/src/lib.rs @@ -39,7 +39,9 @@ use target_lexicon::OperatingSystem; /// | `#[cfg(PyPy)]` | This marks code which is run when compiling for PyPy. | /// | `#[cfg(GraalPy)]` | This marks code which is run when compiling for GraalPy. | /// -/// For examples of how to use these attributes, [see PyO3's guide](https://pyo3.rs/latest/building-and-distribution/multiple_python_versions.html). +/// For examples of how to use these attributes, +#[doc = concat!("[see PyO3's guide](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution/multiple_python_versions.html)")] +/// . #[cfg(feature = "resolve-config")] pub fn use_pyo3_cfgs() { print_expected_cfgs(); diff --git a/pyo3-ffi/src/lib.rs b/pyo3-ffi/src/lib.rs index 3f6d6732bf3..ff4d03d3a44 100644 --- a/pyo3-ffi/src/lib.rs +++ b/pyo3-ffi/src/lib.rs @@ -221,11 +221,10 @@ //! [`maturin`]: https://github.com/PyO3/maturin "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages" //! [`pyo3-build-config`]: https://docs.rs/pyo3-build-config //! [feature flags]: https://doc.rust-lang.org/cargo/reference/features.html "Features - The Cargo Book" -//! [manual_builds]: https://pyo3.rs/latest/building-and-distribution.html#manual-builds "Manual builds - Building and Distribution - PyO3 user guide" +#![doc = concat!("[manual_builds]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution.html#manual-builds \"Manual builds - Building and Distribution - PyO3 user guide\"")] //! [setuptools-rust]: https://github.com/PyO3/setuptools-rust "Setuptools plugin for Rust extensions" //! [PEP 384]: https://www.python.org/dev/peps/pep-0384 "PEP 384 -- Defining a Stable ABI" -//! [Features chapter of the guide]: https://pyo3.rs/latest/features.html#features-reference "Features Reference - PyO3 user guide" - +#![doc = concat!("[Features chapter of the guide]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/features.html#features-reference \"Features Reference - PyO3 user guide\"")] #![allow( missing_docs, non_camel_case_types, diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index fd85cfa3bb6..2e2ad278e55 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -232,17 +232,19 @@ pub fn build_py_class( if let Some(lt) = class.generics.lifetimes().next() { bail_spanned!( - lt.span() => - "#[pyclass] cannot have lifetime parameters. \ - For an explanation, see https://pyo3.rs/latest/class.html#no-lifetime-parameters" + lt.span() => concat!( + "#[pyclass] cannot have lifetime parameters. For an explanation, see \ + https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#no-lifetime-parameters" + ) ); } ensure_spanned!( class.generics.params.is_empty(), - class.generics.span() => - "#[pyclass] cannot have generic parameters. \ - For an explanation, see https://pyo3.rs/latest/class.html#no-generic-parameters" + class.generics.span() => concat!( + "#[pyclass] cannot have generic parameters. For an explanation, see \ + https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#no-generic-parameters" + ) ); let mut field_options: Vec<(&syn::Field, FieldPyO3Options)> = match &mut class.fields { diff --git a/pyo3-macros/src/lib.rs b/pyo3-macros/src/lib.rs index 95e983079f1..d9e22a94ede 100644 --- a/pyo3-macros/src/lib.rs +++ b/pyo3-macros/src/lib.rs @@ -32,7 +32,7 @@ use syn::{parse::Nothing, parse_macro_input, Item}; /// `#[pymodule]` implementation generates a hidden module with the same name containing /// metadata about the module, which is used by `wrap_pymodule!`). /// -/// [1]: https://pyo3.rs/latest/module.html +#[doc = concat!("[1]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/module.html")] #[proc_macro_attribute] pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream { match parse_macro_input!(input as Item) { @@ -99,17 +99,17 @@ pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream { /// multiple `#[pymethods]` blocks for a single `#[pyclass]`. /// This will add a transitive dependency on the [`inventory`][3] crate. /// -/// [1]: https://pyo3.rs/latest/class.html#instance-methods -/// [2]: https://pyo3.rs/latest/features.html#multiple-pymethods +#[doc = concat!("[1]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#instance-methods")] +#[doc = concat!("[2]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/features.html#multiple-pymethods")] /// [3]: https://docs.rs/inventory/ -/// [4]: https://pyo3.rs/latest/class.html#constructor -/// [5]: https://pyo3.rs/latest/class.html#object-properties-using-getter-and-setter -/// [6]: https://pyo3.rs/latest/class.html#static-methods -/// [7]: https://pyo3.rs/latest/class.html#class-methods -/// [8]: https://pyo3.rs/latest/class.html#callable-objects -/// [9]: https://pyo3.rs/latest/class.html#class-attributes -/// [10]: https://pyo3.rs/latest/class.html#method-arguments -/// [11]: https://pyo3.rs/latest/class.html#object-properties-using-pyo3get-set +#[doc = concat!("[4]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#constructor")] +#[doc = concat!("[5]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#object-properties-using-getter-and-setter")] +#[doc = concat!("[6]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#static-methods")] +#[doc = concat!("[7]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#class-methods")] +#[doc = concat!("[8]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#callable-objects")] +#[doc = concat!("[9]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#class-attributes")] +#[doc = concat!("[10]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#method-arguments")] +#[doc = concat!("[11]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#object-properties-using-pyo3get-set")] #[proc_macro_attribute] pub fn pymethods(attr: TokenStream, input: TokenStream) -> TokenStream { let methods_type = if cfg!(feature = "multiple-pymethods") { @@ -138,7 +138,7 @@ pub fn pymethods(attr: TokenStream, input: TokenStream) -> TokenStream { /// `#[pyfunction]` implementation generates a hidden module with the same name containing /// metadata about the function, which is used by `wrap_pyfunction!`). /// -/// [1]: https://pyo3.rs/latest/function.html +#[doc = concat!("[1]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/function.html")] #[proc_macro_attribute] pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as syn::ItemFn); diff --git a/src/instance.rs b/src/instance.rs index 499f751027c..06e55c00b0f 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -744,7 +744,8 @@ impl IntoPy for Borrowed<'_, '_, T> { /// - [`Py::borrow`], [`Py::try_borrow`], [`Py::borrow_mut`], or [`Py::try_borrow_mut`], /// /// to get a (mutable) reference to a contained pyclass, using a scheme similar to std's [`RefCell`]. -/// See the [guide entry](https://pyo3.rs/latest/class.html#bound-and-interior-mutability) +/// See the +#[doc = concat!("[guide entry](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#bound-and-interior-mutability)")] /// for more information. /// - You can call methods directly on `Py` with [`Py::call_bound`], [`Py::call_method_bound`] and friends. /// diff --git a/src/lib.rs b/src/lib.rs index ef905ec356c..71cdbcea5bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -295,25 +295,25 @@ //! [`rust_decimal`]: ./rust_decimal/index.html "Documenation about the `rust_decimal` feature." //! [`Decimal`]: https://docs.rs/rust_decimal/latest/rust_decimal/struct.Decimal.html //! [`serde`]: <./serde/index.html> "Documentation about the `serde` feature." -//! [calling_rust]: https://pyo3.rs/latest/python-from-rust.html "Calling Python from Rust - PyO3 user guide" +#![doc = concat!("[calling_rust]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/python-from-rust.html \"Calling Python from Rust - PyO3 user guide\"")] //! [examples subdirectory]: https://github.com/PyO3/pyo3/tree/main/examples //! [feature flags]: https://doc.rust-lang.org/cargo/reference/features.html "Features - The Cargo Book" //! [global interpreter lock]: https://docs.python.org/3/glossary.html#term-global-interpreter-lock //! [hashbrown]: https://docs.rs/hashbrown //! [smallvec]: https://docs.rs/smallvec //! [indexmap]: https://docs.rs/indexmap -//! [manual_builds]: https://pyo3.rs/latest/building-and-distribution.html#manual-builds "Manual builds - Building and Distribution - PyO3 user guide" +#![doc = concat!("[manual_builds]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution.html#manual-builds \"Manual builds - Building and Distribution - PyO3 user guide\"")] //! [num-bigint]: https://docs.rs/num-bigint //! [num-complex]: https://docs.rs/num-complex //! [num-rational]: https://docs.rs/num-rational //! [serde]: https://docs.rs/serde //! [setuptools-rust]: https://github.com/PyO3/setuptools-rust "Setuptools plugin for Rust extensions" //! [the guide]: https://pyo3.rs "PyO3 user guide" -//! [types]: https://pyo3.rs/latest/types.html "GIL lifetimes, mutability and Python object types" +#![doc = concat!("[types]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/types.html \"GIL lifetimes, mutability and Python object types\"")] //! [PEP 384]: https://www.python.org/dev/peps/pep-0384 "PEP 384 -- Defining a Stable ABI" //! [Python from Rust]: https://github.com/PyO3/pyo3#using-python-from-rust //! [Rust from Python]: https://github.com/PyO3/pyo3#using-rust-from-python -//! [Features chapter of the guide]: https://pyo3.rs/latest/features.html#features-reference "Features Reference - PyO3 user guide" +#![doc = concat!("[Features chapter of the guide]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/features.html#features-reference \"Features Reference - PyO3 user guide\"")] //! [`Ungil`]: crate::marker::Ungil pub use crate::class::*; pub use crate::conversion::{AsPyPointer, FromPyObject, IntoPy, ToPyObject}; @@ -483,7 +483,7 @@ pub use pyo3_macros::{pyfunction, pymethods, pymodule, FromPyObject}; /// For more on creating Python classes, /// see the [class section of the guide][1]. /// -/// [1]: https://pyo3.rs/latest/class.html +#[doc = concat!("[1]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html")] #[cfg(feature = "macros")] pub use pyo3_macros::pyclass; diff --git a/src/sync.rs b/src/sync.rs index a8265eabdbd..390011fdd5b 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -59,7 +59,9 @@ unsafe impl Sync for GILProtected where T: Send {} /// Unlike `once_cell::sync` which blocks threads to achieve thread safety, this implementation /// uses the Python GIL to mediate concurrent access. This helps in cases where `once_cell` or /// `lazy_static`'s synchronization strategy can lead to deadlocks when interacting with the Python -/// GIL. For an example, see [the FAQ section](https://pyo3.rs/latest/faq.html) of the guide. +/// GIL. For an example, see +#[doc = concat!("[the FAQ section](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/faq.html)")] +/// of the guide. /// /// Note that: /// 1) `get_or_init` and `get_or_try_init` do not protect against infinite recursion diff --git a/src/types/module.rs b/src/types/module.rs index 5438c22f681..c805c09a239 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -304,7 +304,7 @@ impl PyModule { /// make an *instance* of `Foo` (or *get* one for that matter, as we haven't exported /// anything that can return instances of `Foo`). /// - /// [1]: https://pyo3.rs/latest/class.html#constructor + #[doc = concat!("[1]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#constructor")] pub fn add_class(&self) -> PyResult<()> where T: PyClass, @@ -509,7 +509,7 @@ pub trait PyModuleMethods<'py>: crate::sealed::Sealed { /// make an *instance* of `Foo` (or *get* one for that matter, as we haven't exported /// anything that can return instances of `Foo`). /// - /// [1]: https://pyo3.rs/latest/class.html#constructor + #[doc = concat!("[1]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#constructor")] fn add_class(&self) -> PyResult<()> where T: PyClass; diff --git a/tests/ui/reject_generics.stderr b/tests/ui/reject_generics.stderr index 2285b9271fa..78c3d00e624 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/latest/class.html#no-generic-parameters +error: #[pyclass] cannot have generic parameters. For an explanation, see https://pyo3.rs/v0.23.0-dev/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/latest/class.html#no-lifetime-parameters +error: #[pyclass] cannot have lifetime parameters. For an explanation, see https://pyo3.rs/v0.23.0-dev/class.html#no-lifetime-parameters --> tests/ui/reject_generics.rs:9:27 | 9 | struct ClassWithLifetimes<'a> { From 7e54a9c3855a63a592592c411a773d04fd94524d Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 10 Jul 2024 15:18:13 +0100 Subject: [PATCH 07/14] docs: fixups to 0.22 migration guide (#4332) --- guide/src/migration.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/guide/src/migration.md b/guide/src/migration.md index 8ac3ff16c47..403118307d4 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -5,6 +5,15 @@ For a detailed list of all changes, see the [CHANGELOG](changelog.md). ## from 0.21.* to 0.22 +### Deprecation of `gil-refs` feature continues +
+Click to expand + +Following the introduction of the "Bound" API in PyO3 0.21 and the planned removal of the "GIL Refs" API, all functionality related to GIL Refs is now gated behind the `gil-refs` feature and emits a deprecation warning on use. + +See the 0.21 migration entry for help upgrading. +
+ ### Deprecation of implicit default for trailing optional arguments
Click to expand @@ -529,6 +538,7 @@ assert_eq!(&*name, "list"); # } # Python::with_gil(example).unwrap(); ``` +
## from 0.19.* to 0.20 From 578ce46a922f11d7590655a9a439c5bb01bdbbd6 Mon Sep 17 00:00:00 2001 From: Larry Z Date: Wed, 10 Jul 2024 19:39:39 +0100 Subject: [PATCH 08/14] Prevent building in GIL-less environment (#4327) * Prevent building in GIL-less environment * Add change log * add "yet" to phrasing * Add testing to build script * add link to issue * Fix formatting issues --------- Co-authored-by: David Hewitt --- newsfragments/4327.packaging.md | 3 +++ noxfile.py | 15 +++++++++++++-- pyo3-build-config/src/impl_.rs | 5 ++++- pyo3-ffi/build.rs | 22 +++++++++++++++++++++- 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 newsfragments/4327.packaging.md diff --git a/newsfragments/4327.packaging.md b/newsfragments/4327.packaging.md new file mode 100644 index 00000000000..c98d06f7cba --- /dev/null +++ b/newsfragments/4327.packaging.md @@ -0,0 +1,3 @@ +This PR lets PyO3 checks `Py_GIL_DISABLED` build flag and prevents `pyo3-ffi` crate building against GIL-less Python, +unless +explicitly opt using the `UNSAFE_PYO3_BUILD_FREE_THREADED` environment flag. \ No newline at end of file diff --git a/noxfile.py b/noxfile.py index 4757a282e84..5d8123c7eb4 100644 --- a/noxfile.py +++ b/noxfile.py @@ -9,7 +9,7 @@ from functools import lru_cache from glob import glob from pathlib import Path -from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple +from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple import nox import nox.command @@ -655,6 +655,14 @@ def test_version_limits(session: nox.Session): config_file.set("PyPy", "3.11") _run_cargo(session, "check", env=env, expect_error=True) + # Python build with GIL disabled should fail building + config_file.set("CPython", "3.13", build_flags=["Py_GIL_DISABLED"]) + _run_cargo(session, "check", env=env, expect_error=True) + + # Python build with GIL disabled should pass with env flag on + env["UNSAFE_PYO3_BUILD_FREE_THREADED"] = "1" + _run_cargo(session, "check", env=env) + @nox.session(name="check-feature-powerset", venv_backend="none") def check_feature_powerset(session: nox.Session): @@ -919,7 +927,9 @@ class _ConfigFile: def __init__(self, config_file) -> None: self._config_file = config_file - def set(self, implementation: str, version: str) -> None: + def set( + self, implementation: str, version: str, build_flags: Iterable[str] = () + ) -> None: """Set the contents of this config file to the given implementation and version.""" self._config_file.seek(0) self._config_file.truncate(0) @@ -927,6 +937,7 @@ def set(self, implementation: str, version: str) -> None: f"""\ implementation={implementation} version={version} +build_flags={','.join(build_flags)} suppress_build_script_link_lines=true """ ) diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index 3dc1e912447..d38d41ed552 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -996,6 +996,7 @@ pub enum BuildFlag { Py_DEBUG, Py_REF_DEBUG, Py_TRACE_REFS, + Py_GIL_DISABLED, COUNT_ALLOCS, Other(String), } @@ -1016,6 +1017,7 @@ impl FromStr for BuildFlag { "Py_DEBUG" => Ok(BuildFlag::Py_DEBUG), "Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG), "Py_TRACE_REFS" => Ok(BuildFlag::Py_TRACE_REFS), + "Py_GIL_DISABLED" => Ok(BuildFlag::Py_GIL_DISABLED), "COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS), other => Ok(BuildFlag::Other(other.to_owned())), } @@ -1039,10 +1041,11 @@ impl FromStr for BuildFlag { pub struct BuildFlags(pub HashSet); impl BuildFlags { - const ALL: [BuildFlag; 4] = [ + const ALL: [BuildFlag; 5] = [ BuildFlag::Py_DEBUG, BuildFlag::Py_REF_DEBUG, BuildFlag::Py_TRACE_REFS, + BuildFlag::Py_GIL_DISABLED, BuildFlag::COUNT_ALLOCS, ]; diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs index b4521678ba9..83408b31222 100644 --- a/pyo3-ffi/build.rs +++ b/pyo3-ffi/build.rs @@ -4,8 +4,9 @@ use pyo3_build_config::{ cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config, InterpreterConfig, PythonVersion, }, - warn, PythonImplementation, + warn, BuildFlag, PythonImplementation, }; +use std::ops::Not; /// Minimum Python version PyO3 supports. struct SupportedVersions { @@ -120,6 +121,24 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { Ok(()) } +fn ensure_gil_enabled(interpreter_config: &InterpreterConfig) -> Result<()> { + let gil_enabled = interpreter_config + .build_flags + .0 + .contains(&BuildFlag::Py_GIL_DISABLED) + .not(); + ensure!( + gil_enabled || std::env::var("UNSAFE_PYO3_BUILD_FREE_THREADED").map_or(false, |os_str| os_str == "1"), + "the Python interpreter was built with the GIL disabled, which is not yet supported by PyO3\n\ + = help: see https://github.com/PyO3/pyo3/issues/4265 for more information\n\ + = help: please check if an updated version of PyO3 is available. Current version: {}\n\ + = help: set UNSAFE_PYO3_BUILD_FREE_THREADED=1 to suppress this check and build anyway for free-threaded Python", + std::env::var("CARGO_PKG_VERSION").unwrap() + ); + + Ok(()) +} + fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result<()> { if let Some(pointer_width) = interpreter_config.pointer_width { // Try to check whether the target architecture matches the python library @@ -185,6 +204,7 @@ fn configure_pyo3() -> Result<()> { ensure_python_version(&interpreter_config)?; ensure_target_pointer_width(&interpreter_config)?; + ensure_gil_enabled(&interpreter_config)?; // Serialize the whole interpreter config into DEP_PYTHON_PYO3_CONFIG env var. interpreter_config.to_cargo_dep_env()?; From a1ffafcd3be2d757fdad2f1c45d989884329817d Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 16 Jul 2024 08:48:22 +0100 Subject: [PATCH 09/14] remove `BuildFlag` member to avoid breaking change in patch --- pyo3-build-config/src/impl_.rs | 5 +---- pyo3-ffi/build.rs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index d38d41ed552..3dc1e912447 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -996,7 +996,6 @@ pub enum BuildFlag { Py_DEBUG, Py_REF_DEBUG, Py_TRACE_REFS, - Py_GIL_DISABLED, COUNT_ALLOCS, Other(String), } @@ -1017,7 +1016,6 @@ impl FromStr for BuildFlag { "Py_DEBUG" => Ok(BuildFlag::Py_DEBUG), "Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG), "Py_TRACE_REFS" => Ok(BuildFlag::Py_TRACE_REFS), - "Py_GIL_DISABLED" => Ok(BuildFlag::Py_GIL_DISABLED), "COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS), other => Ok(BuildFlag::Other(other.to_owned())), } @@ -1041,11 +1039,10 @@ impl FromStr for BuildFlag { pub struct BuildFlags(pub HashSet); impl BuildFlags { - const ALL: [BuildFlag; 5] = [ + const ALL: [BuildFlag; 4] = [ BuildFlag::Py_DEBUG, BuildFlag::Py_REF_DEBUG, BuildFlag::Py_TRACE_REFS, - BuildFlag::Py_GIL_DISABLED, BuildFlag::COUNT_ALLOCS, ]; diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs index 83408b31222..25f2ca1733d 100644 --- a/pyo3-ffi/build.rs +++ b/pyo3-ffi/build.rs @@ -125,7 +125,7 @@ fn ensure_gil_enabled(interpreter_config: &InterpreterConfig) -> Result<()> { let gil_enabled = interpreter_config .build_flags .0 - .contains(&BuildFlag::Py_GIL_DISABLED) + .contains(&BuildFlag::Other("Py_GIL_DISABLED".to_string())) .not(); ensure!( gil_enabled || std::env::var("UNSAFE_PYO3_BUILD_FREE_THREADED").map_or(false, |os_str| os_str == "1"), From 554478eb53a526a62c0138502f3963f9be22da7b Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 10 Jul 2024 23:38:38 +0100 Subject: [PATCH 10/14] allow `#[pymodule(...)]` to accept all relevant `#[pyo3(...)]` options (#4330) --- examples/getitem/src/lib.rs | 3 +- guide/src/module.md | 3 +- newsfragments/4330.changed.md | 1 + pyo3-macros-backend/src/module.rs | 121 +++++++++--------- pyo3-macros/src/lib.rs | 44 +++---- tests/test_declarative_module.rs | 6 +- tests/test_module.rs | 3 +- tests/ui/empty.rs | 1 + tests/ui/invalid_pymodule_args.stderr | 2 +- tests/ui/invalid_pymodule_glob.rs | 2 + tests/ui/invalid_pymodule_glob.stderr | 4 +- tests/ui/invalid_pymodule_in_root.rs | 1 + tests/ui/invalid_pymodule_in_root.stderr | 8 +- tests/ui/invalid_pymodule_trait.stderr | 6 + .../ui/invalid_pymodule_two_pymodule_init.rs | 6 +- .../invalid_pymodule_two_pymodule_init.stderr | 4 +- 16 files changed, 108 insertions(+), 107 deletions(-) create mode 100644 newsfragments/4330.changed.md create mode 100644 tests/ui/empty.rs diff --git a/examples/getitem/src/lib.rs b/examples/getitem/src/lib.rs index ce162a70bf9..ba850a06b8d 100644 --- a/examples/getitem/src/lib.rs +++ b/examples/getitem/src/lib.rs @@ -75,8 +75,7 @@ impl ExampleContainer { } } -#[pymodule] -#[pyo3(name = "getitem")] +#[pymodule(name = "getitem")] fn example(m: &Bound<'_, PyModule>) -> PyResult<()> { // ? -https://github.com/PyO3/maturin/issues/475 m.add_class::()?; diff --git a/guide/src/module.md b/guide/src/module.md index a2cb8b37a05..af6da0c916f 100644 --- a/guide/src/module.md +++ b/guide/src/module.md @@ -31,8 +31,7 @@ fn double(x: usize) -> usize { x * 2 } -#[pymodule] -#[pyo3(name = "custom_name")] +#[pymodule(name = "custom_name")] fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(double, m)?) } diff --git a/newsfragments/4330.changed.md b/newsfragments/4330.changed.md new file mode 100644 index 00000000000..d465ec99cec --- /dev/null +++ b/newsfragments/4330.changed.md @@ -0,0 +1 @@ +`#[pymodule(...)]` now directly accepts all relevant `#[pyo3(...)]` options. diff --git a/pyo3-macros-backend/src/module.rs b/pyo3-macros-backend/src/module.rs index 2ca084a6a4b..cd6340eef88 100644 --- a/pyo3-macros-backend/src/module.rs +++ b/pyo3-macros-backend/src/module.rs @@ -2,8 +2,8 @@ use crate::{ attributes::{ - self, take_attributes, take_pyo3_options, CrateAttribute, ModuleAttribute, NameAttribute, - SubmoduleAttribute, + self, kw, take_attributes, take_pyo3_options, CrateAttribute, ModuleAttribute, + NameAttribute, SubmoduleAttribute, }, get_doc, pyclass::PyClassPyO3Option, @@ -26,97 +26,85 @@ use syn::{ #[derive(Default)] pub struct PyModuleOptions { krate: Option, - name: Option, + name: Option, module: Option, - is_submodule: bool, + submodule: Option, } -impl PyModuleOptions { - pub fn from_attrs(attrs: &mut Vec) -> Result { +impl Parse for PyModuleOptions { + fn parse(input: ParseStream<'_>) -> syn::Result { let mut options: PyModuleOptions = Default::default(); - for option in take_pyo3_options(attrs)? { - match option { - PyModulePyO3Option::Name(name) => options.set_name(name.value.0)?, - PyModulePyO3Option::Crate(path) => options.set_crate(path)?, - PyModulePyO3Option::Module(module) => options.set_module(module)?, - PyModulePyO3Option::Submodule(submod) => options.set_submodule(submod)?, - } - } + options.add_attributes( + Punctuated::::parse_terminated(input)?, + )?; Ok(options) } +} - fn set_name(&mut self, name: syn::Ident) -> Result<()> { - ensure_spanned!( - self.name.is_none(), - name.span() => "`name` may only be specified once" - ); - - self.name = Some(name); - Ok(()) - } - - fn set_crate(&mut self, path: CrateAttribute) -> Result<()> { - ensure_spanned!( - self.krate.is_none(), - path.span() => "`crate` may only be specified once" - ); - - self.krate = Some(path); - Ok(()) - } - - fn set_module(&mut self, name: ModuleAttribute) -> Result<()> { - ensure_spanned!( - self.module.is_none(), - name.span() => "`module` may only be specified once" - ); - - self.module = Some(name); - Ok(()) +impl PyModuleOptions { + fn take_pyo3_options(&mut self, attrs: &mut Vec) -> Result<()> { + self.add_attributes(take_pyo3_options(attrs)?) } - fn set_submodule(&mut self, submod: SubmoduleAttribute) -> Result<()> { - ensure_spanned!( - !self.is_submodule, - submod.span() => "`submodule` may only be specified once" - ); - - self.is_submodule = true; + fn add_attributes( + &mut self, + attrs: impl IntoIterator, + ) -> Result<()> { + macro_rules! set_option { + ($key:ident) => { + { + ensure_spanned!( + self.$key.is_none(), + $key.span() => concat!("`", stringify!($key), "` may only be specified once") + ); + self.$key = Some($key); + } + }; + } + for attr in attrs { + match attr { + PyModulePyO3Option::Crate(krate) => set_option!(krate), + PyModulePyO3Option::Name(name) => set_option!(name), + PyModulePyO3Option::Module(module) => set_option!(module), + PyModulePyO3Option::Submodule(submodule) => set_option!(submodule), + } + } Ok(()) } } pub fn pymodule_module_impl( - mut module: syn::ItemMod, - mut is_submodule: bool, + module: &mut syn::ItemMod, + mut options: PyModuleOptions, ) -> Result { let syn::ItemMod { attrs, vis, unsafety: _, ident, - mod_token: _, + mod_token, content, semi: _, - } = &mut module; + } = module; let items = if let Some((_, items)) = content { items } else { - bail_spanned!(module.span() => "`#[pymodule]` can only be used on inline modules") + bail_spanned!(mod_token.span() => "`#[pymodule]` can only be used on inline modules") }; - let options = PyModuleOptions::from_attrs(attrs)?; + options.take_pyo3_options(attrs)?; let ctx = &Ctx::new(&options.krate, None); let Ctx { pyo3_path, .. } = ctx; let doc = get_doc(attrs, None, ctx); - let name = options.name.unwrap_or_else(|| ident.unraw()); + let name = options + .name + .map_or_else(|| ident.unraw(), |name| name.value.0); let full_name = if let Some(module) = &options.module { format!("{}.{}", module.value.value(), name) } else { name.to_string() }; - is_submodule = is_submodule || options.is_submodule; let mut module_items = Vec::new(); let mut module_items_cfg_attrs = Vec::new(); @@ -350,10 +338,11 @@ pub fn pymodule_module_impl( ) } }}; - let initialization = module_initialization(&name, ctx, module_def, is_submodule); + let initialization = module_initialization(&name, ctx, module_def, options.submodule.is_some()); + Ok(quote!( #(#attrs)* - #vis mod #ident { + #vis #mod_token #ident { #(#items)* #initialization @@ -373,14 +362,19 @@ pub fn pymodule_module_impl( /// Generates the function that is called by the python interpreter to initialize the native /// module -pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result { - let options = PyModuleOptions::from_attrs(&mut function.attrs)?; - process_functions_in_module(&options, &mut function)?; +pub fn pymodule_function_impl( + function: &mut syn::ItemFn, + mut options: PyModuleOptions, +) -> Result { + options.take_pyo3_options(&mut function.attrs)?; + process_functions_in_module(&options, function)?; let ctx = &Ctx::new(&options.krate, None); let stmts = std::mem::take(&mut function.block.stmts); let Ctx { pyo3_path, .. } = ctx; let ident = &function.sig.ident; - let name = options.name.unwrap_or_else(|| ident.unraw()); + let name = options + .name + .map_or_else(|| ident.unraw(), |name| name.value.0); let vis = &function.vis; let doc = get_doc(&function.attrs, None, ctx); @@ -419,7 +413,6 @@ pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result .push(parse_quote!(#[allow(clippy::used_underscore_binding)])); Ok(quote! { - #function #[doc(hidden)] #vis mod #ident { #initialization diff --git a/pyo3-macros/src/lib.rs b/pyo3-macros/src/lib.rs index d9e22a94ede..c4263a512d3 100644 --- a/pyo3-macros/src/lib.rs +++ b/pyo3-macros/src/lib.rs @@ -3,14 +3,14 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use proc_macro::TokenStream; -use proc_macro2::{Span, TokenStream as TokenStream2}; +use proc_macro2::TokenStream as TokenStream2; use pyo3_macros_backend::{ build_derive_from_pyobject, build_py_class, build_py_enum, build_py_function, build_py_methods, pymodule_function_impl, pymodule_module_impl, PyClassArgs, PyClassMethodsType, - PyFunctionOptions, + PyFunctionOptions, PyModuleOptions, }; use quote::quote; -use syn::{parse::Nothing, parse_macro_input, Item}; +use syn::{parse_macro_input, Item}; /// A proc macro used to implement Python modules. /// @@ -24,6 +24,9 @@ use syn::{parse::Nothing, parse_macro_input, Item}; /// | Annotation | Description | /// | :- | :- | /// | `#[pyo3(name = "...")]` | Defines the name of the module in Python. | +/// | `#[pyo3(submodule)]` | Skips adding a `PyInit_` FFI symbol to the compiled binary. | +/// | `#[pyo3(module = "...")]` | Defines the Python `dotted.path` to the parent module for use in introspection. | +/// | `#[pyo3(crate = "pyo3")]` | Defines the path to PyO3 to use code generated by the macro. | /// /// For more on creating Python modules see the [module section of the guide][1]. /// @@ -35,32 +38,29 @@ use syn::{parse::Nothing, parse_macro_input, Item}; #[doc = concat!("[1]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/module.html")] #[proc_macro_attribute] pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream { - match parse_macro_input!(input as Item) { + let options = parse_macro_input!(args as PyModuleOptions); + + let mut ast = parse_macro_input!(input as Item); + let expanded = match &mut ast { Item::Mod(module) => { - let is_submodule = match parse_macro_input!(args as Option) { - Some(i) if i == "submodule" => true, - Some(_) => { - return syn::Error::new( - Span::call_site(), - "#[pymodule] only accepts submodule as an argument", - ) - .into_compile_error() - .into(); - } - None => false, - }; - pymodule_module_impl(module, is_submodule) - } - Item::Fn(function) => { - parse_macro_input!(args as Nothing); - pymodule_function_impl(function) + match pymodule_module_impl(module, options) { + // #[pymodule] on a module will rebuild the original ast, so we don't emit it here + Ok(expanded) => return expanded.into(), + Err(e) => Err(e), + } } + Item::Fn(function) => pymodule_function_impl(function, options), unsupported => Err(syn::Error::new_spanned( unsupported, "#[pymodule] only supports modules and functions.", )), } - .unwrap_or_compile_error() + .unwrap_or_compile_error(); + + quote!( + #ast + #expanded + ) .into() } diff --git a/tests/test_declarative_module.rs b/tests/test_declarative_module.rs index f62d51822ee..4f227a73462 100644 --- a/tests/test_declarative_module.rs +++ b/tests/test_declarative_module.rs @@ -49,8 +49,7 @@ create_exception!( "Some description." ); -#[pymodule] -#[pyo3(submodule)] +#[pymodule(submodule)] mod external_submodule {} /// A module written using declarative syntax. @@ -144,8 +143,7 @@ mod declarative_submodule { use super::{double, double_value}; } -#[pymodule] -#[pyo3(name = "declarative_module_renamed")] +#[pymodule(name = "declarative_module_renamed")] mod declarative_module2 { #[pymodule_export] use super::double; diff --git a/tests/test_module.rs b/tests/test_module.rs index b2487cfd8b3..eba1bcce6d2 100644 --- a/tests/test_module.rs +++ b/tests/test_module.rs @@ -138,8 +138,7 @@ fn test_module_with_explicit_py_arg() { }); } -#[pymodule] -#[pyo3(name = "other_name")] +#[pymodule(name = "other_name")] fn some_name(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add("other_name", "other_name")?; Ok(()) diff --git a/tests/ui/empty.rs b/tests/ui/empty.rs new file mode 100644 index 00000000000..be89c636426 --- /dev/null +++ b/tests/ui/empty.rs @@ -0,0 +1 @@ +// see invalid_pymodule_in_root.rs diff --git a/tests/ui/invalid_pymodule_args.stderr b/tests/ui/invalid_pymodule_args.stderr index 933b6d6081c..261d8115e15 100644 --- a/tests/ui/invalid_pymodule_args.stderr +++ b/tests/ui/invalid_pymodule_args.stderr @@ -1,4 +1,4 @@ -error: unexpected token +error: expected one of: `name`, `crate`, `module`, `submodule` --> tests/ui/invalid_pymodule_args.rs:3:12 | 3 | #[pymodule(some_arg)] diff --git a/tests/ui/invalid_pymodule_glob.rs b/tests/ui/invalid_pymodule_glob.rs index 107cdf9382a..853493b535e 100644 --- a/tests/ui/invalid_pymodule_glob.rs +++ b/tests/ui/invalid_pymodule_glob.rs @@ -1,3 +1,5 @@ +#![allow(unused_imports)] + use pyo3::prelude::*; #[pyfunction] diff --git a/tests/ui/invalid_pymodule_glob.stderr b/tests/ui/invalid_pymodule_glob.stderr index 237e02037aa..1c033083e0c 100644 --- a/tests/ui/invalid_pymodule_glob.stderr +++ b/tests/ui/invalid_pymodule_glob.stderr @@ -1,5 +1,5 @@ error: #[pymodule] cannot import glob statements - --> tests/ui/invalid_pymodule_glob.rs:11:16 + --> tests/ui/invalid_pymodule_glob.rs:13:16 | -11 | use super::*; +13 | use super::*; | ^ diff --git a/tests/ui/invalid_pymodule_in_root.rs b/tests/ui/invalid_pymodule_in_root.rs index 47af4205f71..76ced6c3fb6 100644 --- a/tests/ui/invalid_pymodule_in_root.rs +++ b/tests/ui/invalid_pymodule_in_root.rs @@ -1,6 +1,7 @@ use pyo3::prelude::*; #[pymodule] +#[path = "empty.rs"] // to silence error related to missing file mod invalid_pymodule_in_root_module; fn main() {} diff --git a/tests/ui/invalid_pymodule_in_root.stderr b/tests/ui/invalid_pymodule_in_root.stderr index 91783be0e97..06152161e5d 100644 --- a/tests/ui/invalid_pymodule_in_root.stderr +++ b/tests/ui/invalid_pymodule_in_root.stderr @@ -1,13 +1,13 @@ error[E0658]: non-inline modules in proc macro input are unstable - --> tests/ui/invalid_pymodule_in_root.rs:4:1 + --> tests/ui/invalid_pymodule_in_root.rs:5:1 | -4 | mod invalid_pymodule_in_root_module; +5 | mod invalid_pymodule_in_root_module; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: see issue #54727 for more information error: `#[pymodule]` can only be used on inline modules - --> tests/ui/invalid_pymodule_in_root.rs:4:1 + --> tests/ui/invalid_pymodule_in_root.rs:5:1 | -4 | mod invalid_pymodule_in_root_module; +5 | mod invalid_pymodule_in_root_module; | ^^^ diff --git a/tests/ui/invalid_pymodule_trait.stderr b/tests/ui/invalid_pymodule_trait.stderr index 4b02f14a540..0b0d46da93d 100644 --- a/tests/ui/invalid_pymodule_trait.stderr +++ b/tests/ui/invalid_pymodule_trait.stderr @@ -3,3 +3,9 @@ error: `#[pymodule_export]` may only be used on `use` statements | 5 | #[pymodule_export] | ^ + +error: cannot find attribute `pymodule_export` in this scope + --> tests/ui/invalid_pymodule_trait.rs:5:7 + | +5 | #[pymodule_export] + | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/invalid_pymodule_two_pymodule_init.rs b/tests/ui/invalid_pymodule_two_pymodule_init.rs index d676b0fa277..ed72cbb7237 100644 --- a/tests/ui/invalid_pymodule_two_pymodule_init.rs +++ b/tests/ui/invalid_pymodule_two_pymodule_init.rs @@ -2,13 +2,15 @@ use pyo3::prelude::*; #[pymodule] mod module { + use pyo3::prelude::*; + #[pymodule_init] - fn init(m: &PyModule) -> PyResult<()> { + fn init(_m: &Bound<'_, PyModule>) -> PyResult<()> { Ok(()) } #[pymodule_init] - fn init2(m: &PyModule) -> PyResult<()> { + fn init2(_m: &Bound<'_, PyModule>) -> PyResult<()> { Ok(()) } } diff --git a/tests/ui/invalid_pymodule_two_pymodule_init.stderr b/tests/ui/invalid_pymodule_two_pymodule_init.stderr index c117ebd573f..8fbd12f2e45 100644 --- a/tests/ui/invalid_pymodule_two_pymodule_init.stderr +++ b/tests/ui/invalid_pymodule_two_pymodule_init.stderr @@ -1,5 +1,5 @@ error: only one `#[pymodule_init]` may be specified - --> tests/ui/invalid_pymodule_two_pymodule_init.rs:11:5 + --> tests/ui/invalid_pymodule_two_pymodule_init.rs:13:5 | -11 | fn init2(m: &PyModule) -> PyResult<()> { +13 | fn init2(_m: &Bound<'_, PyModule>) -> PyResult<()> { | ^^ From 745545c08c0acb2ca60718309874f089f490d843 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Fri, 12 Jul 2024 17:13:00 +0100 Subject: [PATCH 11/14] use FFI calls for refcounting on all abi3 versions (#4324) * use FFI calls for refcounting on all abi3 versions * fix implementation on PyPy --- newsfragments/4324.changed.md | 1 + pyo3-ffi/src/object.rs | 91 ++++++++++++++--------------------- 2 files changed, 36 insertions(+), 56 deletions(-) create mode 100644 newsfragments/4324.changed.md diff --git a/newsfragments/4324.changed.md b/newsfragments/4324.changed.md new file mode 100644 index 00000000000..2818159dda3 --- /dev/null +++ b/newsfragments/4324.changed.md @@ -0,0 +1 @@ +Use FFI function calls for reference counting on all abi3 versions. diff --git a/pyo3-ffi/src/object.rs b/pyo3-ffi/src/object.rs index 7acd0897217..21161a34d3a 100644 --- a/pyo3-ffi/src/object.rs +++ b/pyo3-ffi/src/object.rs @@ -490,11 +490,9 @@ extern "C" { #[cfg_attr(GraalPy, link_name = "_Py_DecRef")] pub fn Py_DecRef(o: *mut PyObject); - #[cfg(Py_3_10)] - #[cfg_attr(PyPy, link_name = "_PyPy_IncRef")] + #[cfg(all(Py_3_10, not(PyPy)))] pub fn _Py_IncRef(o: *mut PyObject); - #[cfg(Py_3_10)] - #[cfg_attr(PyPy, link_name = "_PyPy_DecRef")] + #[cfg(all(Py_3_10, not(PyPy)))] pub fn _Py_DecRef(o: *mut PyObject); #[cfg(GraalPy)] @@ -509,35 +507,23 @@ extern "C" { #[inline(always)] pub unsafe fn Py_INCREF(op: *mut PyObject) { - #[cfg(any( - GraalPy, - all(Py_LIMITED_API, Py_3_12), - all( - py_sys_config = "Py_REF_DEBUG", - Py_3_10, - not(all(Py_3_12, not(Py_LIMITED_API))) - ) - ))] + // On limited API or with refcount debugging, let the interpreter do refcounting + #[cfg(any(Py_LIMITED_API, py_sys_config = "Py_REF_DEBUG", GraalPy))] { - _Py_IncRef(op); - } + // _Py_IncRef was added to the ABI in 3.10; skips null checks + #[cfg(all(Py_3_10, not(PyPy)))] + { + _Py_IncRef(op); + } - #[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_3_10)))] - { - return Py_IncRef(op); + #[cfg(any(not(Py_3_10), PyPy))] + { + Py_IncRef(op); + } } - #[cfg(any( - all(Py_LIMITED_API, not(Py_3_12)), - all( - not(Py_LIMITED_API), - not(GraalPy), - any( - not(py_sys_config = "Py_REF_DEBUG"), - all(py_sys_config = "Py_REF_DEBUG", Py_3_12), - ) - ), - ))] + // version-specific builds are allowed to directly manipulate the reference count + #[cfg(not(any(any(Py_LIMITED_API, py_sys_config = "Py_REF_DEBUG", GraalPy))))] { #[cfg(all(Py_3_12, target_pointer_width = "64"))] { @@ -564,9 +550,6 @@ pub unsafe fn Py_INCREF(op: *mut PyObject) { // Skipped _Py_INCREF_STAT_INC - if anyone wants this, please file an issue // or submit a PR supporting Py_STATS build option and pystats.h - - #[cfg(all(py_sys_config = "Py_REF_DEBUG", Py_3_12))] - _Py_INCREF_IncRefTotal(); } } @@ -576,35 +559,31 @@ pub unsafe fn Py_INCREF(op: *mut PyObject) { track_caller )] pub unsafe fn Py_DECREF(op: *mut PyObject) { + // On limited API or with refcount debugging, let the interpreter do refcounting + // On 3.12+ we implement refcount debugging to get better assertion locations on negative refcounts #[cfg(any( - GraalPy, - all(Py_LIMITED_API, Py_3_12), - all( - py_sys_config = "Py_REF_DEBUG", - Py_3_10, - not(all(Py_3_12, not(Py_LIMITED_API))) - ) + Py_LIMITED_API, + all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)), + GraalPy ))] { - _Py_DecRef(op); - } + // _Py_DecRef was added to the ABI in 3.10; skips null checks + #[cfg(all(Py_3_10, not(PyPy)))] + { + _Py_DecRef(op); + } - #[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_3_10)))] - { - return Py_DecRef(op); + #[cfg(any(not(Py_3_10), PyPy))] + { + Py_DecRef(op); + } } - #[cfg(any( - all(Py_LIMITED_API, not(Py_3_12)), - all( - not(Py_LIMITED_API), - not(GraalPy), - any( - not(py_sys_config = "Py_REF_DEBUG"), - all(py_sys_config = "Py_REF_DEBUG", Py_3_12), - ) - ), - ))] + #[cfg(not(any( + Py_LIMITED_API, + all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)), + GraalPy + )))] { #[cfg(Py_3_12)] if _Py_IsImmortal(op) != 0 { @@ -614,7 +593,7 @@ pub unsafe fn Py_DECREF(op: *mut PyObject) { // Skipped _Py_DECREF_STAT_INC - if anyone needs this, please file an issue // or submit a PR supporting Py_STATS build option and pystats.h - #[cfg(all(py_sys_config = "Py_REF_DEBUG", Py_3_12))] + #[cfg(py_sys_config = "Py_REF_DEBUG")] _Py_DECREF_DecRefTotal(); #[cfg(Py_3_12)] From e43b811f973c47fc338ec299741030d3550a3f71 Mon Sep 17 00:00:00 2001 From: Matthijs Kok Date: Sat, 13 Jul 2024 21:06:24 +0200 Subject: [PATCH 12/14] chore: update `ruff` configuration to resolve deprecation warning (#4346) * chore: update `ruff` configuration to resolve deprecation warning * add newsfragment --- newsfragments/4346.changed.md | 1 + pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 newsfragments/4346.changed.md diff --git a/newsfragments/4346.changed.md b/newsfragments/4346.changed.md new file mode 100644 index 00000000000..1ac7952eae7 --- /dev/null +++ b/newsfragments/4346.changed.md @@ -0,0 +1 @@ +chore: update `ruff` configuration to resolve deprecation warning diff --git a/pyproject.toml b/pyproject.toml index 26cbfee0064..164296ce234 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,4 @@ -[tool.ruff.extend-per-file-ignores] +[tool.ruff.lint.extend-per-file-ignores] "__init__.py" = ["F403"] [tool.towncrier] From 2baac93fd994d9ac750a1cc35ee3d763f00e28fb Mon Sep 17 00:00:00 2001 From: Icxolu <10486322+Icxolu@users.noreply.github.com> Date: Mon, 15 Jul 2024 21:00:54 +0200 Subject: [PATCH 13/14] only emit c-string literals on Rust 1.79 and later (#4352) (#4353) * only emit c-string literals on Rust 1.79 and later (#4352) * add newsfragment --- newsfragments/4353.fixed.md | 1 + pyo3-build-config/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 newsfragments/4353.fixed.md diff --git a/newsfragments/4353.fixed.md b/newsfragments/4353.fixed.md new file mode 100644 index 00000000000..73783479085 --- /dev/null +++ b/newsfragments/4353.fixed.md @@ -0,0 +1 @@ +fixed compile error due to c-string literals on Rust < 1.79 \ No newline at end of file diff --git a/pyo3-build-config/src/lib.rs b/pyo3-build-config/src/lib.rs index 2da3e56d3b6..0bc2274e0e5 100644 --- a/pyo3-build-config/src/lib.rs +++ b/pyo3-build-config/src/lib.rs @@ -143,7 +143,7 @@ pub fn print_feature_cfgs() { println!("cargo:rustc-cfg=invalid_from_utf8_lint"); } - if rustc_minor_version >= 77 { + if rustc_minor_version >= 79 { println!("cargo:rustc-cfg=c_str_lit"); } From 673f3edc4d1313009281d375c9e96c049b8c1d7a Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 16 Jul 2024 09:01:29 +0100 Subject: [PATCH 14/14] release: 0.22.2 --- CHANGELOG.md | 17 +++++++++++++++++ Cargo.toml | 8 ++++---- README.md | 4 ++-- examples/decorator/.template/pre-script.rhai | 2 +- .../maturin-starter/.template/pre-script.rhai | 2 +- examples/plugin/.template/pre-script.rhai | 2 +- .../.template/pre-script.rhai | 2 +- examples/word-count/.template/pre-script.rhai | 2 +- newsfragments/4324.changed.md | 1 - newsfragments/4327.packaging.md | 3 --- newsfragments/4330.changed.md | 1 - newsfragments/4346.changed.md | 1 - newsfragments/4353.fixed.md | 1 - pyo3-build-config/Cargo.toml | 2 +- pyo3-ffi/Cargo.toml | 4 ++-- pyo3-macros-backend/Cargo.toml | 6 +++--- pyo3-macros/Cargo.toml | 4 ++-- pyproject.toml | 2 +- tests/ui/reject_generics.stderr | 4 ++-- 19 files changed, 39 insertions(+), 29 deletions(-) delete mode 100644 newsfragments/4324.changed.md delete mode 100644 newsfragments/4327.packaging.md delete mode 100644 newsfragments/4330.changed.md delete mode 100644 newsfragments/4346.changed.md delete mode 100644 newsfragments/4353.fixed.md diff --git a/CHANGELOG.md b/CHANGELOG.md index beabedc28de..c0d612c2e75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,23 @@ To see unreleased changes, please see the [CHANGELOG on the main branch guide](h +## [0.22.2] - 2024-07-17 + +### Packaging + +- Require opt-in to freethreaded Python using the `UNSAFE_PYO3_BUILD_FREE_THREADED=1` environment variable (it is not yet supported by PyO3). [#4327](https://github.com/PyO3/pyo3/pull/4327) + +### Changed + +- Use FFI function calls for reference counting on all abi3 versions. [#4324](https://github.com/PyO3/pyo3/pull/4324) +- `#[pymodule(...)]` now directly accepts all relevant `#[pyo3(...)]` options. [#4330](https://github.com/PyO3/pyo3/pull/4330) + +### Fixed + +- Fix compile failure in declarative `#[pymodule]` under presence of `#![no_implicit_prelude]`. [#4328](https://github.com/PyO3/pyo3/pull/4328) +- Fix compile failure due to c-string literals on Rust < 1.79. [#4353](https://github.com/PyO3/pyo3/pull/4353) + + ## [0.22.1] - 2024-07-06 ### Added diff --git a/Cargo.toml b/Cargo.toml index 617400c9637..c7446c7da5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3" -version = "0.22.1" +version = "0.22.2" description = "Bindings to Python interpreter" authors = ["PyO3 Project and Contributors "] readme = "README.md" @@ -21,10 +21,10 @@ memoffset = "0.9" once_cell = "1.13" # ffi bindings to the python interpreter, split into a separate crate so they can be used independently -pyo3-ffi = { path = "pyo3-ffi", version = "=0.22.1" } +pyo3-ffi = { path = "pyo3-ffi", version = "=0.22.2" } # support crates for macros feature -pyo3-macros = { path = "pyo3-macros", version = "=0.22.1", optional = true } +pyo3-macros = { path = "pyo3-macros", version = "=0.22.2", optional = true } indoc = { version = "2.0.1", optional = true } unindent = { version = "0.2.1", optional = true } @@ -63,7 +63,7 @@ rayon = "1.6.1" futures = "0.3.28" [build-dependencies] -pyo3-build-config = { path = "pyo3-build-config", version = "=0.22.1", features = ["resolve-config"] } +pyo3-build-config = { path = "pyo3-build-config", version = "=0.22.2", features = ["resolve-config"] } [features] default = ["macros"] diff --git a/README.md b/README.md index fd4acdd4154..2a6348437a2 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ name = "string_sum" crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.22.1", features = ["extension-module"] } +pyo3 = { version = "0.22.2", features = ["extension-module"] } ``` **`src/lib.rs`** @@ -137,7 +137,7 @@ Start a new project with `cargo new` and add `pyo3` to the `Cargo.toml` like th ```toml [dependencies.pyo3] -version = "0.22.1" +version = "0.22.2" features = ["auto-initialize"] ``` diff --git a/examples/decorator/.template/pre-script.rhai b/examples/decorator/.template/pre-script.rhai index f21daafeb2a..a9f0eb4a289 100644 --- a/examples/decorator/.template/pre-script.rhai +++ b/examples/decorator/.template/pre-script.rhai @@ -1,4 +1,4 @@ -variable::set("PYO3_VERSION", "0.22.1"); +variable::set("PYO3_VERSION", "0.22.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 f21daafeb2a..a9f0eb4a289 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.22.1"); +variable::set("PYO3_VERSION", "0.22.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 086868dfc42..bbc96358a23 100644 --- a/examples/plugin/.template/pre-script.rhai +++ b/examples/plugin/.template/pre-script.rhai @@ -1,4 +1,4 @@ -variable::set("PYO3_VERSION", "0.22.1"); +variable::set("PYO3_VERSION", "0.22.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 0679e89ab3f..d28d9d09987 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.22.1"); +variable::set("PYO3_VERSION", "0.22.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 f21daafeb2a..a9f0eb4a289 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.22.1"); +variable::set("PYO3_VERSION", "0.22.2"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/pyproject.toml", "pyproject.toml"); file::delete(".template"); diff --git a/newsfragments/4324.changed.md b/newsfragments/4324.changed.md deleted file mode 100644 index 2818159dda3..00000000000 --- a/newsfragments/4324.changed.md +++ /dev/null @@ -1 +0,0 @@ -Use FFI function calls for reference counting on all abi3 versions. diff --git a/newsfragments/4327.packaging.md b/newsfragments/4327.packaging.md deleted file mode 100644 index c98d06f7cba..00000000000 --- a/newsfragments/4327.packaging.md +++ /dev/null @@ -1,3 +0,0 @@ -This PR lets PyO3 checks `Py_GIL_DISABLED` build flag and prevents `pyo3-ffi` crate building against GIL-less Python, -unless -explicitly opt using the `UNSAFE_PYO3_BUILD_FREE_THREADED` environment flag. \ No newline at end of file diff --git a/newsfragments/4330.changed.md b/newsfragments/4330.changed.md deleted file mode 100644 index d465ec99cec..00000000000 --- a/newsfragments/4330.changed.md +++ /dev/null @@ -1 +0,0 @@ -`#[pymodule(...)]` now directly accepts all relevant `#[pyo3(...)]` options. diff --git a/newsfragments/4346.changed.md b/newsfragments/4346.changed.md deleted file mode 100644 index 1ac7952eae7..00000000000 --- a/newsfragments/4346.changed.md +++ /dev/null @@ -1 +0,0 @@ -chore: update `ruff` configuration to resolve deprecation warning diff --git a/newsfragments/4353.fixed.md b/newsfragments/4353.fixed.md deleted file mode 100644 index 73783479085..00000000000 --- a/newsfragments/4353.fixed.md +++ /dev/null @@ -1 +0,0 @@ -fixed compile error due to c-string literals on Rust < 1.79 \ No newline at end of file diff --git a/pyo3-build-config/Cargo.toml b/pyo3-build-config/Cargo.toml index d8c84685c7c..e7b180facff 100644 --- a/pyo3-build-config/Cargo.toml +++ b/pyo3-build-config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-build-config" -version = "0.22.1" +version = "0.22.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 85de25c80f0..ee7f6765cf6 100644 --- a/pyo3-ffi/Cargo.toml +++ b/pyo3-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-ffi" -version = "0.22.1" +version = "0.22.2" description = "Python-API bindings for the PyO3 ecosystem" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] @@ -38,7 +38,7 @@ abi3-py312 = ["abi3", "pyo3-build-config/abi3-py312"] generate-import-lib = ["pyo3-build-config/python3-dll-a"] [build-dependencies] -pyo3-build-config = { path = "../pyo3-build-config", version = "=0.22.1", features = ["resolve-config"] } +pyo3-build-config = { path = "../pyo3-build-config", version = "=0.22.2", features = ["resolve-config"] } [lints] workspace = true diff --git a/pyo3-macros-backend/Cargo.toml b/pyo3-macros-backend/Cargo.toml index 280e12e37a6..09a662becd2 100644 --- a/pyo3-macros-backend/Cargo.toml +++ b/pyo3-macros-backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-macros-backend" -version = "0.22.1" +version = "0.22.2" description = "Code generation for PyO3 package" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] @@ -16,7 +16,7 @@ edition = "2021" [dependencies] heck = "0.5" proc-macro2 = { version = "1.0.60", default-features = false } -pyo3-build-config = { path = "../pyo3-build-config", version = "=0.22.1", features = ["resolve-config"] } +pyo3-build-config = { path = "../pyo3-build-config", version = "=0.22.2", features = ["resolve-config"] } quote = { version = "1", default-features = false } [dependencies.syn] @@ -25,7 +25,7 @@ default-features = false features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"] [build-dependencies] -pyo3-build-config = { path = "../pyo3-build-config", version = "=0.22.1" } +pyo3-build-config = { path = "../pyo3-build-config", version = "=0.22.2" } [lints] workspace = true diff --git a/pyo3-macros/Cargo.toml b/pyo3-macros/Cargo.toml index 23da8ccd697..98003c67272 100644 --- a/pyo3-macros/Cargo.toml +++ b/pyo3-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-macros" -version = "0.22.1" +version = "0.22.2" description = "Proc macros for PyO3 package" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] @@ -22,7 +22,7 @@ gil-refs = ["pyo3-macros-backend/gil-refs"] 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.22.1" } +pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.22.2" } [lints] workspace = true diff --git a/pyproject.toml b/pyproject.toml index 164296ce234..c5c4c9261e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ [tool.towncrier] filename = "CHANGELOG.md" -version = "0.22.1" +version = "0.22.2" start_string = "\n" template = ".towncrier.template.md" title_format = "## [{version}] - {project_date}" diff --git a/tests/ui/reject_generics.stderr b/tests/ui/reject_generics.stderr index 78c3d00e624..7379299aea4 100644 --- a/tests/ui/reject_generics.stderr +++ b/tests/ui/reject_generics.stderr @@ -1,10 +1,10 @@ -error: #[pyclass] cannot have generic parameters. For an explanation, see https://pyo3.rs/v0.23.0-dev/class.html#no-generic-parameters +error: #[pyclass] cannot have generic parameters. For an explanation, see https://pyo3.rs/v0.22.2/class.html#no-generic-parameters --> tests/ui/reject_generics.rs:4:25 | 4 | struct ClassWithGenerics { | ^ -error: #[pyclass] cannot have lifetime parameters. For an explanation, see https://pyo3.rs/v0.23.0-dev/class.html#no-lifetime-parameters +error: #[pyclass] cannot have lifetime parameters. For an explanation, see https://pyo3.rs/v0.22.2/class.html#no-lifetime-parameters --> tests/ui/reject_generics.rs:9:27 | 9 | struct ClassWithLifetimes<'a> {