Skip to content

Commit 20f075a

Browse files
authored
Merge branch 'PyO3:main' into main
2 parents 023d3b7 + ef13bc6 commit 20f075a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+422
-225
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ about this topic.
210210
- [pyheck](https://github.com/kevinheavey/pyheck) _Fast case conversion library, built by wrapping [heck](https://github.com/withoutboats/heck)._
211211
- Quite easy to follow as there's not much code.
212212
- [pyre](https://github.com/Project-Dream-Weaver/pyre-http) _Fast Python HTTP server written in Rust._
213+
- [pyreqwest_impersonate](https://github.com/deedy5/pyreqwest_impersonate) _The fastest python HTTP client that can impersonate web browsers by mimicking their headers and TLS/JA3/JA4/HTTP2 fingerprints._
213214
- [ril-py](https://github.com/Cryptex-github/ril-py) _A performant and high-level image processing library for Python written in Rust._
214215
- [river](https://github.com/online-ml/river) _Online machine learning in python, the computationally heavy statistics algorithms are implemented in Rust._
215216
- [rust-python-coverage](https://github.com/cjermain/rust-python-coverage) _Example PyO3 project with automated test coverage for Rust and Python._

guide/src/building-and-distribution.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ not work when compiling for `abi3`. These are:
232232

233233
If you want to embed the Python interpreter inside a Rust program, there are two modes in which this can be done: dynamically and statically. We'll cover each of these modes in the following sections. Each of them affect how you must distribute your program. Instead of learning how to do this yourself, you might want to consider using a project like [PyOxidizer] to ship your application and all of its dependencies in a single file.
234234

235-
PyO3 automatically switches between the two linking modes depending on whether the Python distribution you have configured PyO3 to use ([see above](#configuring-the-python-version)) contains a shared library or a static library. The static library is most often seen in Python distributions compiled from source without the `--enable-shared` configuration option. For example, this is the default for `pyenv` on macOS.
235+
PyO3 automatically switches between the two linking modes depending on whether the Python distribution you have configured PyO3 to use ([see above](#configuring-the-python-version)) contains a shared library or a static library. The static library is most often seen in Python distributions compiled from source without the `--enable-shared` configuration option.
236236

237237
### Dynamically embedding the Python interpreter
238238

guide/src/python-from-rust/calling-existing-code.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
If you already have some existing Python code that you need to execute from Rust, the following FAQs can help you select the right PyO3 functionality for your situation:
44

5-
## Want to access Python APIs? Then use `PyModule::import`.
5+
## Want to access Python APIs? Then use `PyModule::import_bound`.
66

7-
[`Pymodule::import`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.import) can
7+
[`PyModule::import_bound`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.import_bound) can
88
be used to get handle to a Python module from Rust. You can use this to import and use any Python
99
module available in your environment.
1010

@@ -95,9 +95,9 @@ assert userdata.as_tuple() == userdata_as_tuple
9595
# }
9696
```
9797

98-
## You have a Python file or code snippet? Then use `PyModule::from_code`.
98+
## You have a Python file or code snippet? Then use `PyModule::from_code_bound`.
9999

100-
[`PyModule::from_code`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.from_code)
100+
[`PyModule::from_code_bound`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.from_code_bound)
101101
can be used to generate a Python module which can then be used just as if it was imported with
102102
`PyModule::import`.
103103

@@ -171,7 +171,7 @@ fn main() -> PyResult<()> {
171171
```
172172

173173
If `append_to_inittab` cannot be used due to constraints in the program,
174-
an alternative is to create a module using [`PyModule::new`]
174+
an alternative is to create a module using [`PyModule::new_bound`]
175175
and insert it manually into `sys.modules`:
176176

177177
```rust
@@ -394,4 +394,4 @@ Python::with_gil(|py| -> PyResult<()> {
394394
```
395395

396396

397-
[`PyModule::new`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.new
397+
[`PyModule::new_bound`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.new_bound

newsfragments/4117.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Correctly handle `#[pyo3(from_py_with = ...)]` attribute on dunder (`__magic__`) method arguments instead of silently ignoring it.

noxfile.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,10 +415,11 @@ def check_guide(session: nox.Session):
415415
_run(
416416
session,
417417
"lychee",
418-
PYO3_DOCS_TARGET,
418+
str(PYO3_DOCS_TARGET),
419419
f"--remap=https://pyo3.rs/main/ file://{PYO3_GUIDE_TARGET}/",
420420
f"--remap=https://pyo3.rs/latest/ file://{PYO3_GUIDE_TARGET}/",
421421
f"--exclude=file://{PYO3_DOCS_TARGET}",
422+
"--exclude=http://www.adobe.com/",
422423
*session.posargs,
423424
)
424425

pyo3-build-config/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ pub fn get() -> &'static InterpreterConfig {
8686
.map(|path| path.exists())
8787
.unwrap_or(false);
8888

89+
// CONFIG_FILE is generated in build.rs, so it's content can vary
90+
#[allow(unknown_lints, clippy::const_is_empty)]
8991
if let Some(interpreter_config) = InterpreterConfig::from_cargo_dep_env() {
9092
interpreter_config
9193
} else if !CONFIG_FILE.is_empty() {
@@ -177,6 +179,8 @@ pub mod pyo3_build_script_impl {
177179
/// correct value for CARGO_CFG_TARGET_OS).
178180
#[cfg(feature = "resolve-config")]
179181
pub fn resolve_interpreter_config() -> Result<InterpreterConfig> {
182+
// CONFIG_FILE is generated in build.rs, so it's content can vary
183+
#[allow(unknown_lints, clippy::const_is_empty)]
180184
if !CONFIG_FILE.is_empty() {
181185
let mut interperter_config = InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE))?;
182186
interperter_config.generate_import_libs()?;

pyo3-macros-backend/src/params.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use syn::spanned::Spanned;
1010

1111
pub struct Holders {
1212
holders: Vec<syn::Ident>,
13-
gil_refs_checkers: Vec<syn::Ident>,
13+
gil_refs_checkers: Vec<GilRefChecker>,
1414
}
1515

1616
impl Holders {
@@ -32,14 +32,28 @@ impl Holders {
3232
&format!("gil_refs_checker_{}", self.gil_refs_checkers.len()),
3333
span,
3434
);
35-
self.gil_refs_checkers.push(gil_refs_checker.clone());
35+
self.gil_refs_checkers
36+
.push(GilRefChecker::FunctionArg(gil_refs_checker.clone()));
37+
gil_refs_checker
38+
}
39+
40+
pub fn push_from_py_with_checker(&mut self, span: Span) -> syn::Ident {
41+
let gil_refs_checker = syn::Ident::new(
42+
&format!("gil_refs_checker_{}", self.gil_refs_checkers.len()),
43+
span,
44+
);
45+
self.gil_refs_checkers
46+
.push(GilRefChecker::FromPyWith(gil_refs_checker.clone()));
3647
gil_refs_checker
3748
}
3849

3950
pub fn init_holders(&self, ctx: &Ctx) -> TokenStream {
4051
let Ctx { pyo3_path } = ctx;
4152
let holders = &self.holders;
42-
let gil_refs_checkers = &self.gil_refs_checkers;
53+
let gil_refs_checkers = self.gil_refs_checkers.iter().map(|checker| match checker {
54+
GilRefChecker::FunctionArg(ident) => ident,
55+
GilRefChecker::FromPyWith(ident) => ident,
56+
});
4357
quote! {
4458
#[allow(clippy::let_unit_value)]
4559
#(let mut #holders = #pyo3_path::impl_::extract_argument::FunctionArgumentHolder::INIT;)*
@@ -50,11 +64,23 @@ impl Holders {
5064
pub fn check_gil_refs(&self) -> TokenStream {
5165
self.gil_refs_checkers
5266
.iter()
53-
.map(|e| quote_spanned! { e.span() => #e.function_arg(); })
67+
.map(|checker| match checker {
68+
GilRefChecker::FunctionArg(ident) => {
69+
quote_spanned! { ident.span() => #ident.function_arg(); }
70+
}
71+
GilRefChecker::FromPyWith(ident) => {
72+
quote_spanned! { ident.span() => #ident.from_py_with_arg(); }
73+
}
74+
})
5475
.collect()
5576
}
5677
}
5778

79+
enum GilRefChecker {
80+
FunctionArg(syn::Ident),
81+
FromPyWith(syn::Ident),
82+
}
83+
5884
/// Return true if the argument list is simply (*args, **kwds).
5985
pub fn is_forwarded_args(signature: &FunctionSignature<'_>) -> bool {
6086
matches!(

pyo3-macros-backend/src/pymethod.rs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,44 +1083,39 @@ impl Ty {
10831083
ctx: &Ctx,
10841084
) -> TokenStream {
10851085
let Ctx { pyo3_path } = ctx;
1086-
let name_str = arg.name().unraw().to_string();
10871086
match self {
10881087
Ty::Object => extract_object(
10891088
extract_error_mode,
10901089
holders,
1091-
&name_str,
1090+
arg,
10921091
quote! { #ident },
1093-
arg.ty().span(),
10941092
ctx
10951093
),
10961094
Ty::MaybeNullObject => extract_object(
10971095
extract_error_mode,
10981096
holders,
1099-
&name_str,
1097+
arg,
11001098
quote! {
11011099
if #ident.is_null() {
11021100
#pyo3_path::ffi::Py_None()
11031101
} else {
11041102
#ident
11051103
}
11061104
},
1107-
arg.ty().span(),
11081105
ctx
11091106
),
11101107
Ty::NonNullObject => extract_object(
11111108
extract_error_mode,
11121109
holders,
1113-
&name_str,
1110+
arg,
11141111
quote! { #ident.as_ptr() },
1115-
arg.ty().span(),
11161112
ctx
11171113
),
11181114
Ty::IPowModulo => extract_object(
11191115
extract_error_mode,
11201116
holders,
1121-
&name_str,
1117+
arg,
11221118
quote! { #ident.as_ptr() },
1123-
arg.ty().span(),
11241119
ctx
11251120
),
11261121
Ty::CompareOp => extract_error_mode.handle_error(
@@ -1148,24 +1143,37 @@ impl Ty {
11481143
fn extract_object(
11491144
extract_error_mode: ExtractErrorMode,
11501145
holders: &mut Holders,
1151-
name: &str,
1146+
arg: &FnArg<'_>,
11521147
source_ptr: TokenStream,
1153-
span: Span,
11541148
ctx: &Ctx,
11551149
) -> TokenStream {
11561150
let Ctx { pyo3_path } = ctx;
1157-
let holder = holders.push_holder(Span::call_site());
1158-
let gil_refs_checker = holders.push_gil_refs_checker(span);
1159-
let extracted = extract_error_mode.handle_error(
1151+
let gil_refs_checker = holders.push_gil_refs_checker(arg.ty().span());
1152+
let name = arg.name().unraw().to_string();
1153+
1154+
let extract = if let Some(from_py_with) =
1155+
arg.from_py_with().map(|from_py_with| &from_py_with.value)
1156+
{
1157+
let from_py_with_checker = holders.push_from_py_with_checker(from_py_with.span());
1158+
quote! {
1159+
#pyo3_path::impl_::extract_argument::from_py_with(
1160+
#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &#source_ptr).0,
1161+
#name,
1162+
#pyo3_path::impl_::deprecations::inspect_fn(#from_py_with, &#from_py_with_checker) as fn(_) -> _,
1163+
)
1164+
}
1165+
} else {
1166+
let holder = holders.push_holder(Span::call_site());
11601167
quote! {
11611168
#pyo3_path::impl_::extract_argument::extract_argument(
11621169
#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &#source_ptr).0,
11631170
&mut #holder,
11641171
#name
11651172
)
1166-
},
1167-
ctx,
1168-
);
1173+
}
1174+
};
1175+
1176+
let extracted = extract_error_mode.handle_error(extract, ctx);
11691177
quote! {
11701178
#pyo3_path::impl_::deprecations::inspect_type(#extracted, &#gil_refs_checker)
11711179
}

pytests/src/othermod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ pub fn othermod(m: &Bound<'_, PyModule>) -> PyResult<()> {
3434

3535
m.add_class::<ModClass>()?;
3636

37-
m.add("USIZE_MIN", usize::min_value())?;
38-
m.add("USIZE_MAX", usize::max_value())?;
37+
m.add("USIZE_MIN", usize::MIN)?;
38+
m.add("USIZE_MAX", usize::MAX)?;
3939

4040
Ok(())
4141
}

src/marker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ pub use nightly::Ungil;
352352
/// # Releasing and freeing memory
353353
///
354354
/// The [`Python`] type can be used to create references to variables owned by the Python
355-
/// interpreter, using functions such as [`Python::eval`] and [`PyModule::import`]. These
355+
/// interpreter, using functions such as [`Python::eval`] and `PyModule::import`. These
356356
/// references are tied to a [`GILPool`] whose references are not cleared until it is dropped.
357357
/// This can cause apparent "memory leaks" if it is kept around for a long time.
358358
///

src/marshal.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@ use std::os::raw::c_int;
1313
pub const VERSION: i32 = 4;
1414

1515
/// Deprecated form of [`dumps_bound`]
16-
#[cfg_attr(
17-
not(feature = "gil-refs"),
18-
deprecated(
19-
since = "0.21.0",
20-
note = "`dumps` will be replaced by `dumps_bound` in a future PyO3 version"
21-
)
16+
#[cfg(feature = "gil-refs")]
17+
#[deprecated(
18+
since = "0.21.0",
19+
note = "`dumps` will be replaced by `dumps_bound` in a future PyO3 version"
2220
)]
2321
pub fn dumps<'py>(
2422
py: Python<'py>,
@@ -61,12 +59,10 @@ pub fn dumps_bound<'py>(
6159
}
6260

6361
/// Deprecated form of [`loads_bound`]
64-
#[cfg_attr(
65-
not(feature = "gil-refs"),
66-
deprecated(
67-
since = "0.21.0",
68-
note = "`loads` will be replaced by `loads_bound` in a future PyO3 version"
69-
)
62+
#[cfg(feature = "gil-refs")]
63+
#[deprecated(
64+
since = "0.21.0",
65+
note = "`loads` will be replaced by `loads_bound` in a future PyO3 version"
7066
)]
7167
pub fn loads<'py, B>(py: Python<'py>, data: &B) -> PyResult<&'py PyAny>
7268
where

src/pyclass/create_type_object.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,14 @@ impl PyTypeBuilder {
145145
#[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
146146
ffi::Py_bf_getbuffer => {
147147
// Safety: slot.pfunc is a valid function pointer
148-
self.buffer_procs.bf_getbuffer = Some(std::mem::transmute(pfunc));
148+
self.buffer_procs.bf_getbuffer =
149+
Some(std::mem::transmute::<*mut T, ffi::getbufferproc>(pfunc));
149150
}
150151
#[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
151152
ffi::Py_bf_releasebuffer => {
152153
// Safety: slot.pfunc is a valid function pointer
153-
self.buffer_procs.bf_releasebuffer = Some(std::mem::transmute(pfunc));
154+
self.buffer_procs.bf_releasebuffer =
155+
Some(std::mem::transmute::<*mut T, ffi::releasebufferproc>(pfunc));
154156
}
155157
_ => {}
156158
}

src/types/function.rs

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1+
#[cfg(feature = "gil-refs")]
12
use crate::derive_utils::PyFunctionArguments;
23
use crate::ffi_ptr_ext::FfiPtrExt;
34
use crate::py_result_ext::PyResultExt;
45
use crate::types::capsule::PyCapsuleMethods;
56
use crate::types::module::PyModuleMethods;
7+
#[cfg(feature = "gil-refs")]
8+
use crate::PyNativeType;
69
use crate::{
710
ffi,
811
impl_::pymethods::{self, PyMethodDef, PyMethodDefDestructor},
912
types::{PyCapsule, PyDict, PyModule, PyString, PyTuple},
1013
};
11-
use crate::{Bound, IntoPy, Py, PyAny, PyNativeType, PyResult, Python};
14+
use crate::{Bound, IntoPy, Py, PyAny, PyResult, Python};
1215
use std::cell::UnsafeCell;
1316
use std::ffi::CStr;
1417

@@ -20,12 +23,10 @@ pyobject_native_type_core!(PyCFunction, pyobject_native_static_type_object!(ffi:
2023

2124
impl PyCFunction {
2225
/// Deprecated form of [`PyCFunction::new_with_keywords_bound`]
23-
#[cfg_attr(
24-
not(feature = "gil-refs"),
25-
deprecated(
26-
since = "0.21.0",
27-
note = "`PyCFunction::new_with_keywords` will be replaced by `PyCFunction::new_with_keywords_bound` in a future PyO3 version"
28-
)
26+
#[cfg(feature = "gil-refs")]
27+
#[deprecated(
28+
since = "0.21.0",
29+
note = "`PyCFunction::new_with_keywords` will be replaced by `PyCFunction::new_with_keywords_bound` in a future PyO3 version"
2930
)]
3031
pub fn new_with_keywords<'a>(
3132
fun: ffi::PyCFunctionWithKeywords,
@@ -66,12 +67,10 @@ impl PyCFunction {
6667
}
6768

6869
/// Deprecated form of [`PyCFunction::new`]
69-
#[cfg_attr(
70-
not(feature = "gil-refs"),
71-
deprecated(
72-
since = "0.21.0",
73-
note = "`PyCFunction::new` will be replaced by `PyCFunction::new_bound` in a future PyO3 version"
74-
)
70+
#[cfg(feature = "gil-refs")]
71+
#[deprecated(
72+
since = "0.21.0",
73+
note = "`PyCFunction::new` will be replaced by `PyCFunction::new_bound` in a future PyO3 version"
7574
)]
7675
pub fn new<'a>(
7776
fun: ffi::PyCFunction,
@@ -104,12 +103,10 @@ impl PyCFunction {
104103
}
105104

106105
/// Deprecated form of [`PyCFunction::new_closure`]
107-
#[cfg_attr(
108-
not(feature = "gil-refs"),
109-
deprecated(
110-
since = "0.21.0",
111-
note = "`PyCFunction::new_closure` will be replaced by `PyCFunction::new_closure_bound` in a future PyO3 version"
112-
)
106+
#[cfg(feature = "gil-refs")]
107+
#[deprecated(
108+
since = "0.21.0",
109+
note = "`PyCFunction::new_closure` will be replaced by `PyCFunction::new_closure_bound` in a future PyO3 version"
113110
)]
114111
pub fn new_closure<'a, F, R>(
115112
py: Python<'a>,

0 commit comments

Comments
 (0)