Compare commits

...

14 Commits

Author SHA1 Message Date
Paul Stemmet 1a65112ba7
src/impl_: add ff unsafe-allow-subinterpreters
Allow the subinterpreter safeguards to be disabled, so that applications
like Ceph's manager can continue to use pyo3 modules without soft
crashing.

Enabling this feature should be done with caution, as any storage of Py
objects in rust statics can lead to undefined behavior.

However, not all consumers of pyo3 use global state, and thus a subset
of them (such as python-bcrypt) are safe to use in subinterpreter
contexts.

References: https://github.com/bazaah/aur-ceph/issues/20
References: https://github.com/PyO3/pyo3/pull/2523
References: https://github.com/pyca/cryptography/issues/9016
References: https://github.com/PyO3/pyo3/discussions/2346#discussioncomment-3246505
References: https://github.com/PyO3/pyo3/discussions/2346#discussioncomment-2911159
References: https://github.com/PyO3/pyo3/issues/3451
Signed-off-by: Paul Stemmet <github@luxolus.com>
2024-07-12 10:32:10 +00:00
David Hewitt a646a820fd release: 0.21.2 2024-04-16 08:57:37 +01:00
David Hewitt bd15f92a56 Deprecate the `PySet::empty` gil-ref constructor (#4082)
* Deprecate the `PySet::empty` gil-ref constructor

* add newsfragment
2024-04-16 08:57:37 +01:00
Adam Reichold 4f7c7065c2 Suppress non_local_definitions lint as we often want the non-local effects in macro code (#4074)
* Resolve references to legacy numerical constants and use the associated constants instead

* Suppress non_local_definitions lint as we often want the non-local effects in macro code
2024-04-16 08:57:37 +01:00
Adam Reichold 3f6faf00ff Extend guide on interaction between method receivers and lifetime elision. (#4069) 2024-04-16 08:57:37 +01:00
messense 4e13c0eae8 Link libpython for AIX target (#4073) 2024-04-16 08:57:37 +01:00
Icxolu e501377587 fix declarative-modules compile error (#4054)
* fix declarative-modules compile error

* add newsfragment
2024-04-16 08:57:37 +01:00
Icxolu fd18955da8 add `#[doc(hidden)]` to the Rust module created by `#[pymodule]` (#4067)
* add `#[doc(hidden)]` to the Rust module created by `#[pymodule]`

* add newsfragment
2024-04-16 08:57:37 +01:00
Liam e174a04bb3 Minor: Fix a typo in Contributing.md (#4066) 2024-04-16 08:57:37 +01:00
dependabot[bot] a8abe508ba build(deps): bump peaceiris/actions-gh-pages from 3 to 4 (#4062)
Bumps [peaceiris/actions-gh-pages](https://github.com/peaceiris/actions-gh-pages) from 3 to 4.
- [Release notes](https://github.com/peaceiris/actions-gh-pages/releases)
- [Changelog](https://github.com/peaceiris/actions-gh-pages/blob/main/CHANGELOG.md)
- [Commits](https://github.com/peaceiris/actions-gh-pages/compare/v3...v4)

---
updated-dependencies:
- dependency-name: peaceiris/actions-gh-pages
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 08:57:37 +01:00
Jeong, Heon 68d19738e7 Fix typo (#4052)
Fix incorrect closing brackets
2024-04-16 08:57:37 +01:00
Joseph Perez b8ec43e469 fix: allow impl of Ungil for deprecated PyCell in nightly (#4053) 2024-04-16 08:57:37 +01:00
Icxolu 0b888cb934 add descriptive error msg for `__traverse__` receivers other than `&self` (#4045)
* add descriptive error msg for `__traverse__` receivers other than `self`

* add newsfragment

* improve error message
2024-04-16 08:57:37 +01:00
David Hewitt e4cc98607e fix compile error for multiple async method arguments (#4035) 2024-04-16 08:57:37 +01:00
39 changed files with 272 additions and 127 deletions

View File

@ -46,7 +46,7 @@ jobs:
- name: Deploy docs and the guide
if: ${{ github.event_name == 'release' }}
uses: peaceiris/actions-gh-pages@v3
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./target/guide/

View File

@ -10,6 +10,20 @@ To see unreleased changes, please see the [CHANGELOG on the main branch guide](h
<!-- towncrier release notes start -->
## [0.21.2] - 2024-04-16
### Changed
- Deprecate the `PySet::empty()` gil-ref constructor. [#4082](https://github.com/PyO3/pyo3/pull/4082)
### Fixed
- Fix compile error for `async fn` in `#[pymethods]` with a `&self` receiver and more than one additional argument. [#4035](https://github.com/PyO3/pyo3/pull/4035)
- Improve error message for wrong receiver type in `__traverse__`. [#4045](https://github.com/PyO3/pyo3/pull/4045)
- Fix compile error when exporting a `#[pyclass]` living in a different Rust module using the `experimental-declarative-modules` feature. [#4054](https://github.com/PyO3/pyo3/pull/4054)
- Fix `missing_docs` lint triggering on documented `#[pymodule]` functions. [#4067](https://github.com/PyO3/pyo3/pull/4067)
- Fix undefined symbol errors for extension modules on AIX (by linking `libpython`). [#4073](https://github.com/PyO3/pyo3/pull/4073)
## [0.21.1] - 2024-04-01
### Added
@ -1731,7 +1745,8 @@ Yanked
- Initial release
[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.21.1...HEAD
[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.21.2...HEAD
[0.21.2]: https://github.com/pyo3/pyo3/compare/v0.21.1...v0.21.2
[0.21.1]: https://github.com/pyo3/pyo3/compare/v0.21.0...v0.21.1
[0.21.0]: https://github.com/pyo3/pyo3/compare/v0.20.3...v0.21.0
[0.21.0-beta.0]: https://github.com/pyo3/pyo3/compare/v0.20.3...v0.21.0-beta.0

View File

@ -1,6 +1,6 @@
[package]
name = "pyo3"
version = "0.21.1"
version = "0.21.2"
description = "Bindings to Python interpreter"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
readme = "README.md"
@ -22,10 +22,10 @@ memoffset = "0.9"
portable-atomic = "1.0"
# ffi bindings to the python interpreter, split into a separate crate so they can be used independently
pyo3-ffi = { path = "pyo3-ffi", version = "=0.21.1" }
pyo3-ffi = { path = "pyo3-ffi", version = "=0.21.2" }
# support crates for macros feature
pyo3-macros = { path = "pyo3-macros", version = "=0.21.1", optional = true }
pyo3-macros = { path = "pyo3-macros", version = "=0.21.2", optional = true }
indoc = { version = "2.0.1", optional = true }
unindent = { version = "0.2.1", optional = true }
@ -60,7 +60,7 @@ rayon = "1.6.1"
futures = "0.3.28"
[build-dependencies]
pyo3-build-config = { path = "pyo3-build-config", version = "=0.21.1", features = ["resolve-config"] }
pyo3-build-config = { path = "pyo3-build-config", version = "=0.21.2", features = ["resolve-config"] }
[features]
default = ["macros"]
@ -109,6 +109,9 @@ gil-refs = []
# Optimizes PyObject to Vec conversion and so on.
nightly = []
# Disables the checks for use in subinterpreters.
unsafe-allow-subinterpreters = []
# Activates all additional features
# This is mostly intended for testing purposes - activating *all* of these isn't particularly useful.
full = [

View File

@ -190,7 +190,7 @@ Second, there is a Python-based benchmark contained in the `pytests` subdirector
You can view what code is and isn't covered by PyO3's tests. We aim to have 100% coverage - please check coverage and add tests if you notice a lack of coverage!
- First, ensure the llmv-cov cargo plugin is installed. You may need to run the plugin through cargo once before using it with `nox`.
- First, ensure the llvm-cov cargo plugin is installed. You may need to run the plugin through cargo once before using it with `nox`.
```shell
cargo install cargo-llvm-cov
cargo llvm-cov

View File

@ -68,7 +68,7 @@ name = "string_sum"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.21.1", features = ["extension-module"] }
pyo3 = { version = "0.21.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.21.1"
version = "0.21.2"
features = ["auto-initialize"]
```

View File

@ -1,4 +1,4 @@
variable::set("PYO3_VERSION", "0.21.1");
variable::set("PYO3_VERSION", "0.21.2");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/pyproject.toml", "pyproject.toml");
file::delete(".template");

View File

@ -1,4 +1,4 @@
variable::set("PYO3_VERSION", "0.21.1");
variable::set("PYO3_VERSION", "0.21.2");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/pyproject.toml", "pyproject.toml");
file::delete(".template");

View File

@ -1,4 +1,4 @@
variable::set("PYO3_VERSION", "0.21.1");
variable::set("PYO3_VERSION", "0.21.2");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/plugin_api/Cargo.toml", "plugin_api/Cargo.toml");
file::delete(".template");

View File

@ -1,4 +1,4 @@
variable::set("PYO3_VERSION", "0.21.1");
variable::set("PYO3_VERSION", "0.21.2");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/setup.cfg", "setup.cfg");
file::delete(".template");

View File

@ -1,4 +1,4 @@
variable::set("PYO3_VERSION", "0.21.1");
variable::set("PYO3_VERSION", "0.21.2");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/pyproject.toml", "pyproject.toml");
file::delete(".template");

View File

@ -998,6 +998,44 @@ impl MyClass {
Note that `text_signature` on `#[new]` is not compatible with compilation in
`abi3` mode until Python 3.10 or greater.
### Method receivers and lifetime elision
PyO3 supports writing instance methods using the normal method receivers for shared `&self` and unique `&mut self` references. This interacts with [lifetime elision][lifetime-elision] insofar as the lifetime of a such a receiver is assigned to all elided output lifetime parameters.
This is a good default for general Rust code where return values are more likely to borrow from the receiver than from the other arguments, if they contain any lifetimes at all. However, when returning bound references `Bound<'py, T>` in PyO3-based code, the GIL lifetime `'py` should usually be derived from a GIL token `py: Python<'py>` passed as an argument instead of the receiver.
Specifically, signatures like
```rust,ignore
fn frobnicate(&self, py: Python) -> Bound<Foo>;
```
will not work as they are inferred as
```rust,ignore
fn frobnicate<'a, 'py>(&'a self, py: Python<'py>) -> Bound<'a, Foo>;
```
instead of the intended
```rust,ignore
fn frobnicate<'a, 'py>(&'a self, py: Python<'py>) -> Bound<'py, Foo>;
```
and should usually be written as
```rust,ignore
fn frobnicate<'py>(&self, py: Python<'py>) -> Bound<'py, Foo>;
```
The same problem does not exist for `#[pyfunction]`s as the special case for receiver lifetimes does not apply and indeed a signature like
```rust,ignore
fn frobnicate(bar: &Bar, py: Python) -> Bound<Foo>;
```
will yield compiler error [E0106 "missing lifetime specifier"][compiler-error-e0106].
## `#[pyclass]` enums
Enum support in PyO3 comes in two flavors, depending on what kind of variants the enum has: simple and complex.
@ -1329,3 +1367,6 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
[classattr]: https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables
[`multiple-pymethods`]: features.md#multiple-pymethods
[lifetime-elision]: https://doc.rust-lang.org/reference/lifetime-elision.html
[compiler-error-e0106]: https://doc.rust-lang.org/error_codes/E0106.html

View File

@ -120,7 +120,7 @@ Export an async function that makes use of `async-std`:
use pyo3::{prelude::*, wrap_pyfunction};
#[pyfunction]
fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>>> {
fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>> {
pyo3_asyncio::async_std::future_into_py(py, async {
async_std::task::sleep(std::time::Duration::from_secs(1)).await;
Ok(Python::with_gil(|py| py.None()))

View File

@ -1,6 +1,6 @@
[package]
name = "pyo3-build-config"
version = "0.21.1"
version = "0.21.2"
description = "Build configuration for the PyO3 ecosystem"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"]

View File

@ -775,6 +775,8 @@ pub fn is_linking_libpython() -> bool {
/// Must be called from a PyO3 crate build script.
fn is_linking_libpython_for_target(target: &Triple) -> bool {
target.operating_system == OperatingSystem::Windows
// See https://github.com/PyO3/pyo3/issues/4068#issuecomment-2051159852
|| target.operating_system == OperatingSystem::Aix
|| target.environment == Environment::Android
|| target.environment == Environment::Androideabi
|| !is_extension_module()

View File

@ -1,6 +1,6 @@
[package]
name = "pyo3-ffi"
version = "0.21.1"
version = "0.21.2"
description = "Python-API bindings for the PyO3 ecosystem"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
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.21.1", features = ["resolve-config"] }
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.21.2", features = ["resolve-config"] }
[lints]
workspace = true

View File

@ -11,8 +11,8 @@ pub type Py_ssize_t = ::libc::ssize_t;
pub type Py_hash_t = Py_ssize_t;
pub type Py_uhash_t = ::libc::size_t;
pub const PY_SSIZE_T_MIN: Py_ssize_t = std::isize::MIN as Py_ssize_t;
pub const PY_SSIZE_T_MAX: Py_ssize_t = std::isize::MAX as Py_ssize_t;
pub const PY_SSIZE_T_MIN: Py_ssize_t = isize::MIN as Py_ssize_t;
pub const PY_SSIZE_T_MAX: Py_ssize_t = isize::MAX as Py_ssize_t;
#[cfg(target_endian = "big")]
pub const PY_BIG_ENDIAN: usize = 1;

View File

@ -1,6 +1,6 @@
[package]
name = "pyo3-macros-backend"
version = "0.21.1"
version = "0.21.2"
description = "Code generation for PyO3 package"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"]
@ -16,7 +16,7 @@ edition = "2021"
[dependencies]
heck = "0.4"
proc-macro2 = { version = "1", default-features = false }
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.21.1", features = ["resolve-config"] }
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.21.2", features = ["resolve-config"] }
quote = { version = "1", default-features = false }
[dependencies.syn]

View File

@ -522,33 +522,22 @@ impl<'a> FnSpec<'a> {
Some(cls) => quote!(Some(<#cls as #pyo3_path::PyTypeInfo>::NAME)),
None => quote!(None),
};
let evaluate_args = || -> (Vec<Ident>, TokenStream) {
let mut arg_names = Vec::with_capacity(args.len());
let mut evaluate_arg = quote! {};
for arg in &args {
let arg_name = format_ident!("arg_{}", arg_names.len());
arg_names.push(arg_name.clone());
evaluate_arg.extend(quote! {
let #arg_name = #arg
});
}
(arg_names, evaluate_arg)
};
let arg_names = (0..args.len())
.map(|i| format_ident!("arg_{}", i))
.collect::<Vec<_>>();
let future = match self.tp {
FnType::Fn(SelfType::Receiver { mutable: false, .. }) => {
let (arg_name, evaluate_arg) = evaluate_args();
quote! {{
#evaluate_arg;
#(let #arg_names = #args;)*
let __guard = #pyo3_path::impl_::coroutine::RefGuard::<#cls>::new(&#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf))?;
async move { function(&__guard, #(#arg_name),*).await }
async move { function(&__guard, #(#arg_names),*).await }
}}
}
FnType::Fn(SelfType::Receiver { mutable: true, .. }) => {
let (arg_name, evaluate_arg) = evaluate_args();
quote! {{
#evaluate_arg;
#(let #arg_names = #args;)*
let mut __guard = #pyo3_path::impl_::coroutine::RefMutGuard::<#cls>::new(&#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf))?;
async move { function(&mut __guard, #(#arg_name),*).await }
async move { function(&mut __guard, #(#arg_names),*).await }
}}
}
_ => {

View File

@ -249,6 +249,7 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
#initialization
#[allow(unknown_lints, non_local_definitions)]
impl MakeDef {
const fn make_def() -> #pyo3_path::impl_::pymodule::ModuleDef {
use #pyo3_path::impl_::pymodule as impl_;
@ -324,6 +325,7 @@ pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result<TokenStream>
Ok(quote! {
#function
#[doc(hidden)]
#vis mod #ident {
#initialization
}
@ -332,6 +334,7 @@ pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result<TokenStream>
// this avoids complications around the fact that the generated module has a different scope
// (and `super` doesn't always refer to the outer scope, e.g. if the `#[pymodule] is
// inside a function body)
#[allow(unknown_lints, non_local_definitions)]
impl #ident::MakeDef {
const fn make_def() -> #pyo3_path::impl_::pymodule::ModuleDef {
fn __pyo3_pymodule(module: &#pyo3_path::Bound<'_, #pyo3_path::types::PyModule>) -> #pyo3_path::PyResult<()> {

View File

@ -1614,7 +1614,7 @@ impl<'a> PyClassImplsBuilder<'a> {
quote! {
impl #cls {
#[doc(hidden)]
const _PYO3_DEF: #pyo3_path::impl_::pymodule::AddClassToModule<Self> = #pyo3_path::impl_::pymodule::AddClassToModule::new();
pub const _PYO3_DEF: #pyo3_path::impl_::pymodule::AddClassToModule<Self> = #pyo3_path::impl_::pymodule::AddClassToModule::new();
}
}
}

View File

@ -273,6 +273,7 @@ pub fn impl_wrap_pyfunction(
// this avoids complications around the fact that the generated module has a different scope
// (and `super` doesn't always refer to the outer scope, e.g. if the `#[pyfunction] is
// inside a function body)
#[allow(unknown_lints, non_local_definitions)]
impl #name::MakeDef {
const _PYO3_DEF: #pyo3_path::impl_::pymethods::PyMethodDef = #methoddef;
}

View File

@ -366,6 +366,7 @@ pub fn impl_py_method_def_new(
#deprecations
use #pyo3_path::impl_::pyclass::*;
#[allow(unknown_lints, non_local_definitions)]
impl PyClassNewTextSignature<#cls> for PyClassImplCollector<#cls> {
#[inline]
fn new_text_signature(self) -> ::std::option::Option<&'static str> {
@ -434,9 +435,24 @@ fn impl_traverse_slot(
let Ctx { pyo3_path } = ctx;
if let (Some(py_arg), _) = split_off_python_arg(&spec.signature.arguments) {
return Err(syn::Error::new_spanned(py_arg.ty, "__traverse__ may not take `Python`. \
Usually, an implementation of `__traverse__` should do nothing but calls to `visit.call`. \
Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, \
i.e. `Python::with_gil` will panic."));
Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \
should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited \
inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic."));
}
// check that the receiver does not try to smuggle an (implicit) `Python` token into here
if let FnType::Fn(SelfType::TryFromBoundRef(span))
| FnType::Fn(SelfType::Receiver {
mutable: true,
span,
}) = spec.tp
{
bail_spanned! { span =>
"__traverse__ may not take a receiver other than `&self`. Usually, an implementation of \
`__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \
should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited \
inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic."
}
}
let rust_fn_ident = spec.name;

View File

@ -1,6 +1,6 @@
[package]
name = "pyo3-macros"
version = "0.21.1"
version = "0.21.2"
description = "Proc macros for PyO3 package"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"]
@ -22,7 +22,7 @@ experimental-declarative-modules = []
proc-macro2 = { version = "1", default-features = false }
quote = "1"
syn = { version = "2", features = ["full", "extra-traits"] }
pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.21.1" }
pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.21.2" }
[lints]
workspace = true

View File

@ -3,7 +3,7 @@
[tool.towncrier]
filename = "CHANGELOG.md"
version = "0.21.1"
version = "0.21.2"
start_string = "<!-- towncrier release notes start -->\n"
template = ".towncrier.template.md"
title_format = "## [{version}] - {project_date}"

View File

@ -4,7 +4,6 @@ use crate::err::{PyErr, PyResult};
use crate::exceptions::PyOverflowError;
use crate::ffi::{self, Py_hash_t};
use crate::{IntoPy, PyObject, Python};
use std::isize;
use std::os::raw::c_int;
/// A type which can be the return type of a python C-API callback
@ -85,11 +84,7 @@ impl IntoPyCallbackOutput<()> for () {
impl IntoPyCallbackOutput<ffi::Py_ssize_t> for usize {
#[inline]
fn convert(self, _py: Python<'_>) -> PyResult<ffi::Py_ssize_t> {
if self <= (isize::MAX as usize) {
Ok(self as isize)
} else {
Err(PyOverflowError::new_err(()))
}
self.try_into().map_err(|_err| PyOverflowError::new_err(()))
}
}

View File

@ -445,7 +445,7 @@ mod test_128bit_integers {
#[test]
fn test_i128_max() {
Python::with_gil(|py| {
let v = std::i128::MAX;
let v = i128::MAX;
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<i128>(py).unwrap());
assert_eq!(v as u128, obj.extract::<u128>(py).unwrap());
@ -456,7 +456,7 @@ mod test_128bit_integers {
#[test]
fn test_i128_min() {
Python::with_gil(|py| {
let v = std::i128::MIN;
let v = i128::MIN;
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<i128>(py).unwrap());
assert!(obj.extract::<i64>(py).is_err());
@ -467,7 +467,7 @@ mod test_128bit_integers {
#[test]
fn test_u128_max() {
Python::with_gil(|py| {
let v = std::u128::MAX;
let v = u128::MAX;
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<u128>(py).unwrap());
assert!(obj.extract::<i128>(py).is_err());
@ -495,7 +495,7 @@ mod test_128bit_integers {
#[test]
fn test_nonzero_i128_max() {
Python::with_gil(|py| {
let v = NonZeroI128::new(std::i128::MAX).unwrap();
let v = NonZeroI128::new(i128::MAX).unwrap();
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<NonZeroI128>(py).unwrap());
assert_eq!(
@ -509,7 +509,7 @@ mod test_128bit_integers {
#[test]
fn test_nonzero_i128_min() {
Python::with_gil(|py| {
let v = NonZeroI128::new(std::i128::MIN).unwrap();
let v = NonZeroI128::new(i128::MIN).unwrap();
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<NonZeroI128>(py).unwrap());
assert!(obj.extract::<NonZeroI64>(py).is_err());
@ -520,7 +520,7 @@ mod test_128bit_integers {
#[test]
fn test_nonzero_u128_max() {
Python::with_gil(|py| {
let v = NonZeroU128::new(std::u128::MAX).unwrap();
let v = NonZeroU128::new(u128::MAX).unwrap();
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<NonZeroU128>(py).unwrap());
assert!(obj.extract::<NonZeroI128>(py).is_err());
@ -573,7 +573,7 @@ mod tests {
#[test]
fn test_u32_max() {
Python::with_gil(|py| {
let v = std::u32::MAX;
let v = u32::MAX;
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<u32>(py).unwrap());
assert_eq!(u64::from(v), obj.extract::<u64>(py).unwrap());
@ -584,7 +584,7 @@ mod tests {
#[test]
fn test_i64_max() {
Python::with_gil(|py| {
let v = std::i64::MAX;
let v = i64::MAX;
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<i64>(py).unwrap());
assert_eq!(v as u64, obj.extract::<u64>(py).unwrap());
@ -595,7 +595,7 @@ mod tests {
#[test]
fn test_i64_min() {
Python::with_gil(|py| {
let v = std::i64::MIN;
let v = i64::MIN;
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<i64>(py).unwrap());
assert!(obj.extract::<i32>(py).is_err());
@ -606,7 +606,7 @@ mod tests {
#[test]
fn test_u64_max() {
Python::with_gil(|py| {
let v = std::u64::MAX;
let v = u64::MAX;
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<u64>(py).unwrap());
assert!(obj.extract::<i64>(py).is_err());
@ -664,7 +664,7 @@ mod tests {
#[test]
fn test_nonzero_u32_max() {
Python::with_gil(|py| {
let v = NonZeroU32::new(std::u32::MAX).unwrap();
let v = NonZeroU32::new(u32::MAX).unwrap();
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<NonZeroU32>(py).unwrap());
assert_eq!(NonZeroU64::from(v), obj.extract::<NonZeroU64>(py).unwrap());
@ -675,7 +675,7 @@ mod tests {
#[test]
fn test_nonzero_i64_max() {
Python::with_gil(|py| {
let v = NonZeroI64::new(std::i64::MAX).unwrap();
let v = NonZeroI64::new(i64::MAX).unwrap();
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<NonZeroI64>(py).unwrap());
assert_eq!(
@ -689,7 +689,7 @@ mod tests {
#[test]
fn test_nonzero_i64_min() {
Python::with_gil(|py| {
let v = NonZeroI64::new(std::i64::MIN).unwrap();
let v = NonZeroI64::new(i64::MIN).unwrap();
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<NonZeroI64>(py).unwrap());
assert!(obj.extract::<NonZeroI32>(py).is_err());
@ -700,7 +700,7 @@ mod tests {
#[test]
fn test_nonzero_u64_max() {
Python::with_gil(|py| {
let v = NonZeroU64::new(std::u64::MAX).unwrap();
let v = NonZeroU64::new(u64::MAX).unwrap();
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<NonZeroU64>(py).unwrap());
assert!(obj.extract::<NonZeroI64>(py).is_err());

View File

@ -852,6 +852,7 @@ slot_fragment_trait! {
#[macro_export]
macro_rules! generate_pyclass_richcompare_slot {
($cls:ty) => {{
#[allow(unknown_lints, non_local_definitions)]
impl $cls {
#[allow(non_snake_case)]
unsafe extern "C" fn __pymethod___richcmp____(

View File

@ -102,7 +102,7 @@ impl ModuleDef {
// that static data is not reused across interpreters.
//
// PyPy does not have subinterpreters, so no need to check interpreter ID.
#[cfg(not(any(PyPy, GraalPy)))]
#[cfg(not(any(PyPy, GraalPy, feature = "unsafe-allow-subinterpreters")))]
{
// PyInterpreterState_Get is only available on 3.9 and later, but is missing
// from python3.dll for Windows stable API on 3.9

View File

@ -280,6 +280,7 @@ mod nightly {
impl !Ungil for crate::PyAny {}
// All the borrowing wrappers
#[allow(deprecated)]
impl<T> !Ungil for crate::PyCell<T> {}
impl<T> !Ungil for crate::PyRef<'_, T> {}
impl<T> !Ungil for crate::PyRefMut<'_, T> {}

View File

@ -55,7 +55,7 @@ struct BorrowFlag(usize);
impl BorrowFlag {
pub(crate) const UNUSED: BorrowFlag = BorrowFlag(0);
const HAS_MUTABLE_BORROW: BorrowFlag = BorrowFlag(usize::max_value());
const HAS_MUTABLE_BORROW: BorrowFlag = BorrowFlag(usize::MAX);
const fn increment(self) -> Self {
Self(self.0 + 1)
}

View File

@ -2491,6 +2491,7 @@ class SimpleClass:
#[cfg(feature = "macros")]
#[test]
#[allow(unknown_lints, non_local_definitions)]
fn test_hasattr_error() {
use crate::exceptions::PyValueError;
use crate::prelude::*;

View File

@ -58,7 +58,14 @@ impl PySet {
}
/// Deprecated form of [`PySet::empty_bound`].
pub fn empty(py: Python<'_>) -> PyResult<&'_ PySet> {
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.2",
note = "`PySet::empty` will be replaced by `PySet::empty_bound` in a future PyO3 version"
)
)]
pub fn empty(py: Python<'_>) -> PyResult<&PySet> {
Self::empty_bound(py).map(Bound::into_gil_ref)
}

View File

@ -53,4 +53,5 @@ fn test_compile_errors() {
#[cfg(feature = "experimental-async")]
#[cfg(any(not(Py_LIMITED_API), Py_3_10))] // to avoid PyFunctionArgument for &str
t.compile_fail("tests/ui/invalid_cancel_handle.rs");
t.pass("tests/ui/pymodule_missing_docs.rs");
}

View File

@ -245,39 +245,6 @@ fn coroutine_panic() {
})
}
#[test]
fn test_async_method_receiver_with_other_args() {
#[pyclass]
struct Value(i32);
#[pymethods]
impl Value {
#[new]
fn new() -> Self {
Self(0)
}
async fn get_value_plus_with(&self, v: i32) -> i32 {
self.0 + v
}
async fn set_value(&mut self, new_value: i32) -> i32 {
self.0 = new_value;
self.0
}
}
Python::with_gil(|gil| {
let test = r#"
import asyncio
v = Value()
assert asyncio.run(v.get_value_plus_with(3)) == 3
assert asyncio.run(v.set_value(10)) == 10
assert asyncio.run(v.get_value_plus_with(1)) == 11
"#;
let locals = [("Value", gil.get_type_bound::<Value>())].into_py_dict_bound(gil);
py_run!(gil, *locals, test);
});
}
#[test]
fn test_async_method_receiver() {
#[pyclass]
@ -341,3 +308,36 @@ fn test_async_method_receiver() {
assert!(IS_DROPPED.load(Ordering::SeqCst));
}
#[test]
fn test_async_method_receiver_with_other_args() {
#[pyclass]
struct Value(i32);
#[pymethods]
impl Value {
#[new]
fn new() -> Self {
Self(0)
}
async fn get_value_plus_with(&self, v1: i32, v2: i32) -> i32 {
self.0 + v1 + v2
}
async fn set_value(&mut self, new_value: i32) -> i32 {
self.0 = new_value;
self.0
}
}
Python::with_gil(|gil| {
let test = r#"
import asyncio
v = Value()
assert asyncio.run(v.get_value_plus_with(3, 0)) == 3
assert asyncio.run(v.set_value(10)) == 10
assert asyncio.run(v.get_value_plus_with(1, 1)) == 12
"#;
let locals = [("Value", gil.get_type_bound::<Value>())].into_py_dict_bound(gil);
py_run!(gil, *locals, test);
});
}

View File

@ -9,6 +9,13 @@ use pyo3::types::PyBool;
#[path = "../src/tests/common.rs"]
mod common;
mod some_module {
use pyo3::prelude::*;
#[pyclass]
pub struct SomePyClass;
}
#[pyclass]
struct ValueClass {
value: usize,
@ -50,6 +57,10 @@ mod declarative_module {
#[pymodule_export]
use super::{declarative_module2, double, MyError, ValueClass as Value};
// test for #4036
#[pymodule_export]
use super::some_module::SomePyClass;
#[pymodule]
mod inner {
use super::*;

View File

@ -3,7 +3,7 @@
use pyo3::exceptions::{PyAttributeError, PyIndexError, PyValueError};
use pyo3::types::{PyDict, PyList, PyMapping, PySequence, PySlice, PyType};
use pyo3::{prelude::*, py_run};
use std::{isize, iter};
use std::iter;
#[path = "../src/tests/common.rs"]
mod common;

View File

@ -0,0 +1,12 @@
#![deny(missing_docs)]
//! Some crate docs
use pyo3::prelude::*;
/// Some module documentation
#[pymodule]
pub fn python_module(_m: &Bound<'_, PyModule>) -> PyResult<()> {
Ok(())
}
fn main() {}

View File

@ -1,13 +1,55 @@
use pyo3::prelude::*;
use pyo3::PyVisit;
use pyo3::PyTraverseError;
use pyo3::PyVisit;
#[pyclass]
struct TraverseTriesToTakePyRef {}
#[pymethods]
impl TraverseTriesToTakePyRef {
fn __traverse__(slf: PyRef<Self>, visit: PyVisit) {}
fn __traverse__(slf: PyRef<Self>, visit: PyVisit) -> Result<(), PyTraverseError> {
Ok(())
}
}
#[pyclass]
struct TraverseTriesToTakePyRefMut {}
#[pymethods]
impl TraverseTriesToTakePyRefMut {
fn __traverse__(slf: PyRefMut<Self>, visit: PyVisit) -> Result<(), PyTraverseError> {
Ok(())
}
}
#[pyclass]
struct TraverseTriesToTakeBound {}
#[pymethods]
impl TraverseTriesToTakeBound {
fn __traverse__(slf: Bound<'_, Self>, visit: PyVisit) -> Result<(), PyTraverseError> {
Ok(())
}
}
#[pyclass]
struct TraverseTriesToTakeMutSelf {}
#[pymethods]
impl TraverseTriesToTakeMutSelf {
fn __traverse__(&mut self, visit: PyVisit) -> Result<(), PyTraverseError> {
Ok(())
}
}
#[pyclass]
struct TraverseTriesToTakeSelf {}
#[pymethods]
impl TraverseTriesToTakeSelf {
fn __traverse__(&self, visit: PyVisit) -> Result<(), PyTraverseError> {
Ok(())
}
}
#[pyclass]
@ -19,9 +61,7 @@ impl Class {
Ok(())
}
fn __clear__(&mut self) {
}
fn __clear__(&mut self) {}
}
fn main() {}

View File

@ -1,23 +1,29 @@
error: __traverse__ may not take `Python`. Usually, an implementation of `__traverse__` should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic.
--> tests/ui/traverse.rs:18:32
error: __traverse__ may not take a receiver other than `&self`. Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic.
--> tests/ui/traverse.rs:10:26
|
18 | fn __traverse__(&self, py: Python<'_>, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
| ^^^^^^^^^^
10 | fn __traverse__(slf: PyRef<Self>, visit: PyVisit) -> Result<(), PyTraverseError> {
| ^^^^^
error[E0308]: mismatched types
--> tests/ui/traverse.rs:9:6
error: __traverse__ may not take a receiver other than `&self`. Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic.
--> tests/ui/traverse.rs:20:26
|
8 | #[pymethods]
| ------------ arguments to this function are incorrect
9 | impl TraverseTriesToTakePyRef {
| ______^
10 | | fn __traverse__(slf: PyRef<Self>, visit: PyVisit) {}
| |___________________^ expected fn pointer, found fn item
20 | fn __traverse__(slf: PyRefMut<Self>, visit: PyVisit) -> Result<(), PyTraverseError> {
| ^^^^^^^^
error: __traverse__ may not take a receiver other than `&self`. Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic.
--> tests/ui/traverse.rs:30:26
|
= note: expected fn pointer `for<'a, 'b> fn(&'a TraverseTriesToTakePyRef, PyVisit<'b>) -> Result<(), PyTraverseError>`
found fn item `for<'a, 'b> fn(pyo3::PyRef<'a, TraverseTriesToTakePyRef, >, PyVisit<'b>) {TraverseTriesToTakePyRef::__traverse__}`
note: function defined here
--> src/impl_/pymethods.rs
30 | fn __traverse__(slf: Bound<'_, Self>, visit: PyVisit) -> Result<(), PyTraverseError> {
| ^^^^^
error: __traverse__ may not take a receiver other than `&self`. Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic.
--> tests/ui/traverse.rs:40:21
|
| pub unsafe fn _call_traverse<T>(
| ^^^^^^^^^^^^^^
40 | fn __traverse__(&mut self, visit: PyVisit) -> Result<(), PyTraverseError> {
| ^
error: __traverse__ may not take `Python`. Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic.
--> tests/ui/traverse.rs:60:32
|
60 | fn __traverse__(&self, py: Python<'_>, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
| ^^^^^^^^^^