Merge branch 'main' into aliases
This commit is contained in:
commit
4d1d859a64
|
@ -39,6 +39,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- `PyErr::new_type` now takes an optional docstring and now returns `PyResult<Py<PyType>>` rather than a `ffi::PyTypeObject` pointer.
|
- `PyErr::new_type` now takes an optional docstring and now returns `PyResult<Py<PyType>>` rather than a `ffi::PyTypeObject` pointer.
|
||||||
- The `create_exception!` macro can now take an optional docstring. This docstring, if supplied, is visible to users (with `.__doc__` and `help()`) and
|
- The `create_exception!` macro can now take an optional docstring. This docstring, if supplied, is visible to users (with `.__doc__` and `help()`) and
|
||||||
accompanies your error type in your crate's documentation.
|
accompanies your error type in your crate's documentation.
|
||||||
|
- Improve performance and error messages for `#[derive(FromPyObject)]` for enums. [#2068](https://github.com/PyO3/pyo3/pull/2068)
|
||||||
|
- Reduce generated LLVM code size (to improve compile times) for:
|
||||||
|
- internal `handle_panic` helper [#2074](https://github.com/PyO3/pyo3/pull/2074)
|
||||||
|
- `#[pyfunction]` and `#[pymethods]` argument extraction [#2075](https://github.com/PyO3/pyo3/pull/2075)
|
||||||
|
- `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076)
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,11 @@ harness = false
|
||||||
name = "bench_dict"
|
name = "bench_dict"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "bench_frompyobject"
|
||||||
|
harness = false
|
||||||
|
required-features = ["macros"]
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "bench_gil"
|
name = "bench_gil"
|
||||||
harness = false
|
harness = false
|
||||||
|
@ -106,6 +111,7 @@ harness = false
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "bench_pyclass"
|
name = "bench_pyclass"
|
||||||
harness = false
|
harness = false
|
||||||
|
required-features = ["macros"]
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "bench_pyobject"
|
name = "bench_pyobject"
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
use criterion::{criterion_group, criterion_main, Bencher, Criterion};
|
||||||
|
|
||||||
|
use pyo3::{prelude::*, types::PyString};
|
||||||
|
|
||||||
|
#[derive(FromPyObject)]
|
||||||
|
enum ManyTypes {
|
||||||
|
Int(i32),
|
||||||
|
Bytes(Vec<u8>),
|
||||||
|
String(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enum_from_pyobject(b: &mut Bencher) {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let obj = PyString::new(py, "hello world");
|
||||||
|
b.iter(|| {
|
||||||
|
let _: ManyTypes = obj.extract().unwrap();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn criterion_benchmark(c: &mut Criterion) {
|
||||||
|
c.bench_function("enum_from_pyobject", enum_from_pyobject);
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, criterion_benchmark);
|
||||||
|
criterion_main!(benches);
|
|
@ -1,8 +1,4 @@
|
||||||
#[cfg(feature = "macros")]
|
|
||||||
use criterion::{criterion_group, criterion_main, Criterion};
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
|
||||||
#[cfg(feature = "macros")]
|
|
||||||
mod m {
|
|
||||||
use pyo3::{class::PyObjectProtocol, prelude::*, type_object::LazyStaticType};
|
use pyo3::{class::PyObjectProtocol, prelude::*, type_object::LazyStaticType};
|
||||||
|
|
||||||
/// This is a feature-rich class instance used to benchmark various parts of the pyclass lifecycle.
|
/// This is a feature-rich class instance used to benchmark various parts of the pyclass lifecycle.
|
||||||
|
@ -43,22 +39,11 @@ mod m {
|
||||||
ty.get_or_init::<MyClass>(py);
|
ty.get_or_init::<MyClass>(py);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "macros")]
|
|
||||||
fn criterion_benchmark(c: &mut Criterion) {
|
fn criterion_benchmark(c: &mut Criterion) {
|
||||||
c.bench_function("first_time_init", m::first_time_init);
|
c.bench_function("first_time_init", first_time_init);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "macros")]
|
|
||||||
criterion_group!(benches, criterion_benchmark);
|
criterion_group!(benches, criterion_benchmark);
|
||||||
|
|
||||||
#[cfg(feature = "macros")]
|
|
||||||
criterion_main!(benches);
|
criterion_main!(benches);
|
||||||
|
|
||||||
#[cfg(not(feature = "macros"))]
|
|
||||||
fn main() {
|
|
||||||
unimplemented!(
|
|
||||||
"benchmarking `bench_pyclass` is only available with the `macros` feature enabled"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -83,7 +83,7 @@ These features enable conversions between Python types and types from other Rust
|
||||||
|
|
||||||
### `anyhow`
|
### `anyhow`
|
||||||
|
|
||||||
Adds a dependency on [anyhow](https://docs.rs/anyhow). Enables a conversion from [anyhow](https://docs.rs/anyhow)’s [`Error`]https://docs.rs/anyhow/latest/anyhow/struct.Error.html) type to [`PyErr`](https://docs.rs/pyo3/latest/pyo3/struct.PyErr.html), for easy error handling.
|
Adds a dependency on [anyhow](https://docs.rs/anyhow). Enables a conversion from [anyhow](https://docs.rs/anyhow)’s [`Error`](https://docs.rs/anyhow/latest/anyhow/struct.Error.html) type to [`PyErr`](https://docs.rs/pyo3/latest/pyo3/struct.PyErr.html), for easy error handling.
|
||||||
|
|
||||||
### `eyre`
|
### `eyre`
|
||||||
|
|
||||||
|
|
|
@ -54,38 +54,39 @@ impl<'a> Enum<'a> {
|
||||||
/// Build derivation body for enums.
|
/// Build derivation body for enums.
|
||||||
fn build(&self) -> TokenStream {
|
fn build(&self) -> TokenStream {
|
||||||
let mut var_extracts = Vec::new();
|
let mut var_extracts = Vec::new();
|
||||||
let mut error_names = String::new();
|
let mut variant_names = Vec::new();
|
||||||
for (i, var) in self.variants.iter().enumerate() {
|
let mut error_names = Vec::new();
|
||||||
|
for var in &self.variants {
|
||||||
let struct_derive = var.build();
|
let struct_derive = var.build();
|
||||||
let ext = quote!(
|
let ext = quote!({
|
||||||
let maybe_ret = || -> _pyo3::PyResult<Self> {
|
let maybe_ret = || -> _pyo3::PyResult<Self> {
|
||||||
#struct_derive
|
#struct_derive
|
||||||
}();
|
}();
|
||||||
|
|
||||||
match maybe_ret {
|
match maybe_ret {
|
||||||
ok @ ::std::result::Result::Ok(_) => return ok,
|
ok @ ::std::result::Result::Ok(_) => return ok,
|
||||||
::std::result::Result::Err(err) => {
|
::std::result::Result::Err(err) => err
|
||||||
let py = _pyo3::PyNativeType::py(obj);
|
|
||||||
err_reasons.push_str(&::std::format!("{}\n", err.value(py).str()?));
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
var_extracts.push(ext);
|
var_extracts.push(ext);
|
||||||
if i > 0 {
|
variant_names.push(var.path.segments.last().unwrap().ident.to_string());
|
||||||
error_names.push_str(" | ");
|
error_names.push(&var.err_name);
|
||||||
}
|
|
||||||
error_names.push_str(&var.err_name);
|
|
||||||
}
|
}
|
||||||
let ty_name = self.enum_ident.to_string();
|
let ty_name = self.enum_ident.to_string();
|
||||||
quote!(
|
quote!(
|
||||||
let mut err_reasons = ::std::string::String::new();
|
let errors = [
|
||||||
#(#var_extracts)*
|
#(#var_extracts),*
|
||||||
let err_msg = ::std::format!("failed to extract enum {} ('{}')\n{}",
|
];
|
||||||
|
::std::result::Result::Err(
|
||||||
|
_pyo3::impl_::frompyobject::failed_to_extract_enum(
|
||||||
|
obj.py(),
|
||||||
#ty_name,
|
#ty_name,
|
||||||
#error_names,
|
&[#(#variant_names),*],
|
||||||
&err_reasons);
|
&[#(#error_names),*],
|
||||||
::std::result::Result::Err(_pyo3::exceptions::PyTypeError::new_err(err_msg))
|
&errors
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,13 +217,8 @@ impl<'a> Container<'a> {
|
||||||
new_err
|
new_err
|
||||||
})?})
|
})?})
|
||||||
)
|
)
|
||||||
} else {
|
} else if !self.is_enum_variant {
|
||||||
let error_msg = if self.is_enum_variant {
|
let error_msg = format!("failed to extract inner field of {}", quote!(#self_ty));
|
||||||
let variant_name = &self.path.segments.last().unwrap();
|
|
||||||
format!("- variant {} ({})", quote!(#variant_name), &self.err_name)
|
|
||||||
} else {
|
|
||||||
format!("failed to extract inner field of {}", quote!(#self_ty))
|
|
||||||
};
|
|
||||||
quote!(
|
quote!(
|
||||||
::std::result::Result::Ok(#self_ty(obj.extract().map_err(|err| {
|
::std::result::Result::Ok(#self_ty(obj.extract().map_err(|err| {
|
||||||
let py = _pyo3::PyNativeType::py(obj);
|
let py = _pyo3::PyNativeType::py(obj);
|
||||||
|
@ -232,6 +228,8 @@ impl<'a> Container<'a> {
|
||||||
_pyo3::exceptions::PyTypeError::new_err(err_msg)
|
_pyo3::exceptions::PyTypeError::new_err(err_msg)
|
||||||
})?))
|
})?))
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
quote!(obj.extract().map(#self_ty))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ mod utils;
|
||||||
mod attributes;
|
mod attributes;
|
||||||
mod defs;
|
mod defs;
|
||||||
mod deprecations;
|
mod deprecations;
|
||||||
mod from_pyobject;
|
mod frompyobject;
|
||||||
mod konst;
|
mod konst;
|
||||||
mod method;
|
mod method;
|
||||||
mod module;
|
mod module;
|
||||||
|
@ -23,7 +23,7 @@ mod pyimpl;
|
||||||
mod pymethod;
|
mod pymethod;
|
||||||
mod pyproto;
|
mod pyproto;
|
||||||
|
|
||||||
pub use from_pyobject::build_derive_from_pyobject;
|
pub use frompyobject::build_derive_from_pyobject;
|
||||||
pub use module::{process_functions_in_module, py_init, PyModuleOptions};
|
pub use module::{process_functions_in_module, py_init, PyModuleOptions};
|
||||||
pub use pyclass::{build_py_class, build_py_enum, PyClassArgs};
|
pub use pyclass::{build_py_class, build_py_enum, PyClassArgs};
|
||||||
pub use pyfunction::{build_py_function, PyFunctionOptions};
|
pub use pyfunction::{build_py_function, PyFunctionOptions};
|
||||||
|
|
|
@ -213,8 +213,9 @@ fn impl_arg_param(
|
||||||
|
|
||||||
let ty = arg.ty;
|
let ty = arg.ty;
|
||||||
let name = arg.name;
|
let name = arg.name;
|
||||||
|
let name_str = name.to_string();
|
||||||
let transform_error = quote! {
|
let transform_error = quote! {
|
||||||
|e| _pyo3::derive_utils::argument_extraction_error(#py, stringify!(#name), e)
|
|e| _pyo3::impl_::extract_argument::argument_extraction_error(#py, #name_str, e)
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_args(&spec.attrs, name) {
|
if is_args(&spec.attrs, name) {
|
||||||
|
@ -223,7 +224,7 @@ fn impl_arg_param(
|
||||||
arg.name.span() => "args cannot be optional"
|
arg.name.span() => "args cannot be optional"
|
||||||
);
|
);
|
||||||
return Ok(quote_arg_span! {
|
return Ok(quote_arg_span! {
|
||||||
let #arg_name = _args.unwrap().extract().map_err(#transform_error)?;
|
let #arg_name = _pyo3::impl_::extract_argument::extract_argument(_args.unwrap(), #name_str)?;
|
||||||
});
|
});
|
||||||
} else if is_kwargs(&spec.attrs, name) {
|
} else if is_kwargs(&spec.attrs, name) {
|
||||||
ensure_spanned!(
|
ensure_spanned!(
|
||||||
|
@ -231,9 +232,8 @@ fn impl_arg_param(
|
||||||
arg.name.span() => "kwargs must be Option<_>"
|
arg.name.span() => "kwargs must be Option<_>"
|
||||||
);
|
);
|
||||||
return Ok(quote_arg_span! {
|
return Ok(quote_arg_span! {
|
||||||
let #arg_name = _kwargs.map(|kwargs| kwargs.extract())
|
let #arg_name = _kwargs.map(|kwargs| _pyo3::impl_::extract_argument::extract_argument(kwargs, #name_str))
|
||||||
.transpose()
|
.transpose()?;
|
||||||
.map_err(#transform_error)?;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,7 +243,7 @@ fn impl_arg_param(
|
||||||
let extract = if let Some(FromPyWithAttribute(expr_path)) = &arg.attrs.from_py_with {
|
let extract = if let Some(FromPyWithAttribute(expr_path)) = &arg.attrs.from_py_with {
|
||||||
quote_arg_span! { #expr_path(_obj).map_err(#transform_error) }
|
quote_arg_span! { #expr_path(_obj).map_err(#transform_error) }
|
||||||
} else {
|
} else {
|
||||||
quote_arg_span! { _obj.extract().map_err(#transform_error) }
|
quote_arg_span! { _pyo3::impl_::extract_argument::extract_argument(_obj, #name_str) }
|
||||||
};
|
};
|
||||||
|
|
||||||
let arg_value_or_default = match (spec.default_value(name), arg.optional.is_some()) {
|
let arg_value_or_default = match (spec.default_value(name), arg.optional.is_some()) {
|
||||||
|
|
|
@ -3,3 +3,6 @@ usedevelop = True
|
||||||
description = Run the unit tests under {basepython}
|
description = Run the unit tests under {basepython}
|
||||||
deps = -rrequirements-dev.txt
|
deps = -rrequirements-dev.txt
|
||||||
commands = pytest --benchmark-sort=name {posargs}
|
commands = pytest --benchmark-sort=name {posargs}
|
||||||
|
# Use recreate so that tox always rebuilds, otherwise changes to Rust are not
|
||||||
|
# picked up.
|
||||||
|
recreate = True
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::{GILPool, IntoPyPointer};
|
||||||
use crate::{IntoPy, PyObject, Python};
|
use crate::{IntoPy, PyObject, Python};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
use std::panic::{AssertUnwindSafe, UnwindSafe};
|
use std::panic::UnwindSafe;
|
||||||
use std::{isize, panic};
|
use std::{isize, panic};
|
||||||
|
|
||||||
/// A type which can be the return type of a python C-API callback
|
/// A type which can be the return type of a python C-API callback
|
||||||
|
@ -241,13 +241,8 @@ where
|
||||||
R: PyCallbackOutput,
|
R: PyCallbackOutput,
|
||||||
{
|
{
|
||||||
let pool = GILPool::new();
|
let pool = GILPool::new();
|
||||||
let unwind_safe_py = AssertUnwindSafe(pool.python());
|
let py = pool.python();
|
||||||
let panic_result = panic::catch_unwind(move || -> PyResult<_> {
|
panic_result_into_callback_output(py, panic::catch_unwind(move || -> PyResult<_> { body(py) }))
|
||||||
let py = *unwind_safe_py;
|
|
||||||
body(py)
|
|
||||||
});
|
|
||||||
|
|
||||||
panic_result_into_callback_output(pool.python(), panic_result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn panic_result_into_callback_output<R>(
|
fn panic_result_into_callback_output<R>(
|
||||||
|
|
|
@ -102,9 +102,12 @@ impl FunctionDescription {
|
||||||
varkeywords
|
varkeywords
|
||||||
}
|
}
|
||||||
(Some(kwargs), false) => {
|
(Some(kwargs), false) => {
|
||||||
self.extract_keyword_arguments(kwargs, output, |name, _| {
|
self.extract_keyword_arguments(
|
||||||
Err(self.unexpected_keyword_argument(name))
|
kwargs,
|
||||||
})?;
|
output,
|
||||||
|
#[cold]
|
||||||
|
|name, _| Err(self.unexpected_keyword_argument(name)),
|
||||||
|
)?;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
(None, _) => None,
|
(None, _) => None,
|
||||||
|
@ -112,58 +115,39 @@ impl FunctionDescription {
|
||||||
|
|
||||||
// Check that there's sufficient positional arguments once keyword arguments are specified
|
// Check that there's sufficient positional arguments once keyword arguments are specified
|
||||||
if args_provided < self.required_positional_parameters {
|
if args_provided < self.required_positional_parameters {
|
||||||
let missing_positional_arguments: Vec<_> = self
|
for out in &output[..self.required_positional_parameters] {
|
||||||
.positional_parameter_names
|
if out.is_none() {
|
||||||
.iter()
|
return Err(self.missing_required_positional_arguments(output));
|
||||||
.take(self.required_positional_parameters)
|
}
|
||||||
.zip(output.iter())
|
|
||||||
.filter_map(|(param, out)| if out.is_none() { Some(*param) } else { None })
|
|
||||||
.collect();
|
|
||||||
if !missing_positional_arguments.is_empty() {
|
|
||||||
return Err(
|
|
||||||
self.missing_required_arguments("positional", &missing_positional_arguments)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check no missing required keyword arguments
|
// Check no missing required keyword arguments
|
||||||
let missing_keyword_only_arguments: Vec<_> = self
|
let keyword_output = &output[num_positional_parameters..];
|
||||||
.keyword_only_parameters
|
for (param, out) in self.keyword_only_parameters.iter().zip(keyword_output) {
|
||||||
.iter()
|
if param.required && out.is_none() {
|
||||||
.zip(&output[num_positional_parameters..])
|
return Err(self.missing_required_keyword_arguments(keyword_output));
|
||||||
.filter_map(|(keyword_desc, out)| {
|
|
||||||
if keyword_desc.required && out.is_none() {
|
|
||||||
Some(keyword_desc.name)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if !missing_keyword_only_arguments.is_empty() {
|
|
||||||
return Err(self.missing_required_arguments("keyword", &missing_keyword_only_arguments));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((varargs, varkeywords))
|
Ok((varargs, varkeywords))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn extract_keyword_arguments<'p>(
|
fn extract_keyword_arguments<'p>(
|
||||||
&self,
|
&self,
|
||||||
kwargs: impl Iterator<Item = (&'p PyAny, &'p PyAny)>,
|
kwargs: impl Iterator<Item = (&'p PyAny, &'p PyAny)>,
|
||||||
output: &mut [Option<&'p PyAny>],
|
output: &mut [Option<&'p PyAny>],
|
||||||
mut unexpected_keyword_handler: impl FnMut(&'p PyAny, &'p PyAny) -> PyResult<()>,
|
mut unexpected_keyword_handler: impl FnMut(&'p PyAny, &'p PyAny) -> PyResult<()>,
|
||||||
) -> PyResult<()> {
|
) -> PyResult<()> {
|
||||||
let (args_output, kwargs_output) =
|
let positional_args_count = self.positional_parameter_names.len();
|
||||||
output.split_at_mut(self.positional_parameter_names.len());
|
|
||||||
let mut positional_only_keyword_arguments = Vec::new();
|
let mut positional_only_keyword_arguments = Vec::new();
|
||||||
for (kwarg_name, value) in kwargs {
|
'for_each_kwarg: for (kwarg_name_py, value) in kwargs {
|
||||||
let utf8_string = match kwarg_name.downcast::<PyString>()?.to_str() {
|
let kwarg_name = match kwarg_name_py.downcast::<PyString>()?.to_str() {
|
||||||
Ok(utf8_string) => utf8_string,
|
Ok(kwarg_name) => kwarg_name,
|
||||||
// This keyword is not a UTF8 string: all PyO3 argument names are guaranteed to be
|
// This keyword is not a UTF8 string: all PyO3 argument names are guaranteed to be
|
||||||
// UTF8 by construction.
|
// UTF8 by construction.
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
unexpected_keyword_handler(kwarg_name, value)?;
|
unexpected_keyword_handler(kwarg_name_py, value)?;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -171,31 +155,24 @@ impl FunctionDescription {
|
||||||
// Compare the keyword name against each parameter in turn. This is exactly the same method
|
// Compare the keyword name against each parameter in turn. This is exactly the same method
|
||||||
// which CPython uses to map keyword names. Although it's O(num_parameters), the number of
|
// which CPython uses to map keyword names. Although it's O(num_parameters), the number of
|
||||||
// parameters is expected to be small so it's not worth constructing a mapping.
|
// parameters is expected to be small so it's not worth constructing a mapping.
|
||||||
if let Some(i) = self
|
for (i, param) in self.keyword_only_parameters.iter().enumerate() {
|
||||||
.keyword_only_parameters
|
if param.name == kwarg_name {
|
||||||
.iter()
|
output[positional_args_count + i] = Some(value);
|
||||||
.position(|param| utf8_string == param.name)
|
continue 'for_each_kwarg;
|
||||||
{
|
}
|
||||||
kwargs_output[i] = Some(value);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repeat for positional parameters
|
// Repeat for positional parameters
|
||||||
if let Some((i, param)) = self
|
if let Some(i) = self.find_keyword_parameter_in_positionals(kwarg_name) {
|
||||||
.positional_parameter_names
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|&(_, param)| utf8_string == *param)
|
|
||||||
{
|
|
||||||
if i < self.positional_only_parameters {
|
if i < self.positional_only_parameters {
|
||||||
positional_only_keyword_arguments.push(*param);
|
positional_only_keyword_arguments.push(kwarg_name);
|
||||||
} else if args_output[i].replace(value).is_some() {
|
} else if output[i].replace(value).is_some() {
|
||||||
return Err(self.multiple_values_for_argument(param));
|
return Err(self.multiple_values_for_argument(kwarg_name));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
unexpected_keyword_handler(kwarg_name, value)?;
|
unexpected_keyword_handler(kwarg_name_py, value)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if positional_only_keyword_arguments.is_empty() {
|
if positional_only_keyword_arguments.is_empty() {
|
||||||
|
@ -205,6 +182,16 @@ impl FunctionDescription {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_keyword_parameter_in_positionals(&self, kwarg_name: &str) -> Option<usize> {
|
||||||
|
for (i, param_name) in self.positional_parameter_names.iter().enumerate() {
|
||||||
|
if *param_name == kwarg_name {
|
||||||
|
return Some(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
fn too_many_positional_arguments(&self, args_provided: usize) -> PyErr {
|
fn too_many_positional_arguments(&self, args_provided: usize) -> PyErr {
|
||||||
let was = if args_provided == 1 { "was" } else { "were" };
|
let was = if args_provided == 1 { "was" } else { "were" };
|
||||||
let msg = if self.required_positional_parameters != self.positional_parameter_names.len() {
|
let msg = if self.required_positional_parameters != self.positional_parameter_names.len() {
|
||||||
|
@ -228,6 +215,7 @@ impl FunctionDescription {
|
||||||
PyTypeError::new_err(msg)
|
PyTypeError::new_err(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
fn multiple_values_for_argument(&self, argument: &str) -> PyErr {
|
fn multiple_values_for_argument(&self, argument: &str) -> PyErr {
|
||||||
PyTypeError::new_err(format!(
|
PyTypeError::new_err(format!(
|
||||||
"{} got multiple values for argument '{}'",
|
"{} got multiple values for argument '{}'",
|
||||||
|
@ -236,6 +224,7 @@ impl FunctionDescription {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
fn unexpected_keyword_argument(&self, argument: &PyAny) -> PyErr {
|
fn unexpected_keyword_argument(&self, argument: &PyAny) -> PyErr {
|
||||||
PyTypeError::new_err(format!(
|
PyTypeError::new_err(format!(
|
||||||
"{} got an unexpected keyword argument '{}'",
|
"{} got an unexpected keyword argument '{}'",
|
||||||
|
@ -244,6 +233,7 @@ impl FunctionDescription {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
fn positional_only_keyword_arguments(&self, parameter_names: &[&str]) -> PyErr {
|
fn positional_only_keyword_arguments(&self, parameter_names: &[&str]) -> PyErr {
|
||||||
let mut msg = format!(
|
let mut msg = format!(
|
||||||
"{} got some positional-only arguments passed as keyword arguments: ",
|
"{} got some positional-only arguments passed as keyword arguments: ",
|
||||||
|
@ -253,6 +243,7 @@ impl FunctionDescription {
|
||||||
PyTypeError::new_err(msg)
|
PyTypeError::new_err(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
fn missing_required_arguments(&self, argument_type: &str, parameter_names: &[&str]) -> PyErr {
|
fn missing_required_arguments(&self, argument_type: &str, parameter_names: &[&str]) -> PyErr {
|
||||||
let arguments = if parameter_names.len() == 1 {
|
let arguments = if parameter_names.len() == 1 {
|
||||||
"argument"
|
"argument"
|
||||||
|
@ -269,18 +260,40 @@ impl FunctionDescription {
|
||||||
push_parameter_list(&mut msg, parameter_names);
|
push_parameter_list(&mut msg, parameter_names);
|
||||||
PyTypeError::new_err(msg)
|
PyTypeError::new_err(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
fn missing_required_keyword_arguments(&self, keyword_outputs: &[Option<&PyAny>]) -> PyErr {
|
||||||
|
debug_assert_eq!(self.keyword_only_parameters.len(), keyword_outputs.len());
|
||||||
|
|
||||||
|
let missing_keyword_only_arguments: Vec<_> = self
|
||||||
|
.keyword_only_parameters
|
||||||
|
.iter()
|
||||||
|
.zip(keyword_outputs)
|
||||||
|
.filter_map(|(keyword_desc, out)| {
|
||||||
|
if keyword_desc.required && out.is_none() {
|
||||||
|
Some(keyword_desc.name)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
debug_assert!(!missing_keyword_only_arguments.is_empty());
|
||||||
|
self.missing_required_arguments("keyword", &missing_keyword_only_arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add the argument name to the error message of an error which occurred during argument extraction
|
#[cold]
|
||||||
pub fn argument_extraction_error(py: Python, arg_name: &str, error: PyErr) -> PyErr {
|
fn missing_required_positional_arguments(&self, output: &[Option<&PyAny>]) -> PyErr {
|
||||||
if error.is_instance_of::<PyTypeError>(py) {
|
let missing_positional_arguments: Vec<_> = self
|
||||||
let reason = error
|
.positional_parameter_names
|
||||||
.value(py)
|
.iter()
|
||||||
.str()
|
.take(self.required_positional_parameters)
|
||||||
.unwrap_or_else(|_| PyString::new(py, ""));
|
.zip(output)
|
||||||
PyTypeError::new_err(format!("argument '{}': {}", arg_name, reason))
|
.filter_map(|(param, out)| if out.is_none() { Some(*param) } else { None })
|
||||||
} else {
|
.collect();
|
||||||
error
|
|
||||||
|
debug_assert!(!missing_positional_arguments.is_empty());
|
||||||
|
self.missing_required_arguments("positional", &missing_positional_arguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
src/gil.rs
10
src/gil.rs
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
//! Interaction with Python's global interpreter lock
|
//! Interaction with Python's global interpreter lock
|
||||||
|
|
||||||
use crate::{ffi, internal_tricks::Unsendable, Python};
|
use crate::impl_::not_send::{NotSend, NOT_SEND};
|
||||||
|
use crate::{ffi, Python};
|
||||||
use parking_lot::{const_mutex, Mutex, Once};
|
use parking_lot::{const_mutex, Mutex, Once};
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -157,6 +158,7 @@ where
|
||||||
pub struct GILGuard {
|
pub struct GILGuard {
|
||||||
gstate: ffi::PyGILState_STATE,
|
gstate: ffi::PyGILState_STATE,
|
||||||
pool: ManuallyDrop<Option<GILPool>>,
|
pool: ManuallyDrop<Option<GILPool>>,
|
||||||
|
_not_send: NotSend,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GILGuard {
|
impl GILGuard {
|
||||||
|
@ -230,6 +232,7 @@ impl GILGuard {
|
||||||
GILGuard {
|
GILGuard {
|
||||||
gstate,
|
gstate,
|
||||||
pool: ManuallyDrop::new(pool),
|
pool: ManuallyDrop::new(pool),
|
||||||
|
_not_send: NOT_SEND,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,7 +335,7 @@ pub struct GILPool {
|
||||||
/// Initial length of owned objects and anys.
|
/// Initial length of owned objects and anys.
|
||||||
/// `Option` is used since TSL can be broken when `new` is called from `atexit`.
|
/// `Option` is used since TSL can be broken when `new` is called from `atexit`.
|
||||||
start: Option<usize>,
|
start: Option<usize>,
|
||||||
no_send: Unsendable,
|
_not_send: NotSend,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GILPool {
|
impl GILPool {
|
||||||
|
@ -351,11 +354,12 @@ impl GILPool {
|
||||||
POOL.update_counts(Python::assume_gil_acquired());
|
POOL.update_counts(Python::assume_gil_acquired());
|
||||||
GILPool {
|
GILPool {
|
||||||
start: OWNED_OBJECTS.try_with(|o| o.borrow().len()).ok(),
|
start: OWNED_OBJECTS.try_with(|o| o.borrow().len()).ok(),
|
||||||
no_send: Unsendable::default(),
|
_not_send: NOT_SEND,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the Python token associated with this [`GILPool`].
|
/// Gets the Python token associated with this [`GILPool`].
|
||||||
|
#[inline]
|
||||||
pub fn python(&self) -> Python {
|
pub fn python(&self) -> Python {
|
||||||
unsafe { Python::assume_gil_acquired() }
|
unsafe { Python::assume_gil_acquired() }
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,4 +5,8 @@
|
||||||
//! breaking semver guarantees.
|
//! breaking semver guarantees.
|
||||||
|
|
||||||
pub mod deprecations;
|
pub mod deprecations;
|
||||||
|
pub mod extract_argument;
|
||||||
pub mod freelist;
|
pub mod freelist;
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub mod frompyobject;
|
||||||
|
pub(crate) mod not_send;
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
use crate::{
|
||||||
|
exceptions::PyTypeError, type_object::PyTypeObject, FromPyObject, PyAny, PyErr, PyResult,
|
||||||
|
Python,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[inline]
|
||||||
|
pub fn extract_argument<'py, T>(obj: &'py PyAny, arg_name: &str) -> PyResult<T>
|
||||||
|
where
|
||||||
|
T: FromPyObject<'py>,
|
||||||
|
{
|
||||||
|
match obj.extract() {
|
||||||
|
Ok(e) => Ok(e),
|
||||||
|
Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds the argument name to the error message of an error which occurred during argument extraction.
|
||||||
|
///
|
||||||
|
/// Only modifies TypeError. (Cannot guarantee all exceptions have constructors from
|
||||||
|
/// single string.)
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[cold]
|
||||||
|
pub fn argument_extraction_error(py: Python, arg_name: &str, error: PyErr) -> PyErr {
|
||||||
|
if error.get_type(py) == PyTypeError::type_object(py) {
|
||||||
|
PyTypeError::new_err(format!("argument '{}': {}", arg_name, error.value(py)))
|
||||||
|
} else {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
use crate::{exceptions::PyTypeError, PyErr, Python};
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
pub fn failed_to_extract_enum(
|
||||||
|
py: Python,
|
||||||
|
type_name: &str,
|
||||||
|
variant_names: &[&str],
|
||||||
|
error_names: &[&str],
|
||||||
|
errors: &[PyErr],
|
||||||
|
) -> PyErr {
|
||||||
|
let mut err_msg = format!(
|
||||||
|
"failed to extract enum {} ('{}')",
|
||||||
|
type_name,
|
||||||
|
error_names.join(" | ")
|
||||||
|
);
|
||||||
|
for ((variant_name, error_name), error) in variant_names.iter().zip(error_names).zip(errors) {
|
||||||
|
err_msg.push('\n');
|
||||||
|
err_msg.push_str(&format!(
|
||||||
|
"- variant {variant_name} ({error_name}): {error_msg}",
|
||||||
|
variant_name = variant_name,
|
||||||
|
error_name = error_name,
|
||||||
|
error_msg = error.value(py).str().unwrap().to_str().unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
PyTypeError::new_err(err_msg)
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use crate::Python;
|
||||||
|
|
||||||
|
/// A marker type that makes the type !Send.
|
||||||
|
/// Workaround for lack of !Send on stable (https://github.com/rust-lang/rust/issues/68318).
|
||||||
|
pub(crate) struct NotSend(PhantomData<*mut Python<'static>>);
|
||||||
|
|
||||||
|
pub(crate) const NOT_SEND: NotSend = NotSend(PhantomData);
|
|
@ -1,12 +1,5 @@
|
||||||
use crate::ffi::{Py_ssize_t, PY_SSIZE_T_MAX};
|
use crate::ffi::{Py_ssize_t, PY_SSIZE_T_MAX};
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
/// A marker type that makes the type !Send.
|
|
||||||
///
|
|
||||||
/// Temporal hack until <https://github.com/rust-lang/rust/issues/13231> is resolved.
|
|
||||||
pub(crate) type Unsendable = PhantomData<Rc<()>>;
|
|
||||||
|
|
||||||
pub struct PrivateMarker;
|
pub struct PrivateMarker;
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ Python interpreter to exit.
|
||||||
|
|
||||||
impl PanicException {
|
impl PanicException {
|
||||||
// Try to format the error in the same way panic does
|
// Try to format the error in the same way panic does
|
||||||
|
#[cold]
|
||||||
pub(crate) fn from_panic_payload(payload: Box<dyn Any + Send + 'static>) -> PyErr {
|
pub(crate) fn from_panic_payload(payload: Box<dyn Any + Send + 'static>) -> PyErr {
|
||||||
if let Some(string) = payload.downcast_ref::<String>() {
|
if let Some(string) = payload.downcast_ref::<String>() {
|
||||||
Self::new_err((string.clone(),))
|
Self::new_err((string.clone(),))
|
||||||
|
|
108
src/pyclass.rs
108
src/pyclass.rs
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
ffi::CString,
|
ffi::{CStr, CString},
|
||||||
os::raw::{c_char, c_int, c_uint, c_void},
|
os::raw::{c_char, c_int, c_uint, c_void},
|
||||||
ptr,
|
ptr,
|
||||||
};
|
};
|
||||||
|
@ -29,32 +29,6 @@ pub trait PyClass:
|
||||||
type BaseNativeType: PyTypeInfo + PyNativeType;
|
type BaseNativeType: PyTypeInfo + PyNativeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For collecting slot items.
|
|
||||||
#[derive(Default)]
|
|
||||||
struct TypeSlots(Vec<ffi::PyType_Slot>);
|
|
||||||
|
|
||||||
impl TypeSlots {
|
|
||||||
fn push(&mut self, slot: c_int, pfunc: *mut c_void) {
|
|
||||||
self.0.push(ffi::PyType_Slot { slot, pfunc });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tp_doc<T: PyClass>() -> PyResult<Option<*mut c_void>> {
|
|
||||||
Ok(match T::DOC {
|
|
||||||
"\0" => None,
|
|
||||||
s if s.as_bytes().ends_with(b"\0") => Some(s.as_ptr() as _),
|
|
||||||
// If the description is not null-terminated, create CString and leak it
|
|
||||||
s => Some(CString::new(s)?.into_raw() as _),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_type_name<T: PyTypeInfo>(module_name: Option<&str>) -> PyResult<*mut c_char> {
|
|
||||||
Ok(match module_name {
|
|
||||||
Some(module_name) => CString::new(format!("{}.{}", module_name, T::NAME))?.into_raw(),
|
|
||||||
None => CString::new(format!("builtins.{}", T::NAME))?.into_raw(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_raw<T>(vec: Vec<T>) -> *mut c_void {
|
fn into_raw<T>(vec: Vec<T>) -> *mut c_void {
|
||||||
Box::into_raw(vec.into_boxed_slice()) as _
|
Box::into_raw(vec.into_boxed_slice()) as _
|
||||||
}
|
}
|
||||||
|
@ -66,41 +40,53 @@ pub(crate) fn create_type_object<T>(
|
||||||
where
|
where
|
||||||
T: PyClass,
|
T: PyClass,
|
||||||
{
|
{
|
||||||
let mut slots = TypeSlots::default();
|
let mut slots = Vec::new();
|
||||||
|
|
||||||
slots.push(ffi::Py_tp_base, T::BaseType::type_object_raw(py) as _);
|
fn push_slot(slots: &mut Vec<ffi::PyType_Slot>, slot: c_int, pfunc: *mut c_void) {
|
||||||
if let Some(doc) = tp_doc::<T>()? {
|
slots.push(ffi::PyType_Slot { slot, pfunc });
|
||||||
slots.push(ffi::Py_tp_doc, doc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
slots.push(ffi::Py_tp_new, T::get_new().unwrap_or(fallback_new) as _);
|
push_slot(
|
||||||
slots.push(ffi::Py_tp_dealloc, tp_dealloc::<T> as _);
|
&mut slots,
|
||||||
|
ffi::Py_tp_base,
|
||||||
|
T::BaseType::type_object_raw(py) as _,
|
||||||
|
);
|
||||||
|
if let Some(doc) = py_class_doc(T::DOC) {
|
||||||
|
push_slot(&mut slots, ffi::Py_tp_doc, doc as _);
|
||||||
|
}
|
||||||
|
|
||||||
|
push_slot(
|
||||||
|
&mut slots,
|
||||||
|
ffi::Py_tp_new,
|
||||||
|
T::get_new().unwrap_or(fallback_new) as _,
|
||||||
|
);
|
||||||
|
push_slot(&mut slots, ffi::Py_tp_dealloc, tp_dealloc::<T> as _);
|
||||||
|
|
||||||
if let Some(alloc) = T::get_alloc() {
|
if let Some(alloc) = T::get_alloc() {
|
||||||
slots.push(ffi::Py_tp_alloc, alloc as _);
|
push_slot(&mut slots, ffi::Py_tp_alloc, alloc as _);
|
||||||
}
|
}
|
||||||
if let Some(free) = T::get_free() {
|
if let Some(free) = T::get_free() {
|
||||||
slots.push(ffi::Py_tp_free, free as _);
|
push_slot(&mut slots, ffi::Py_tp_free, free as _);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(Py_3_9)]
|
#[cfg(Py_3_9)]
|
||||||
{
|
{
|
||||||
let members = py_class_members::<T>();
|
let members = py_class_members(PyCell::<T>::dict_offset(), PyCell::<T>::weakref_offset());
|
||||||
if !members.is_empty() {
|
if !members.is_empty() {
|
||||||
slots.push(ffi::Py_tp_members, into_raw(members))
|
push_slot(&mut slots, ffi::Py_tp_members, into_raw(members))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// normal methods
|
// normal methods
|
||||||
let methods = py_class_method_defs(&T::for_each_method_def);
|
let methods = py_class_method_defs(&T::for_each_method_def);
|
||||||
if !methods.is_empty() {
|
if !methods.is_empty() {
|
||||||
slots.push(ffi::Py_tp_methods, into_raw(methods));
|
push_slot(&mut slots, ffi::Py_tp_methods, into_raw(methods));
|
||||||
}
|
}
|
||||||
|
|
||||||
// properties
|
// properties
|
||||||
let props = py_class_properties(T::Dict::IS_DUMMY, &T::for_each_method_def);
|
let props = py_class_properties(T::Dict::IS_DUMMY, &T::for_each_method_def);
|
||||||
if !props.is_empty() {
|
if !props.is_empty() {
|
||||||
slots.push(ffi::Py_tp_getset, into_raw(props));
|
push_slot(&mut slots, ffi::Py_tp_getset, into_raw(props));
|
||||||
}
|
}
|
||||||
|
|
||||||
// protocol methods
|
// protocol methods
|
||||||
|
@ -109,16 +95,16 @@ where
|
||||||
has_gc_methods |= proto_slots
|
has_gc_methods |= proto_slots
|
||||||
.iter()
|
.iter()
|
||||||
.any(|slot| slot.slot == ffi::Py_tp_clear || slot.slot == ffi::Py_tp_traverse);
|
.any(|slot| slot.slot == ffi::Py_tp_clear || slot.slot == ffi::Py_tp_traverse);
|
||||||
slots.0.extend_from_slice(proto_slots);
|
slots.extend_from_slice(proto_slots);
|
||||||
});
|
});
|
||||||
|
|
||||||
slots.push(0, ptr::null_mut());
|
push_slot(&mut slots, 0, ptr::null_mut());
|
||||||
let mut spec = ffi::PyType_Spec {
|
let mut spec = ffi::PyType_Spec {
|
||||||
name: get_type_name::<T>(module_name)?,
|
name: py_class_qualified_name(module_name, T::NAME)?,
|
||||||
basicsize: std::mem::size_of::<T::Layout>() as c_int,
|
basicsize: std::mem::size_of::<T::Layout>() as c_int,
|
||||||
itemsize: 0,
|
itemsize: 0,
|
||||||
flags: py_class_flags(has_gc_methods, T::IS_GC, T::IS_BASETYPE),
|
flags: py_class_flags(has_gc_methods, T::IS_GC, T::IS_BASETYPE),
|
||||||
slots: slots.0.as_mut_ptr(),
|
slots: slots.as_mut_ptr(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let type_object = unsafe { ffi::PyType_FromSpec(&mut spec) };
|
let type_object = unsafe { ffi::PyType_FromSpec(&mut spec) };
|
||||||
|
@ -188,6 +174,33 @@ fn tp_init_additional<T: PyClass>(type_object: *mut ffi::PyTypeObject) {
|
||||||
#[cfg(any(Py_LIMITED_API, Py_3_10))]
|
#[cfg(any(Py_LIMITED_API, Py_3_10))]
|
||||||
fn tp_init_additional<T: PyClass>(_type_object: *mut ffi::PyTypeObject) {}
|
fn tp_init_additional<T: PyClass>(_type_object: *mut ffi::PyTypeObject) {}
|
||||||
|
|
||||||
|
fn py_class_doc(class_doc: &str) -> Option<*mut c_char> {
|
||||||
|
match class_doc {
|
||||||
|
"\0" => None,
|
||||||
|
s => {
|
||||||
|
// To pass *mut pointer to python safely, leak a CString in whichever case
|
||||||
|
let cstring = if s.as_bytes().last() == Some(&0) {
|
||||||
|
CStr::from_bytes_with_nul(s.as_bytes())
|
||||||
|
.unwrap_or_else(|e| panic!("doc contains interior nul byte: {:?} in {}", e, s))
|
||||||
|
.to_owned()
|
||||||
|
} else {
|
||||||
|
CString::new(s)
|
||||||
|
.unwrap_or_else(|e| panic!("doc contains interior nul byte: {:?} in {}", e, s))
|
||||||
|
};
|
||||||
|
Some(cstring.into_raw())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn py_class_qualified_name(module_name: Option<&str>, class_name: &str) -> PyResult<*mut c_char> {
|
||||||
|
Ok(CString::new(format!(
|
||||||
|
"{}.{}",
|
||||||
|
module_name.unwrap_or("builtins"),
|
||||||
|
class_name
|
||||||
|
))?
|
||||||
|
.into_raw())
|
||||||
|
}
|
||||||
|
|
||||||
fn py_class_flags(has_gc_methods: bool, is_gc: bool, is_basetype: bool) -> c_uint {
|
fn py_class_flags(has_gc_methods: bool, is_gc: bool, is_basetype: bool) -> c_uint {
|
||||||
let mut flags = if has_gc_methods || is_gc {
|
let mut flags = if has_gc_methods || is_gc {
|
||||||
ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC
|
ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC
|
||||||
|
@ -230,7 +243,10 @@ fn py_class_method_defs(
|
||||||
///
|
///
|
||||||
/// Only works on Python 3.9 and up.
|
/// Only works on Python 3.9 and up.
|
||||||
#[cfg(Py_3_9)]
|
#[cfg(Py_3_9)]
|
||||||
fn py_class_members<T: PyClass>() -> Vec<ffi::structmember::PyMemberDef> {
|
fn py_class_members(
|
||||||
|
dict_offset: Option<isize>,
|
||||||
|
weakref_offset: Option<isize>,
|
||||||
|
) -> Vec<ffi::structmember::PyMemberDef> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn offset_def(name: &'static str, offset: ffi::Py_ssize_t) -> ffi::structmember::PyMemberDef {
|
fn offset_def(name: &'static str, offset: ffi::Py_ssize_t) -> ffi::structmember::PyMemberDef {
|
||||||
ffi::structmember::PyMemberDef {
|
ffi::structmember::PyMemberDef {
|
||||||
|
@ -245,12 +261,12 @@ fn py_class_members<T: PyClass>() -> Vec<ffi::structmember::PyMemberDef> {
|
||||||
let mut members = Vec::new();
|
let mut members = Vec::new();
|
||||||
|
|
||||||
// __dict__ support
|
// __dict__ support
|
||||||
if let Some(dict_offset) = PyCell::<T>::dict_offset() {
|
if let Some(dict_offset) = dict_offset {
|
||||||
members.push(offset_def("__dictoffset__\0", dict_offset));
|
members.push(offset_def("__dictoffset__\0", dict_offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
// weakref support
|
// weakref support
|
||||||
if let Some(weakref_offset) = PyCell::<T>::weakref_offset() {
|
if let Some(weakref_offset) = weakref_offset {
|
||||||
members.push(offset_def("__weaklistoffset__\0", weakref_offset));
|
members.push(offset_def("__weaklistoffset__\0", weakref_offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use crate::err::{self, PyDowncastError, PyErr, PyResult};
|
use crate::err::{self, PyDowncastError, PyErr, PyResult};
|
||||||
use crate::gil::{self, GILGuard, GILPool};
|
use crate::gil::{self, GILGuard, GILPool};
|
||||||
|
use crate::impl_::not_send::NotSend;
|
||||||
use crate::type_object::{PyTypeInfo, PyTypeObject};
|
use crate::type_object::{PyTypeInfo, PyTypeObject};
|
||||||
use crate::types::{PyAny, PyDict, PyModule, PyType};
|
use crate::types::{PyAny, PyDict, PyModule, PyType};
|
||||||
use crate::{ffi, AsPyPointer, FromPyPointer, IntoPyPointer, PyNativeType, PyObject, PyTryFrom};
|
use crate::{ffi, AsPyPointer, FromPyPointer, IntoPyPointer, PyNativeType, PyObject, PyTryFrom};
|
||||||
|
@ -184,7 +185,7 @@ impl PartialOrd<(u8, u8, u8)> for PythonVersionInfo<'_> {
|
||||||
/// [`Py::clone_ref`]: crate::Py::clone_ref
|
/// [`Py::clone_ref`]: crate::Py::clone_ref
|
||||||
/// [Memory Management]: https://pyo3.rs/main/memory.html#gil-bound-memory
|
/// [Memory Management]: https://pyo3.rs/main/memory.html#gil-bound-memory
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Python<'py>(PhantomData<&'py GILGuard>);
|
pub struct Python<'py>(PhantomData<(&'py GILGuard, NotSend)>);
|
||||||
|
|
||||||
impl Python<'_> {
|
impl Python<'_> {
|
||||||
/// Acquires the global interpreter lock, allowing access to the Python interpreter. The
|
/// Acquires the global interpreter lock, allowing access to the Python interpreter. The
|
||||||
|
|
|
@ -29,6 +29,7 @@ fn _test_compile_errors() {
|
||||||
t.compile_fail("tests/ui/invalid_pymodule_args.rs");
|
t.compile_fail("tests/ui/invalid_pymodule_args.rs");
|
||||||
t.compile_fail("tests/ui/missing_clone.rs");
|
t.compile_fail("tests/ui/missing_clone.rs");
|
||||||
t.compile_fail("tests/ui/reject_generics.rs");
|
t.compile_fail("tests/ui/reject_generics.rs");
|
||||||
|
t.compile_fail("tests/ui/not_send.rs");
|
||||||
|
|
||||||
tests_rust_1_49(&t);
|
tests_rust_1_49(&t);
|
||||||
tests_rust_1_55(&t);
|
tests_rust_1_55(&t);
|
||||||
|
|
|
@ -312,8 +312,6 @@ pub enum Foo<'a> {
|
||||||
#[pyo3(item("foo"))]
|
#[pyo3(item("foo"))]
|
||||||
a: String,
|
a: String,
|
||||||
},
|
},
|
||||||
#[pyo3(transparent)]
|
|
||||||
CatchAll(&'a PyAny),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass]
|
#[pyclass]
|
||||||
|
@ -381,15 +379,52 @@ fn test_enum() {
|
||||||
Foo::StructWithGetItemArg { a } => assert_eq!(a, "test"),
|
Foo::StructWithGetItemArg { a } => assert_eq!(a, "test"),
|
||||||
_ => panic!("Expected extracting Foo::StructWithGetItemArg, got {:?}", f),
|
_ => panic!("Expected extracting Foo::StructWithGetItemArg, got {:?}", f),
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_enum_error() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
let dict = PyDict::new(py);
|
let dict = PyDict::new(py);
|
||||||
let f = Foo::extract(dict.as_ref()).expect("Failed to extract Foo from dict");
|
let err = Foo::extract(dict.as_ref()).unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"\
|
||||||
|
TypeError: failed to extract enum Foo ('TupleVar | StructVar | TransparentTuple | TransparentStructVar | StructVarGetAttrArg | StructWithGetItem | StructWithGetItemArg')
|
||||||
|
- variant TupleVar (TupleVar): 'dict' object cannot be converted to 'PyTuple'
|
||||||
|
- variant StructVar (StructVar): 'dict' object has no attribute 'test'
|
||||||
|
- variant TransparentTuple (TransparentTuple): 'dict' object cannot be interpreted as an integer
|
||||||
|
- variant TransparentStructVar (TransparentStructVar): failed to extract field Foo :: TransparentStructVar.a
|
||||||
|
- variant StructVarGetAttrArg (StructVarGetAttrArg): 'dict' object has no attribute 'bla'
|
||||||
|
- variant StructWithGetItem (StructWithGetItem): 'a'
|
||||||
|
- variant StructWithGetItemArg (StructWithGetItemArg): 'foo'"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromPyObject)]
|
||||||
|
enum EnumWithCatchAll<'a> {
|
||||||
|
#[pyo3(transparent)]
|
||||||
|
Foo(Foo<'a>),
|
||||||
|
#[pyo3(transparent)]
|
||||||
|
CatchAll(&'a PyAny),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_enum_catch_all() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let dict = PyDict::new(py);
|
||||||
|
let f = EnumWithCatchAll::extract(dict.as_ref())
|
||||||
|
.expect("Failed to extract EnumWithCatchAll from dict");
|
||||||
match f {
|
match f {
|
||||||
Foo::CatchAll(any) => {
|
EnumWithCatchAll::CatchAll(any) => {
|
||||||
let d = <&PyDict>::extract(any).expect("Expected pydict");
|
let d = <&PyDict>::extract(any).expect("Expected pydict");
|
||||||
assert!(d.is_empty());
|
assert!(d.is_empty());
|
||||||
}
|
}
|
||||||
_ => panic!("Expected extracting Foo::CatchAll, got {:?}", f),
|
_ => panic!(
|
||||||
|
"Expected extracting EnumWithCatchAll::CatchAll, got {:?}",
|
||||||
|
f
|
||||||
|
),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -412,10 +447,11 @@ fn test_err_rename() {
|
||||||
assert!(f.is_err());
|
assert!(f.is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
f.unwrap_err().to_string(),
|
f.unwrap_err().to_string(),
|
||||||
"TypeError: failed to extract enum Bar (\'str | uint | int\')\n- variant A (str): \
|
"\
|
||||||
\'dict\' object cannot be converted to \'PyString\'\n- variant B (uint): \'dict\' object \
|
TypeError: failed to extract enum Bar (\'str | uint | int\')
|
||||||
cannot be interpreted as an integer\n- variant C (int): \'dict\' object cannot be \
|
- variant A (str): \'dict\' object cannot be converted to \'PyString\'
|
||||||
interpreted as an integer\n"
|
- variant B (uint): \'dict\' object cannot be interpreted as an integer
|
||||||
|
- variant C (int): \'dict\' object cannot be interpreted as an integer"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
fn test_not_send_allow_threads(py: Python) {
|
||||||
|
py.allow_threads(|| { drop(py); });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
test_not_send_allow_threads(py);
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
error[E0277]: `*mut pyo3::Python<'static>` cannot be shared between threads safely
|
||||||
|
--> tests/ui/not_send.rs:4:8
|
||||||
|
|
|
||||||
|
4 | py.allow_threads(|| { drop(py); });
|
||||||
|
| ^^^^^^^^^^^^^ `*mut pyo3::Python<'static>` cannot be shared between threads safely
|
||||||
|
|
|
||||||
|
= help: within `pyo3::Python<'_>`, the trait `Sync` is not implemented for `*mut pyo3::Python<'static>`
|
||||||
|
= note: required because it appears within the type `PhantomData<*mut pyo3::Python<'static>>`
|
||||||
|
= note: required because it appears within the type `pyo3::impl_::not_send::NotSend`
|
||||||
|
= note: required because it appears within the type `(&GILGuard, pyo3::impl_::not_send::NotSend)`
|
||||||
|
= note: required because it appears within the type `PhantomData<(&GILGuard, pyo3::impl_::not_send::NotSend)>`
|
||||||
|
= note: required because it appears within the type `pyo3::Python<'_>`
|
||||||
|
= note: required because of the requirements on the impl of `Send` for `&pyo3::Python<'_>`
|
||||||
|
= note: required because it appears within the type `[closure@$DIR/tests/ui/not_send.rs:4:22: 4:38]`
|
Loading…
Reference in New Issue