Merge branch 'master' into abi3-merge-master

This commit is contained in:
Alex Gaynor 2020-09-10 17:01:13 -04:00
commit 5bfb4674b1
50 changed files with 1269 additions and 951 deletions

View File

@ -1,5 +1,6 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file. For help with updating to new
PyO3 versions, please see the [migration guide](https://pyo3.rs/master/migration.html).
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
@ -18,9 +19,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Add macro attribute to `#[pyfn]` and `#[pyfunction]` to pass the module of a Python function to the function - Add macro attribute to `#[pyfn]` and `#[pyfunction]` to pass the module of a Python function to the function
body. [#1143](https://github.com/PyO3/pyo3/pull/1143) body. [#1143](https://github.com/PyO3/pyo3/pull/1143)
- Add `add_function()` and `add_submodule()` functions to `PyModule` [#1143](https://github.com/PyO3/pyo3/pull/1143) - Add `add_function()` and `add_submodule()` functions to `PyModule` [#1143](https://github.com/PyO3/pyo3/pull/1143)
- Add native `PyCFunction` and `PyFunction` types, change `add_function` to take a wrapper returning
a `&PyCFunction`instead of `PyObject`. [#1163](https://github.com/PyO3/pyo3/pull/1163)
### Changed ### Changed
- Exception types have been renamed from e.g. `RuntimeError` to `PyRuntimeError`, and are now only accessible by `&T` or `Py<T>` similar to other Python-native types. The old names continue to exist but are deprecated. [#1024](https://github.com/PyO3/pyo3/pull/1024) - Exception types have been renamed from e.g. `RuntimeError` to `PyRuntimeError`, and are now accessible by `&T` or `Py<T>` similar to other Python-native types. The old names continue to exist but are deprecated. [#1024](https://github.com/PyO3/pyo3/pull/1024) [#1115](https://github.com/PyO3/pyo3/pull/1115)
- Rename `PyException::py_err()` to `PyException::new_err()`.
- Rename `PyUnicodeDecodeErr::new_err()` to `PyUnicodeDecodeErr::new()`.
- Remove `PyStopIteration::stop_iteration()`.
- Correct FFI definitions `Py_SetProgramName` and `Py_SetPythonHome` to take `*const` argument instead of `*mut`. [#1021](https://github.com/PyO3/pyo3/pull/1021) - Correct FFI definitions `Py_SetProgramName` and `Py_SetPythonHome` to take `*const` argument instead of `*mut`. [#1021](https://github.com/PyO3/pyo3/pull/1021)
- Rename `PyString::to_string` to `to_str`, change return type `Cow<str>` to `&str`. [#1023](https://github.com/PyO3/pyo3/pull/1023) - Rename `PyString::to_string` to `to_str`, change return type `Cow<str>` to `&str`. [#1023](https://github.com/PyO3/pyo3/pull/1023)
- Correct FFI definition `_PyLong_AsByteArray` `*mut c_uchar` argument instead of `*const c_uchar`. [#1029](https://github.com/PyO3/pyo3/pull/1029) - Correct FFI definition `_PyLong_AsByteArray` `*mut c_uchar` argument instead of `*const c_uchar`. [#1029](https://github.com/PyO3/pyo3/pull/1029)
@ -30,7 +36,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Change `PyIterator::from_object` to return `PyResult<PyIterator>` instead of `Result<PyIterator, PyDowncastError>`. [#1051](https://github.com/PyO3/pyo3/pull/1051) - Change `PyIterator::from_object` to return `PyResult<PyIterator>` instead of `Result<PyIterator, PyDowncastError>`. [#1051](https://github.com/PyO3/pyo3/pull/1051)
- `IntoPy` is no longer implied by `FromPy`. [#1063](https://github.com/PyO3/pyo3/pull/1063) - `IntoPy` is no longer implied by `FromPy`. [#1063](https://github.com/PyO3/pyo3/pull/1063)
- `PyObject` is now just a type alias for `Py<PyAny>`. [#1063](https://github.com/PyO3/pyo3/pull/1063) - `PyObject` is now just a type alias for `Py<PyAny>`. [#1063](https://github.com/PyO3/pyo3/pull/1063)
- Implement `Send + Sync` for `PyErr`. `PyErr::new`, `PyErr::from_type`, `PyException::py_err` and `PyException::into` have had these bounds added to their arguments. [#1067](https://github.com/PyO3/pyo3/pull/1067) - Rework PyErr to be compatible with the `std::error::Error` trait: [#1067](https://github.com/PyO3/pyo3/pull/1067) [#1115](https://github.com/PyO3/pyo3/pull/1115)
- Implement `Display`, `Error`, `Send` and `Sync` for `PyErr` and `PyErrArguments`.
- Add `PyErr::instance()` which returns `&PyBaseException`.
- `PyErr`'s fields are now an implementation detail. The equivalent values can be accessed with `PyErr::ptype()`, `PyErr::pvalue()` and `PyErr::ptraceback()`.
- Change `PyErr::print()` and `PyErr::print_and_set_sys_last_vars()` to take `&self` instead of `self`.
- Remove `PyErrValue`, `PyErr::from_value`, `PyErr::into_normalized()`, and `PyErr::normalize()`.
- Remove `PyException::into()` and `Into<PyResult<T>>` for `PyErr` and `PyException`.
- Change `#[pyproto]` to return NotImplemented for operators for which Python can try a reversed operation. #[1072](https://github.com/PyO3/pyo3/pull/1072) - Change `#[pyproto]` to return NotImplemented for operators for which Python can try a reversed operation. #[1072](https://github.com/PyO3/pyo3/pull/1072)
- `PyModule::add` now uses `IntoPy<PyObject>` instead of `ToPyObject`. #[1124](https://github.com/PyO3/pyo3/pull/1124) - `PyModule::add` now uses `IntoPy<PyObject>` instead of `ToPyObject`. #[1124](https://github.com/PyO3/pyo3/pull/1124)
- Add nested modules as `&PyModule` instead of using the wrapper generated by `#[pymodule]`. [#1143](https://github.com/PyO3/pyo3/pull/1143) - Add nested modules as `&PyModule` instead of using the wrapper generated by `#[pymodule]`. [#1143](https://github.com/PyO3/pyo3/pull/1143)

View File

@ -67,7 +67,7 @@ fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
/// A Python module implemented in Rust. /// A Python module implemented in Rust.
#[pymodule] #[pymodule]
fn string_sum(py: Python, m: &PyModule) -> PyResult<()> { fn string_sum(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string))?; m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(()) Ok(())
} }

View File

@ -215,29 +215,29 @@ impl TzClass {
#[pymodule] #[pymodule]
fn datetime(_py: Python<'_>, m: &PyModule) -> PyResult<()> { fn datetime(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(make_date))?; m.add_function(wrap_pyfunction!(make_date, m)?)?;
m.add_function(wrap_pyfunction!(get_date_tuple))?; m.add_function(wrap_pyfunction!(get_date_tuple, m)?)?;
m.add_function(wrap_pyfunction!(date_from_timestamp))?; m.add_function(wrap_pyfunction!(date_from_timestamp, m)?)?;
m.add_function(wrap_pyfunction!(make_time))?; m.add_function(wrap_pyfunction!(make_time, m)?)?;
m.add_function(wrap_pyfunction!(get_time_tuple))?; m.add_function(wrap_pyfunction!(get_time_tuple, m)?)?;
m.add_function(wrap_pyfunction!(make_delta))?; m.add_function(wrap_pyfunction!(make_delta, m)?)?;
m.add_function(wrap_pyfunction!(get_delta_tuple))?; m.add_function(wrap_pyfunction!(get_delta_tuple, m)?)?;
m.add_function(wrap_pyfunction!(make_datetime))?; m.add_function(wrap_pyfunction!(make_datetime, m)?)?;
m.add_function(wrap_pyfunction!(get_datetime_tuple))?; m.add_function(wrap_pyfunction!(get_datetime_tuple, m)?)?;
m.add_function(wrap_pyfunction!(datetime_from_timestamp))?; m.add_function(wrap_pyfunction!(datetime_from_timestamp, m)?)?;
// Python 3.6+ functions // Python 3.6+ functions
#[cfg(Py_3_6)] #[cfg(Py_3_6)]
{ {
m.add_function(wrap_pyfunction!(time_with_fold))?; m.add_function(wrap_pyfunction!(time_with_fold, m)?)?;
#[cfg(not(PyPy))] #[cfg(not(PyPy))]
{ {
m.add_function(wrap_pyfunction!(get_time_tuple_fold))?; m.add_function(wrap_pyfunction!(get_time_tuple_fold, m)?)?;
m.add_function(wrap_pyfunction!(get_datetime_tuple_fold))?; m.add_function(wrap_pyfunction!(get_datetime_tuple_fold, m)?)?;
} }
} }
m.add_function(wrap_pyfunction!(issue_219))?; m.add_function(wrap_pyfunction!(issue_219, m)?)?;
m.add_class::<TzClass>()?; m.add_class::<TzClass>()?;
Ok(()) Ok(())

View File

@ -31,7 +31,7 @@ fn double(x: i32) -> i32 {
#[pymodule] #[pymodule]
fn othermod(_py: Python<'_>, m: &PyModule) -> PyResult<()> { fn othermod(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(double))?; m.add_function(wrap_pyfunction!(double, m)?)?;
m.add_class::<ModClass>()?; m.add_class::<ModClass>()?;

View File

@ -56,8 +56,8 @@ fn count_line(line: &str, needle: &str) -> usize {
#[pymodule] #[pymodule]
fn word_count(_py: Python<'_>, m: &PyModule) -> PyResult<()> { fn word_count(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(search))?; m.add_wrapped(wrap_pyfunction!(search))?;
m.add_function(wrap_pyfunction!(search_sequential))?; m.add_function(wrap_pyfunction!(search_sequential, m)?)?;
m.add_function(wrap_pyfunction!(search_sequential_allow_threads))?; m.add_function(wrap_pyfunction!(search_sequential_allow_threads, m)?)?;
Ok(()) Ok(())
} }

View File

@ -61,7 +61,7 @@ use pyo3::exceptions::PyTypeError;
fn main() { fn main() {
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
let py = gil.python(); let py = gil.python();
PyErr::new::<PyTypeError, _>("Error").restore(py); PyTypeError::new_err("Error").restore(py);
assert!(PyErr::occurred(py)); assert!(PyErr::occurred(py));
drop(PyErr::fetch(py)); drop(PyErr::fetch(py));
} }
@ -76,7 +76,7 @@ If you already have a Python exception instance, you can simply call [`PyErr::fr
PyErr::from_instance(py, err).restore(py); PyErr::from_instance(py, err).restore(py);
``` ```
If a Rust type exists for the exception, then it is possible to use the `py_err` method. If a Rust type exists for the exception, then it is possible to use the `new_err` method.
For example, each standard exception defined in the `pyo3::exceptions` module For example, each standard exception defined in the `pyo3::exceptions` module
has a corresponding Rust type, exceptions defined by [`create_exception!`] and [`import_exception!`] macro has a corresponding Rust type, exceptions defined by [`create_exception!`] and [`import_exception!`] macro
have Rust types as well. have Rust types as well.
@ -87,7 +87,7 @@ have Rust types as well.
# fn check_for_error() -> bool {false} # fn check_for_error() -> bool {false}
fn my_func(arg: PyObject) -> PyResult<()> { fn my_func(arg: PyObject) -> PyResult<()> {
if check_for_error() { if check_for_error() {
Err(PyValueError::py_err("argument is wrong")) Err(PyValueError::new_err("argument is wrong"))
} else { } else {
Ok(()) Ok(())
} }
@ -115,41 +115,32 @@ fn main() {
[`Python::is_instance`] calls the underlying [`PyType::is_instance`](https://docs.rs/pyo3/latest/pyo3/types/struct.PyType.html#method.is_instance) [`Python::is_instance`] calls the underlying [`PyType::is_instance`](https://docs.rs/pyo3/latest/pyo3/types/struct.PyType.html#method.is_instance)
method to do the actual work. method to do the actual work.
To check the type of an exception, you can simply do: To check the type of an exception, you can similarly do:
```rust ```rust
# use pyo3::exceptions::PyTypeError; # use pyo3::exceptions::PyTypeError;
# use pyo3::prelude::*; # use pyo3::prelude::*;
# fn main() {
# let gil = Python::acquire_gil(); # let gil = Python::acquire_gil();
# let py = gil.python(); # let py = gil.python();
# let err = PyTypeError::py_err(()); # let err = PyTypeError::new_err(());
err.is_instance::<PyTypeError>(py); err.is_instance::<PyTypeError>(py);
# }
``` ```
## Handling Rust errors ## Handling Rust errors
The vast majority of operations in this library will return [`PyResult<T>`](https://docs.rs/pyo3/latest/pyo3/prelude/type.PyResult.html), The vast majority of operations in this library will return
[`PyResult<T>`](https://docs.rs/pyo3/latest/pyo3/prelude/type.PyResult.html),
which is an alias for the type `Result<T, PyErr>`. which is an alias for the type `Result<T, PyErr>`.
A [`PyErr`] represents a Python exception. A [`PyErr`] represents a Python exception. Errors within the PyO3 library are also exposed as
Errors within the PyO3 library are also exposed as Python exceptions. Python exceptions.
The PyO3 library handles Python exceptions in two stages. During the first stage, a [`PyErr`] instance is If your code has a custom error type e.g. `MyError`, adding an implementation of
created. At this stage, holding Python's GIL is not required. During the second stage, an actual Python `std::convert::From<MyError> for PyErr` is usually enough. PyO3 will then automatically convert
exception instance is created and set active in the Python interpreter. your error to a Python exception when needed.
In simple cases, for custom errors adding an implementation of `std::convert::From<T>` trait
for this custom error is enough. `PyErr::new` accepts an argument in the form
of `ToPyObject + 'static`. If the `'static` constraint can not be satisfied or
more complex arguments are required, the
[`PyErrArguments`](https://docs.rs/pyo3/latest/pyo3/trait.PyErrArguments.html)
trait can be implemented. In that case, actual exception argument creation is delayed
until a `Python` object is available.
```rust ```rust
# use pyo3::{PyErr, PyResult}; # use pyo3::prelude::*;
# use pyo3::exceptions::PyOSError; # use pyo3::exceptions::PyOSError;
# use std::error::Error; # use std::error::Error;
# use std::fmt; # use std::fmt;
@ -170,11 +161,12 @@ until a `Python` object is available.
# } # }
impl std::convert::From<CustomIOError> for PyErr { impl std::convert::From<CustomIOError> for PyErr {
fn from(err: CustomIOError) -> PyErr { fn from(err: CustomIOError) -> PyErr {
PyOSError::py_err(err.to_string()) PyOSError::new_err(err.to_string())
} }
} }
fn connect(s: String) -> PyResult<bool> { #[pyfunction]
fn connect(s: String) -> Result<bool, CustomIOError> {
bind("127.0.0.1:80")?; bind("127.0.0.1:80")?;
Ok(true) Ok(true)
} }
@ -195,6 +187,10 @@ fn parse_int(s: String) -> PyResult<usize> {
The code snippet above will raise a `ValueError` in Python if `String::parse()` returns an error. The code snippet above will raise a `ValueError` in Python if `String::parse()` returns an error.
If lazy construction of the Python exception instance is desired, the
[`PyErrArguments`](https://docs.rs/pyo3/latest/pyo3/trait.PyErrArguments.html)
trait can be implemented. In that case, actual exception argument creation is delayed
until the `PyErr` is needed.
## Using exceptions defined in Python code ## Using exceptions defined in Python code
@ -213,7 +209,7 @@ fn tell(file: &PyAny) -> PyResult<u64> {
use pyo3::exceptions::*; use pyo3::exceptions::*;
match file.call_method0("tell") { match file.call_method0("tell") {
Err(_) => Err(io::UnsupportedOperation::py_err("not supported: tell")), Err(_) => Err(io::UnsupportedOperation::new_err("not supported: tell")),
Ok(x) => x.extract::<u64>(), Ok(x) => x.extract::<u64>(),
} }
} }

View File

@ -36,7 +36,7 @@ fn double(x: usize) -> usize {
#[pymodule] #[pymodule]
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> { fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(double)).unwrap(); m.add_function(wrap_pyfunction!(double, m)?).unwrap();
Ok(()) Ok(())
} }
@ -65,7 +65,7 @@ fn num_kwds(kwds: Option<&PyDict>) -> usize {
#[pymodule] #[pymodule]
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> { fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(num_kwds)).unwrap(); m.add_function(wrap_pyfunction!(num_kwds, m)?).unwrap();
Ok(()) Ok(())
} }
@ -206,7 +206,7 @@ fn pyfunction_with_module(module: &PyModule) -> PyResult<&str> {
#[pymodule] #[pymodule]
fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> { fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(pyfunction_with_module)) m.add_function(wrap_pyfunction!(pyfunction_with_module, m)?)
} }
# fn main() {} # fn main() {}

View File

@ -5,13 +5,88 @@ For a detailed list of all changes, see the [CHANGELOG](changelog.md).
## from 0.11.* to 0.12 ## from 0.11.* to 0.12
### `FromPy` has been removed ### `PyErr` has been reworked
To simplify the PyO3 public conversion trait hierarchy, the `FromPy` has been removed. In PyO3
`0.11` there were two ways to define the to-Python conversion for a type: `FromPy<T> for PyObject`,
and `IntoPy<PyObject> for T`.
Now, the canonical implementation is always `IntoPy`, so downstream crates may need to adjust In PyO3 `0.12` the `PyErr` type has been re-implemented to be significantly more compatible with
accordingly. the standard Rust error handling ecosystem. Specificially `PyErr` now implements
`Error + Send + Sync`, which are the standard traits used for error types.
While this has necessitated the removal of a number of APIs, the resulting `PyErr` type should now
be much more easier to work with. The following sections list the changes in detail and how to
migrate to the new APIs.
#### `PyErr::new` and `PyErr::from_type` now require `Send + Sync` for their argument
For most uses no change will be needed. If you are trying to construct `PyErr` from a value that is
not `Send + Sync`, you will need to first create the Python object and then use
`PyErr::from_instance`.
Similarly, any types which implemented `PyErrArguments` will now need to be `Send + Sync`.
#### `PyErr`'s contents are now private
It is no longer possible to access the fields `.ptype`, `.pvalue` and `.ptraceback` of a `PyErr`.
You should instead now use the new methods `PyErr::ptype()`, `PyErr::pvalue()` and `PyErr::ptraceback()`.
#### `PyErrValue` and `PyErr::from_value` have been removed
As these were part the internals of `PyErr` which have been reworked, these APIs no longer exist.
If you used this API, it is recommended to use `PyException::new_err` (see [the section on
Exception types](#exception-types-have-been-reworked)).
#### `Into<PyResult<T>>` for `PyErr` has been removed
This implementation was redundant. Just construct the `Result::Err` variant directly.
Before:
```rust,ignore
let result: PyResult<()> = PyErr::new::<TypeError, _>("error message").into();
```
After (also using the new reworked exception types; see the following section):
```rust
# use pyo3::{PyErr, PyResult, exceptions::PyTypeError};
let result: PyResult<()> = Err(PyTypeError::new_err("error message"));
```
### Exception types have been reworked
Previously exception types were zero-sized marker types purely used to construct `PyErr`. In PyO3
0.12, these types have been replaced with full definitions and are usable in the same way as `PyAny`, `PyDict` etc. This
makes it possible to interact with Python exception objects.
The new types also have names starting with the "Py" prefix. For example, before:
```rust,ignore
let err: PyErr = TypeError::py_err("error message");
```
After:
```
# use pyo3::{PyErr, PyResult, Python, type_object::PyTypeObject};
# use pyo3::exceptions::{PyBaseException, PyTypeError};
# Python::with_gil(|py| -> PyResult<()> {
let err: PyErr = PyTypeError::new_err("error message");
// Uses Display for PyErr, new for PyO3 0.12
assert_eq!(err.to_string(), "TypeError: error message");
// Now possible to interact with exception instances, new for PyO3 0.12
let instance: &PyBaseException = err.instance(py);
assert_eq!(instance.getattr("__class__")?, PyTypeError::type_object(py).as_ref());
# Ok(())
# }).unwrap();
```
### `FromPy` has been removed
To simplify the PyO3 conversion traits, the `FromPy` trait has been removed. Previously there were
two ways to define the to-Python conversion for a type:
`FromPy<T> for PyObject` and `IntoPy<PyObject> for T`.
Now there is only one way to define the conversion, `IntoPy`, so downstream crates may need to
adjust accordingly.
Before: Before:
```rust,ignore ```rust,ignore
@ -85,7 +160,6 @@ let list_ref: &PyList = list_py.as_ref(py);
# }) # })
``` ```
## from 0.10.* to 0.11 ## from 0.10.* to 0.11
### Stable Rust ### Stable Rust

View File

@ -73,7 +73,7 @@ fn subfunction() -> String {
} }
fn init_submodule(module: &PyModule) -> PyResult<()> { fn init_submodule(module: &PyModule) -> PyResult<()> {
module.add_function(wrap_pyfunction!(subfunction))?; module.add_function(wrap_pyfunction!(subfunction, module)?)?;
Ok(()) Ok(())
} }

View File

@ -488,7 +488,7 @@ pub struct UserModel {
#[pymodule] #[pymodule]
fn trait_exposure(_py: Python, m: &PyModule) -> PyResult<()> { fn trait_exposure(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<UserModel>()?; m.add_class::<UserModel>()?;
m.add_function(wrap_pyfunction!(solve_wrapper))?; m.add_function(wrap_pyfunction!(solve_wrapper, m)?)?;
Ok(()) Ok(())
} }

View File

@ -78,7 +78,7 @@ impl<'a> Enum<'a> {
.map(|s| format!("{} ({})", s.to_string_lossy(), type_name)) .map(|s| format!("{} ({})", s.to_string_lossy(), type_name))
.unwrap_or_else(|_| type_name.to_string()); .unwrap_or_else(|_| type_name.to_string());
let err_msg = format!("Can't convert {} to {}", from, #error_names); let err_msg = format!("Can't convert {} to {}", from, #error_names);
Err(::pyo3::exceptions::PyTypeError::py_err(err_msg)) Err(::pyo3::exceptions::PyTypeError::new_err(err_msg))
) )
} }
} }
@ -263,7 +263,7 @@ impl<'a> Container<'a> {
quote!( quote!(
let s = <::pyo3::types::PyTuple as ::pyo3::conversion::PyTryFrom>::try_from(obj)?; let s = <::pyo3::types::PyTuple as ::pyo3::conversion::PyTryFrom>::try_from(obj)?;
if s.len() != #len { if s.len() != #len {
return Err(::pyo3::exceptions::PyValueError::py_err(#msg)) return Err(::pyo3::exceptions::PyValueError::new_err(#msg))
} }
let slice = s.as_slice(); let slice = s.as_slice();
Ok(#self_ty(#fields)) Ok(#self_ty(#fields))

View File

@ -44,7 +44,7 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> {
let item: syn::ItemFn = syn::parse_quote! { let item: syn::ItemFn = syn::parse_quote! {
fn block_wrapper() { fn block_wrapper() {
#function_to_python #function_to_python
#module_name.add_function(&#function_wrapper_ident)?; #module_name.add_function(#function_wrapper_ident(#module_name)?)?;
} }
}; };
stmts.extend(item.block.stmts.into_iter()); stmts.extend(item.block.stmts.into_iter());
@ -204,50 +204,26 @@ pub fn add_fn_to_module(
let python_name = &spec.python_name; let python_name = &spec.python_name;
let wrapper = function_c_wrapper(&func.sig.ident, &spec, pyfn_attrs.pass_module); let name = &func.sig.ident;
let wrapper_ident = format_ident!("__pyo3_raw_{}", name);
let wrapper = function_c_wrapper(name, &wrapper_ident, &spec, pyfn_attrs.pass_module);
Ok(quote! { Ok(quote! {
#wrapper
fn #function_wrapper_ident<'a>( fn #function_wrapper_ident<'a>(
args: impl Into<pyo3::derive_utils::WrapPyFunctionArguments<'a>> args: impl Into<pyo3::derive_utils::PyFunctionArguments<'a>>
) -> pyo3::PyResult<pyo3::PyObject> { ) -> pyo3::PyResult<&'a pyo3::types::PyCFunction> {
let arg = args.into(); pyo3::types::PyCFunction::new_with_keywords(#wrapper_ident, stringify!(#python_name), #doc, args.into())
let (py, maybe_module) = arg.into_py_and_maybe_module();
#wrapper
let _def = pyo3::class::PyMethodDef {
ml_name: stringify!(#python_name),
ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap),
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS,
ml_doc: #doc,
};
let (mod_ptr, name) = if let Some(m) = maybe_module {
let mod_ptr = <pyo3::types::PyModule as ::pyo3::conversion::AsPyPointer>::as_ptr(m);
let name = m.name()?;
let name = <&str as pyo3::conversion::IntoPy<PyObject>>::into_py(name, py);
(mod_ptr, <PyObject as pyo3::AsPyPointer>::as_ptr(&name))
} else {
(std::ptr::null_mut(), std::ptr::null_mut())
};
let function = unsafe {
pyo3::PyObject::from_owned_ptr(
py,
pyo3::ffi::PyCFunction_NewEx(
Box::into_raw(Box::new(_def.as_method_def())),
mod_ptr,
name
)
)
};
Ok(function)
} }
}) })
} }
/// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords) /// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords)
fn function_c_wrapper(name: &Ident, spec: &method::FnSpec<'_>, pass_module: bool) -> TokenStream { fn function_c_wrapper(
name: &Ident,
wrapper_ident: &Ident,
spec: &method::FnSpec<'_>,
pass_module: bool,
) -> TokenStream {
let names: Vec<Ident> = get_arg_names(&spec); let names: Vec<Ident> = get_arg_names(&spec);
let cb; let cb;
let slf_module; let slf_module;
@ -265,9 +241,8 @@ fn function_c_wrapper(name: &Ident, spec: &method::FnSpec<'_>, pass_module: bool
slf_module = None; slf_module = None;
}; };
let body = pymethod::impl_arg_params(spec, None, cb); let body = pymethod::impl_arg_params(spec, None, cb);
quote! { quote! {
unsafe extern "C" fn __wrap( unsafe extern "C" fn #wrapper_ident(
_slf: *mut pyo3::ffi::PyObject, _slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject, _args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject _kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject

View File

@ -156,10 +156,10 @@ pub unsafe trait Element: Copy {
fn validate(b: &ffi::Py_buffer) -> PyResult<()> { fn validate(b: &ffi::Py_buffer) -> PyResult<()> {
// shape and stride information must be provided when we use PyBUF_FULL_RO // shape and stride information must be provided when we use PyBUF_FULL_RO
if b.shape.is_null() { if b.shape.is_null() {
return Err(exceptions::PyBufferError::py_err("Shape is Null")); return Err(exceptions::PyBufferError::new_err("Shape is Null"));
} }
if b.strides.is_null() { if b.strides.is_null() {
return Err(exceptions::PyBufferError::py_err( return Err(exceptions::PyBufferError::new_err(
"PyBuffer: Strides is Null", "PyBuffer: Strides is Null",
)); ));
} }
@ -190,7 +190,7 @@ impl<T: Element> PyBuffer<T> {
{ {
Ok(buf) Ok(buf)
} else { } else {
Err(exceptions::PyBufferError::py_err( Err(exceptions::PyBufferError::new_err(
"Incompatible type as buffer", "Incompatible type as buffer",
)) ))
} }
@ -441,7 +441,7 @@ impl<T: Element> PyBuffer<T> {
fn copy_to_slice_impl(&self, py: Python, target: &mut [T], fort: u8) -> PyResult<()> { fn copy_to_slice_impl(&self, py: Python, target: &mut [T], fort: u8) -> PyResult<()> {
if mem::size_of_val(target) != self.len_bytes() { if mem::size_of_val(target) != self.len_bytes() {
return Err(exceptions::PyBufferError::py_err( return Err(exceptions::PyBufferError::new_err(
"Slice length does not match buffer length.", "Slice length does not match buffer length.",
)); ));
} }
@ -528,7 +528,7 @@ impl<T: Element> PyBuffer<T> {
return buffer_readonly_error(); return buffer_readonly_error();
} }
if mem::size_of_val(source) != self.len_bytes() { if mem::size_of_val(source) != self.len_bytes() {
return Err(exceptions::PyBufferError::py_err( return Err(exceptions::PyBufferError::new_err(
"Slice length does not match buffer length.", "Slice length does not match buffer length.",
)); ));
} }
@ -564,7 +564,7 @@ impl<T: Element> PyBuffer<T> {
#[inline(always)] #[inline(always)]
fn buffer_readonly_error() -> PyResult<()> { fn buffer_readonly_error() -> PyResult<()> {
Err(exceptions::PyBufferError::py_err( Err(exceptions::PyBufferError::new_err(
"Cannot write to read-only buffer.", "Cannot write to read-only buffer.",
)) ))
} }

View File

@ -86,7 +86,7 @@ impl IntoPyCallbackOutput<ffi::Py_ssize_t> for usize {
if self <= (isize::MAX as usize) { if self <= (isize::MAX as usize) {
Ok(self as isize) Ok(self as isize)
} else { } else {
Err(PyOverflowError::py_err(())) Err(PyOverflowError::new_err(()))
} }
} }
} }
@ -244,11 +244,11 @@ macro_rules! callback_body_without_convert {
Err(e) => { Err(e) => {
// Try to format the error in the same way panic does // Try to format the error in the same way panic does
if let Some(string) = e.downcast_ref::<String>() { if let Some(string) = e.downcast_ref::<String>() {
Err($crate::panic::PanicException::py_err((string.clone(),))) Err($crate::panic::PanicException::new_err((string.clone(),)))
} else if let Some(s) = e.downcast_ref::<&str>() { } else if let Some(s) = e.downcast_ref::<&str>() {
Err($crate::panic::PanicException::py_err((s.to_string(),))) Err($crate::panic::PanicException::new_err((s.to_string(),)))
} else { } else {
Err($crate::panic::PanicException::py_err(( Err($crate::panic::PanicException::new_err((
"panic from Rust code", "panic from Rust code",
))) )))
} }

View File

@ -9,7 +9,7 @@
//! [typeobj docs](https://docs.python.org/3/c-api/typeobj.html) //! [typeobj docs](https://docs.python.org/3/c-api/typeobj.html)
use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput}; use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput};
use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyErr, PyObject, PyResult}; use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyObject, PyResult};
use std::os::raw::c_int; use std::os::raw::c_int;
/// Operators for the __richcmp__ method /// Operators for the __richcmp__ method
@ -262,7 +262,7 @@ where
ffi::Py_NE => Ok(CompareOp::Ne), ffi::Py_NE => Ok(CompareOp::Ne),
ffi::Py_GT => Ok(CompareOp::Gt), ffi::Py_GT => Ok(CompareOp::Gt),
ffi::Py_GE => Ok(CompareOp::Ge), ffi::Py_GE => Ok(CompareOp::Ge),
_ => Err(PyErr::new::<exceptions::PyValueError, _>( _ => Err(exceptions::PyValueError::new_err(
"tp_richcompare called with invalid comparison operator", "tp_richcompare called with invalid comparison operator",
)), )),
} }

View File

@ -112,7 +112,7 @@ impl IntoPyCallbackOutput<*mut ffi::PyObject> for PyIterNextOutput {
fn convert(self, _py: Python) -> PyResult<*mut ffi::PyObject> { fn convert(self, _py: Python) -> PyResult<*mut ffi::PyObject> {
match self { match self {
IterNextOutput::Yield(o) => Ok(o.into_ptr()), IterNextOutput::Yield(o) => Ok(o.into_ptr()),
IterNextOutput::Return(opt) => Err(crate::exceptions::PyStopIteration::py_err((opt,))), IterNextOutput::Return(opt) => Err(crate::exceptions::PyStopIteration::new_err((opt,))),
} }
} }
} }

View File

@ -228,12 +228,10 @@ macro_rules! py_func_set {
let slf = py.from_borrowed_ptr::<$crate::PyCell<$generic>>(slf); let slf = py.from_borrowed_ptr::<$crate::PyCell<$generic>>(slf);
if value.is_null() { if value.is_null() {
Err($crate::PyErr::new::<exceptions::PyNotImplementedError, _>( Err($crate::exceptions::PyNotImplementedError::new_err(format!(
format!( "Subscript deletion not supported by {:?}",
"Subscript deletion not supported by {:?}", stringify!($generic)
stringify!($generic) )))
),
))
} else { } else {
let name = py.from_borrowed_ptr::<$crate::PyAny>(name); let name = py.from_borrowed_ptr::<$crate::PyAny>(name);
let value = py.from_borrowed_ptr::<$crate::PyAny>(value); let value = py.from_borrowed_ptr::<$crate::PyAny>(value);
@ -264,7 +262,7 @@ macro_rules! py_func_del {
.extract()?; .extract()?;
slf.try_borrow_mut()?.$fn_del(name).convert(py) slf.try_borrow_mut()?.$fn_del(name).convert(py)
} else { } else {
Err(PyErr::new::<exceptions::PyNotImplementedError, _>( Err(exceptions::PyNotImplementedError::new_err(
"Subscript assignment not supported", "Subscript assignment not supported",
)) ))
} }

View File

@ -4,7 +4,6 @@
//! Trait and support implementation for implementing mapping support //! Trait and support implementation for implementing mapping support
use crate::callback::IntoPyCallbackOutput; use crate::callback::IntoPyCallbackOutput;
use crate::err::PyErr;
use crate::{exceptions, ffi, FromPyObject, PyClass, PyObject}; use crate::{exceptions, ffi, FromPyObject, PyClass, PyObject};
#[cfg(Py_LIMITED_API)] #[cfg(Py_LIMITED_API)]

View File

@ -136,7 +136,7 @@ impl IntoPyCallbackOutput<*mut ffi::PyObject> for PyIterANextOutput {
match self { match self {
IterANextOutput::Yield(o) => Ok(o.into_ptr()), IterANextOutput::Yield(o) => Ok(o.into_ptr()),
IterANextOutput::Return(opt) => { IterANextOutput::Return(opt) => {
Err(crate::exceptions::PyStopAsyncIteration::py_err((opt,))) Err(crate::exceptions::PyStopAsyncIteration::new_err((opt,)))
} }
} }
} }

View File

@ -279,7 +279,7 @@ mod sq_ass_item_impl {
let slf = py.from_borrowed_ptr::<PyCell<T>>(slf); let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
if value.is_null() { if value.is_null() {
return Err(PyErr::new::<exceptions::PyNotImplementedError, _>(format!( return Err(exceptions::PyNotImplementedError::new_err(format!(
"Item deletion is not supported by {:?}", "Item deletion is not supported by {:?}",
stringify!(T) stringify!(T)
))); )));

View File

@ -43,7 +43,7 @@ pub fn parse_fn_args<'p>(
let nargs = args.len(); let nargs = args.len();
let mut used_args = 0; let mut used_args = 0;
macro_rules! raise_error { macro_rules! raise_error {
($s: expr $(,$arg:expr)*) => (return Err(PyTypeError::py_err(format!( ($s: expr $(,$arg:expr)*) => (return Err(PyTypeError::new_err(format!(
concat!("{} ", $s), fname.unwrap_or("function") $(,$arg)* concat!("{} ", $s), fname.unwrap_or("function") $(,$arg)*
)))) ))))
} }
@ -209,17 +209,16 @@ where
} }
/// Enum to abstract over the arguments of Python function wrappers. /// Enum to abstract over the arguments of Python function wrappers.
#[doc(hidden)] pub enum PyFunctionArguments<'a> {
pub enum WrapPyFunctionArguments<'a> {
Python(Python<'a>), Python(Python<'a>),
PyModule(&'a PyModule), PyModule(&'a PyModule),
} }
impl<'a> WrapPyFunctionArguments<'a> { impl<'a> PyFunctionArguments<'a> {
pub fn into_py_and_maybe_module(self) -> (Python<'a>, Option<&'a PyModule>) { pub fn into_py_and_maybe_module(self) -> (Python<'a>, Option<&'a PyModule>) {
match self { match self {
WrapPyFunctionArguments::Python(py) => (py, None), PyFunctionArguments::Python(py) => (py, None),
WrapPyFunctionArguments::PyModule(module) => { PyFunctionArguments::PyModule(module) => {
let py = module.py(); let py = module.py();
(py, Some(module)) (py, Some(module))
} }
@ -227,14 +226,14 @@ impl<'a> WrapPyFunctionArguments<'a> {
} }
} }
impl<'a> From<Python<'a>> for WrapPyFunctionArguments<'a> { impl<'a> From<Python<'a>> for PyFunctionArguments<'a> {
fn from(py: Python<'a>) -> WrapPyFunctionArguments<'a> { fn from(py: Python<'a>) -> PyFunctionArguments<'a> {
WrapPyFunctionArguments::Python(py) PyFunctionArguments::Python(py)
} }
} }
impl<'a> From<&'a PyModule> for WrapPyFunctionArguments<'a> { impl<'a> From<&'a PyModule> for PyFunctionArguments<'a> {
fn from(module: &'a PyModule) -> WrapPyFunctionArguments<'a> { fn from(module: &'a PyModule) -> PyFunctionArguments<'a> {
WrapPyFunctionArguments::PyModule(module) PyFunctionArguments::PyModule(module)
} }
} }

View File

@ -1,676 +0,0 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::gil::ensure_gil;
use crate::panic::PanicException;
use crate::type_object::PyTypeObject;
use crate::types::PyType;
use crate::{exceptions, ffi};
use crate::{
AsPyPointer, FromPyPointer, IntoPy, IntoPyPointer, Py, PyAny, PyNativeType, PyObject, Python,
ToBorrowedObject, ToPyObject,
};
use libc::c_int;
use std::borrow::Cow;
use std::ffi::CString;
use std::io;
use std::os::raw::c_char;
use std::ptr::NonNull;
/// Represents a `PyErr` value.
///
/// **Caution:**
///
/// When you construct an instance of `PyErrValue`, we highly recommend to use `from_err_args`
/// method. If you want to to construct `PyErrValue::ToArgs` directly, please do not forget to
/// call `Python::acquire_gil`.
pub enum PyErrValue {
None,
Value(PyObject),
ToArgs(Box<dyn PyErrArguments + Send + Sync>),
ToObject(Box<dyn ToPyObject + Send + Sync>),
}
impl PyErrValue {
pub fn from_err_args<T>(value: T) -> Self
where
T: PyErrArguments + Send + Sync + 'static,
{
let _ = Python::acquire_gil();
PyErrValue::ToArgs(Box::new(value))
}
}
/// Represents a Python exception that was raised.
pub struct PyErr {
/// The type of the exception. This should be either a `PyClass` or a `PyType`.
pub ptype: Py<PyType>,
/// The value of the exception.
///
/// This can be either an instance of `PyObject`, a tuple of arguments to be passed to
/// `ptype`'s constructor, or a single argument to be passed to `ptype`'s constructor. Call
/// `PyErr::to_object()` to get the exception instance in all cases.
pub pvalue: PyErrValue,
/// The `PyTraceBack` object associated with the error.
pub ptraceback: Option<PyObject>,
}
/// Represents the result of a Python call.
pub type PyResult<T> = Result<T, PyErr>;
/// Marker type that indicates an error while downcasting
#[derive(Debug)]
pub struct PyDowncastError<'a> {
from: &'a PyAny,
to: Cow<'static, str>,
}
impl<'a> PyDowncastError<'a> {
pub fn new(from: &'a PyAny, to: impl Into<Cow<'static, str>>) -> Self {
PyDowncastError {
from,
to: to.into(),
}
}
}
/// Helper conversion trait that allows to use custom arguments for exception constructor.
pub trait PyErrArguments {
/// Arguments for exception
fn arguments(&self, _: Python) -> PyObject;
}
impl PyErr {
/// Creates a new PyErr of type `T`.
///
/// `value` can be:
/// * a tuple: the exception instance will be created using Python `T(*tuple)`
/// * any other value: the exception instance will be created using Python `T(value)`
///
/// Note: if `value` is not `Send` or `Sync`, consider using `PyErr::from_value` instead.
///
/// Panics if `T` is not a Python class derived from `BaseException`.
///
/// Example:
/// ```ignore
/// return Err(PyErr::new::<exceptions::PyTypeError, _>("Error message"));
/// ```
///
/// In most cases, you can use a concrete exception's constructors instead:
/// the example is equivalent to
/// ```ignore
/// return Err(exceptions::PyTypeError::py_err("Error message"));
/// return exceptions::PyTypeError::into("Error message");
/// ```
pub fn new<T, V>(value: V) -> PyErr
where
T: PyTypeObject,
V: ToPyObject + Send + Sync + 'static,
{
let gil = ensure_gil();
let py = unsafe { gil.python() };
let ty = T::type_object(py);
assert_ne!(unsafe { ffi::PyExceptionClass_Check(ty.as_ptr()) }, 0);
PyErr {
ptype: ty.into(),
pvalue: PyErrValue::ToObject(Box::new(value)),
ptraceback: None,
}
}
/// Constructs a new error, with the usual lazy initialization of Python exceptions.
///
/// `exc` is the exception type; usually one of the standard exceptions
/// like `exceptions::PyRuntimeError`.
/// `args` is the a tuple of arguments to pass to the exception constructor.
pub fn from_type<A>(exc: &PyType, args: A) -> PyErr
where
A: ToPyObject + Send + Sync + 'static,
{
PyErr {
ptype: exc.into(),
pvalue: PyErrValue::ToObject(Box::new(args)),
ptraceback: None,
}
}
/// Creates a new PyErr of type `T`.
pub fn from_value<T>(value: PyErrValue) -> PyErr
where
T: PyTypeObject,
{
let gil = ensure_gil();
let py = unsafe { gil.python() };
let ty = T::type_object(py);
assert_ne!(unsafe { ffi::PyExceptionClass_Check(ty.as_ptr()) }, 0);
PyErr {
ptype: ty.into(),
pvalue: value,
ptraceback: None,
}
}
/// Creates a new PyErr.
///
/// `obj` must be an Python exception instance, the PyErr will use that instance.
/// If `obj` is a Python exception type object, the PyErr will (lazily) create a new
/// instance of that type.
/// Otherwise, a `TypeError` is created instead.
pub fn from_instance(obj: &PyAny) -> PyErr {
let ptr = obj.as_ptr();
if unsafe { ffi::PyExceptionInstance_Check(ptr) } != 0 {
PyErr {
ptype: unsafe {
Py::from_borrowed_ptr(obj.py(), ffi::PyExceptionInstance_Class(ptr))
},
pvalue: PyErrValue::Value(obj.into()),
ptraceback: None,
}
} else if unsafe { ffi::PyExceptionClass_Check(obj.as_ptr()) } != 0 {
PyErr {
ptype: unsafe { Py::from_borrowed_ptr(obj.py(), ptr) },
pvalue: PyErrValue::None,
ptraceback: None,
}
} else {
PyErr {
ptype: exceptions::PyTypeError::type_object(obj.py()).into(),
pvalue: PyErrValue::ToObject(Box::new("exceptions must derive from BaseException")),
ptraceback: None,
}
}
}
/// Gets whether an error is present in the Python interpreter's global state.
#[inline]
pub fn occurred(_: Python) -> bool {
unsafe { !ffi::PyErr_Occurred().is_null() }
}
/// Retrieves the current error from the Python interpreter's global state.
///
/// The error is cleared from the Python interpreter.
/// If no error is set, returns a `SystemError`.
///
/// If the error fetched is a `PanicException` (which would have originated from a panic in a
/// pyo3 callback) then this function will resume the panic.
pub fn fetch(py: Python) -> PyErr {
unsafe {
let mut ptype: *mut ffi::PyObject = std::ptr::null_mut();
let mut pvalue: *mut ffi::PyObject = std::ptr::null_mut();
let mut ptraceback: *mut ffi::PyObject = std::ptr::null_mut();
ffi::PyErr_Fetch(&mut ptype, &mut pvalue, &mut ptraceback);
let err = PyErr::new_from_ffi_tuple(py, ptype, pvalue, ptraceback);
if ptype == PanicException::type_object(py).as_ptr() {
let msg: String = PyAny::from_borrowed_ptr_or_opt(py, pvalue)
.and_then(|obj| obj.extract().ok())
.unwrap_or_else(|| String::from("Unwrapped panic from Python code"));
eprintln!(
"--- PyO3 is resuming a panic after fetching a PanicException from Python. ---"
);
eprintln!("Python stack trace below:");
err.print(py);
std::panic::resume_unwind(Box::new(msg))
}
err
}
}
/// Creates a new exception type with the given name, which must be of the form
/// `<module>.<ExceptionName>`, as required by `PyErr_NewException`.
///
/// `base` can be an existing exception type to subclass, or a tuple of classes
/// `dict` specifies an optional dictionary of class variables and methods
pub fn new_type<'p>(
_: Python<'p>,
name: &str,
base: Option<&PyType>,
dict: Option<PyObject>,
) -> NonNull<ffi::PyTypeObject> {
let base: *mut ffi::PyObject = match base {
None => std::ptr::null_mut(),
Some(obj) => obj.as_ptr(),
};
let dict: *mut ffi::PyObject = match dict {
None => std::ptr::null_mut(),
Some(obj) => obj.as_ptr(),
};
unsafe {
let null_terminated_name =
CString::new(name).expect("Failed to initialize nul terminated exception name");
NonNull::new_unchecked(ffi::PyErr_NewException(
null_terminated_name.as_ptr() as *mut c_char,
base,
dict,
) as *mut ffi::PyTypeObject)
}
}
unsafe fn new_from_ffi_tuple(
py: Python,
ptype: *mut ffi::PyObject,
pvalue: *mut ffi::PyObject,
ptraceback: *mut ffi::PyObject,
) -> PyErr {
// Note: must not panic to ensure all owned pointers get acquired correctly,
// and because we mustn't panic in normalize().
let pvalue = if let Some(obj) = PyObject::from_owned_ptr_or_opt(py, pvalue) {
PyErrValue::Value(obj)
} else {
PyErrValue::None
};
let ptype = if ptype.is_null() {
<exceptions::PySystemError as PyTypeObject>::type_object(py).into()
} else {
Py::from_owned_ptr(py, ptype)
};
PyErr {
ptype,
pvalue,
ptraceback: PyObject::from_owned_ptr_or_opt(py, ptraceback),
}
}
/// Prints a standard traceback to `sys.stderr`.
pub fn print(self, py: Python) {
self.restore(py);
unsafe { ffi::PyErr_PrintEx(0) }
}
/// Prints a standard traceback to `sys.stderr`, and sets
/// `sys.last_{type,value,traceback}` attributes to this exception's data.
pub fn print_and_set_sys_last_vars(self, py: Python) {
self.restore(py);
unsafe { ffi::PyErr_PrintEx(1) }
}
/// Returns true if the current exception matches the exception in `exc`.
///
/// If `exc` is a class object, this also returns `true` when `self` is an instance of a subclass.
/// If `exc` is a tuple, all exceptions in the tuple (and recursively in subtuples) are searched for a match.
pub fn matches<T>(&self, py: Python, exc: T) -> bool
where
T: ToBorrowedObject,
{
exc.with_borrowed_ptr(py, |exc| unsafe {
ffi::PyErr_GivenExceptionMatches(self.ptype.as_ptr(), exc) != 0
})
}
/// Returns true if the current exception is instance of `T`.
pub fn is_instance<T>(&self, py: Python) -> bool
where
T: PyTypeObject,
{
unsafe {
ffi::PyErr_GivenExceptionMatches(self.ptype.as_ptr(), T::type_object(py).as_ptr()) != 0
}
}
/// Normalizes the error. This ensures that the exception value is an instance
/// of the exception type.
pub fn normalize(&mut self, py: Python) {
// The normalization helper function involves temporarily moving out of the &mut self,
// which requires some unsafe trickery:
unsafe {
std::ptr::write(self, std::ptr::read(self).into_normalized(py));
}
// This is safe as long as normalized() doesn't unwind due to a panic.
}
/// Helper function for normalizing the error by deconstructing and reconstructing the `PyErr`.
/// Must not panic for safety in `normalize()`.
fn into_normalized(self, py: Python) -> PyErr {
let PyErr {
ptype,
pvalue,
ptraceback,
} = self;
let mut pvalue = match pvalue {
PyErrValue::None => std::ptr::null_mut(),
PyErrValue::Value(ob) => ob.into_ptr(),
PyErrValue::ToArgs(ob) => ob.arguments(py).into_ptr(),
PyErrValue::ToObject(ob) => ob.to_object(py).into_ptr(),
};
let mut ptype = ptype.into_ptr();
let mut ptraceback = ptraceback.into_ptr();
unsafe {
ffi::PyErr_NormalizeException(&mut ptype, &mut pvalue, &mut ptraceback);
PyErr::new_from_ffi_tuple(py, ptype, pvalue, ptraceback)
}
}
/// Retrieves the exception instance for this error.
///
/// This method takes `mut self` because the error might need
/// to be normalized in order to create the exception instance.
pub fn instance(mut self, py: Python) -> &exceptions::PyBaseException {
self.normalize(py);
match self.pvalue {
PyErrValue::Value(ref instance) => {
let any: &PyAny = unsafe { py.from_owned_ptr(instance.clone_ref(py).into_ptr()) };
any.downcast()
.expect("Normalized error instance should be a BaseException")
}
PyErrValue::None => panic!("This exception is not an instance"),
_ => unreachable!(),
}
}
/// Writes the error back to the Python interpreter's global state.
/// This is the opposite of `PyErr::fetch()`.
#[inline]
pub fn restore(self, py: Python) {
let PyErr {
ptype,
pvalue,
ptraceback,
} = self;
let pvalue = match pvalue {
PyErrValue::None => std::ptr::null_mut(),
PyErrValue::Value(ob) => ob.into_ptr(),
PyErrValue::ToArgs(ob) => ob.arguments(py).into_ptr(),
PyErrValue::ToObject(ob) => ob.to_object(py).into_ptr(),
};
unsafe { ffi::PyErr_Restore(ptype.into_ptr(), pvalue, ptraceback.into_ptr()) }
}
/// Issues a warning message.
/// May return a `PyErr` if warnings-as-errors is enabled.
pub fn warn(py: Python, category: &PyAny, message: &str, stacklevel: i32) -> PyResult<()> {
let message = CString::new(message)?;
unsafe {
error_on_minusone(
py,
ffi::PyErr_WarnEx(
category.as_ptr(),
message.as_ptr(),
stacklevel as ffi::Py_ssize_t,
),
)
}
}
pub fn clone_ref(&self, py: Python) -> PyErr {
let v = match self.pvalue {
PyErrValue::None => PyErrValue::None,
PyErrValue::Value(ref ob) => PyErrValue::Value(ob.clone_ref(py)),
PyErrValue::ToArgs(ref ob) => PyErrValue::Value(ob.arguments(py)),
PyErrValue::ToObject(ref ob) => PyErrValue::Value(ob.to_object(py)),
};
let t = if let Some(ref val) = self.ptraceback {
Some(val.clone_ref(py))
} else {
None
};
PyErr {
ptype: self.ptype.clone_ref(py),
pvalue: v,
ptraceback: t,
}
}
}
impl std::fmt::Debug for PyErr {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
f.write_str(format!("PyErr {{ type: {:?} }}", self.ptype).as_str())
}
}
impl IntoPy<PyObject> for PyErr {
fn into_py(self, py: Python) -> PyObject {
self.instance(py).into()
}
}
impl IntoPy<Py<exceptions::PyBaseException>> for PyErr {
fn into_py(self, py: Python) -> Py<exceptions::PyBaseException> {
self.instance(py).into()
}
}
impl ToPyObject for PyErr {
fn to_object(&self, py: Python) -> PyObject {
let err = self.clone_ref(py);
err.instance(py).into()
}
}
impl<'a> IntoPy<PyObject> for &'a PyErr {
fn into_py(self, py: Python) -> PyObject {
let err = self.clone_ref(py);
err.instance(py).into()
}
}
/// Convert `PyDowncastError` to Python `TypeError`.
impl<'a> std::convert::From<PyDowncastError<'a>> for PyErr {
fn from(err: PyDowncastError) -> PyErr {
exceptions::PyTypeError::py_err(err.to_string())
}
}
impl<'a> std::error::Error for PyDowncastError<'a> {}
impl<'a> std::fmt::Display for PyDowncastError<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(
f,
"Can't convert {} to {}",
self.from.repr().map(|s| s.to_string_lossy()).or_else(|_| {
self.from
.get_type()
.name()
.map_err(|_| std::fmt::Error)
.map(|s| s.into())
})?,
self.to
)
}
}
/// Convert `PyErr` to `io::Error`
impl std::convert::From<PyErr> for std::io::Error {
fn from(err: PyErr) -> Self {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("Python exception: {:?}", err),
)
}
}
/// Convert `PyErr` to `PyResult<T>`
impl<T> std::convert::Into<PyResult<T>> for PyErr {
fn into(self) -> PyResult<T> {
Err(self)
}
}
macro_rules! impl_to_pyerr {
($err: ty, $pyexc: ty) => {
impl PyErrArguments for $err {
fn arguments(&self, py: Python) -> PyObject {
self.to_string().to_object(py)
}
}
impl std::convert::From<$err> for PyErr {
fn from(err: $err) -> PyErr {
PyErr::from_value::<$pyexc>(PyErrValue::from_err_args(err))
}
}
};
}
/// Create `OSError` from `io::Error`
impl std::convert::From<io::Error> for PyErr {
fn from(err: io::Error) -> PyErr {
macro_rules! err_value {
() => {
PyErrValue::from_err_args(err)
};
}
match err.kind() {
io::ErrorKind::BrokenPipe => {
PyErr::from_value::<exceptions::PyBrokenPipeError>(err_value!())
}
io::ErrorKind::ConnectionRefused => {
PyErr::from_value::<exceptions::PyConnectionRefusedError>(err_value!())
}
io::ErrorKind::ConnectionAborted => {
PyErr::from_value::<exceptions::PyConnectionAbortedError>(err_value!())
}
io::ErrorKind::ConnectionReset => {
PyErr::from_value::<exceptions::PyConnectionResetError>(err_value!())
}
io::ErrorKind::Interrupted => {
PyErr::from_value::<exceptions::PyInterruptedError>(err_value!())
}
io::ErrorKind::NotFound => {
PyErr::from_value::<exceptions::PyFileNotFoundError>(err_value!())
}
io::ErrorKind::WouldBlock => {
PyErr::from_value::<exceptions::PyBlockingIOError>(err_value!())
}
io::ErrorKind::TimedOut => {
PyErr::from_value::<exceptions::PyTimeoutError>(err_value!())
}
_ => PyErr::from_value::<exceptions::PyOSError>(err_value!()),
}
}
}
impl PyErrArguments for io::Error {
fn arguments(&self, py: Python) -> PyObject {
self.to_string().to_object(py)
}
}
impl<W: 'static + Send + Sync + std::fmt::Debug> std::convert::From<std::io::IntoInnerError<W>>
for PyErr
{
fn from(err: std::io::IntoInnerError<W>) -> PyErr {
PyErr::from_value::<exceptions::PyOSError>(PyErrValue::from_err_args(err))
}
}
impl<W: Send + Sync + std::fmt::Debug> PyErrArguments for std::io::IntoInnerError<W> {
fn arguments(&self, py: Python) -> PyObject {
self.to_string().to_object(py)
}
}
impl PyErrArguments for std::convert::Infallible {
fn arguments(&self, py: Python) -> PyObject {
"Infalliable!".to_object(py)
}
}
impl std::convert::From<std::convert::Infallible> for PyErr {
fn from(_: std::convert::Infallible) -> PyErr {
PyErr::new::<exceptions::PyValueError, _>("Infalliable!")
}
}
impl_to_pyerr!(std::array::TryFromSliceError, exceptions::PyValueError);
impl_to_pyerr!(std::num::ParseIntError, exceptions::PyValueError);
impl_to_pyerr!(std::num::ParseFloatError, exceptions::PyValueError);
impl_to_pyerr!(std::num::TryFromIntError, exceptions::PyValueError);
impl_to_pyerr!(std::str::ParseBoolError, exceptions::PyValueError);
impl_to_pyerr!(std::ffi::IntoStringError, exceptions::PyUnicodeDecodeError);
impl_to_pyerr!(std::ffi::NulError, exceptions::PyValueError);
impl_to_pyerr!(std::str::Utf8Error, exceptions::PyUnicodeDecodeError);
impl_to_pyerr!(std::string::FromUtf8Error, exceptions::PyUnicodeDecodeError);
impl_to_pyerr!(
std::string::FromUtf16Error,
exceptions::PyUnicodeDecodeError
);
impl_to_pyerr!(
std::char::DecodeUtf16Error,
exceptions::PyUnicodeDecodeError
);
impl_to_pyerr!(std::net::AddrParseError, exceptions::PyValueError);
pub fn panic_after_error(_py: Python) -> ! {
unsafe {
ffi::PyErr_Print();
}
panic!("Python API call failed");
}
/// Returns Ok if the error code is not -1.
#[inline]
pub fn error_on_minusone(py: Python, result: c_int) -> PyResult<()> {
if result != -1 {
Ok(())
} else {
Err(PyErr::fetch(py))
}
}
#[cfg(test)]
mod tests {
use crate::exceptions;
use crate::panic::PanicException;
use crate::{PyErr, Python};
#[test]
fn set_typeerror() {
let gil = Python::acquire_gil();
let py = gil.python();
let err: PyErr = exceptions::PyTypeError::py_err(());
err.restore(py);
assert!(PyErr::occurred(py));
drop(PyErr::fetch(py));
}
#[test]
fn fetching_panic_exception_panics() {
// If -Cpanic=abort is specified, we can't catch panic.
if option_env!("RUSTFLAGS")
.map(|s| s.contains("-Cpanic=abort"))
.unwrap_or(false)
{
return;
}
let gil = Python::acquire_gil();
let py = gil.python();
let err: PyErr = PanicException::py_err("new panic");
err.restore(py);
assert!(PyErr::occurred(py));
let started_unwind =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| PyErr::fetch(py))).is_err();
assert!(started_unwind);
}
#[test]
fn test_pyerr_send_sync() {
fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}
is_send::<PyErr>();
is_sync::<PyErr>();
}
}

69
src/err/err_state.rs Normal file
View File

@ -0,0 +1,69 @@
use crate::{
exceptions::PyBaseException, ffi, types::PyType, IntoPy, IntoPyPointer, Py, PyObject, Python,
};
#[derive(Clone)]
pub(crate) struct PyErrStateNormalized {
pub ptype: Py<PyType>,
pub pvalue: Py<PyBaseException>,
pub ptraceback: Option<PyObject>,
}
pub(crate) enum PyErrState {
Lazy {
ptype: Py<PyType>,
pvalue: Box<dyn FnOnce(Python) -> PyObject + Send + Sync>,
},
FfiTuple {
ptype: Option<PyObject>,
pvalue: Option<PyObject>,
ptraceback: Option<PyObject>,
},
Normalized(PyErrStateNormalized),
}
/// Helper conversion trait that allows to use custom arguments for lazy exception construction.
pub trait PyErrArguments: Send + Sync {
/// Arguments for exception
fn arguments(self, py: Python) -> PyObject;
}
impl<T> PyErrArguments for T
where
T: IntoPy<PyObject> + Send + Sync,
{
fn arguments(self, py: Python) -> PyObject {
self.into_py(py)
}
}
pub(crate) fn boxed_args(
args: impl PyErrArguments + 'static,
) -> Box<dyn FnOnce(Python) -> PyObject + Send + Sync> {
Box::new(|py| args.arguments(py))
}
impl PyErrState {
pub(crate) fn into_ffi_tuple(
self,
py: Python,
) -> (*mut ffi::PyObject, *mut ffi::PyObject, *mut ffi::PyObject) {
match self {
PyErrState::Lazy { ptype, pvalue } => (
ptype.into_ptr(),
pvalue(py).into_ptr(),
std::ptr::null_mut(),
),
PyErrState::FfiTuple {
ptype,
pvalue,
ptraceback,
} => (ptype.into_ptr(), pvalue.into_ptr(), ptraceback.into_ptr()),
PyErrState::Normalized(PyErrStateNormalized {
ptype,
pvalue,
ptraceback,
}) => (ptype.into_ptr(), pvalue.into_ptr(), ptraceback.into_ptr()),
}
}
}

114
src/err/impls.rs Normal file
View File

@ -0,0 +1,114 @@
use crate::{err::PyErrArguments, exceptions, IntoPy, PyErr, PyObject, Python};
use std::io;
/// Convert `PyErr` to `io::Error`
impl std::convert::From<PyErr> for io::Error {
fn from(err: PyErr) -> Self {
io::Error::new(io::ErrorKind::Other, format!("Python exception: {}", err))
}
}
/// Create `OSError` from `io::Error`
impl std::convert::From<io::Error> for PyErr {
fn from(err: io::Error) -> PyErr {
match err.kind() {
io::ErrorKind::BrokenPipe => exceptions::PyBrokenPipeError::new_err(err),
io::ErrorKind::ConnectionRefused => exceptions::PyConnectionRefusedError::new_err(err),
io::ErrorKind::ConnectionAborted => exceptions::PyConnectionAbortedError::new_err(err),
io::ErrorKind::ConnectionReset => exceptions::PyConnectionResetError::new_err(err),
io::ErrorKind::Interrupted => exceptions::PyInterruptedError::new_err(err),
io::ErrorKind::NotFound => exceptions::PyFileNotFoundError::new_err(err),
io::ErrorKind::WouldBlock => exceptions::PyBlockingIOError::new_err(err),
io::ErrorKind::TimedOut => exceptions::PyTimeoutError::new_err(err),
_ => exceptions::PyOSError::new_err(err),
}
}
}
impl PyErrArguments for io::Error {
fn arguments(self, py: Python) -> PyObject {
self.to_string().into_py(py)
}
}
impl<W: 'static + Send + Sync + std::fmt::Debug> std::convert::From<std::io::IntoInnerError<W>>
for PyErr
{
fn from(err: std::io::IntoInnerError<W>) -> PyErr {
exceptions::PyOSError::new_err(err)
}
}
impl<W: Send + Sync + std::fmt::Debug> PyErrArguments for std::io::IntoInnerError<W> {
fn arguments(self, py: Python) -> PyObject {
self.to_string().into_py(py)
}
}
impl std::convert::From<std::convert::Infallible> for PyErr {
fn from(_: std::convert::Infallible) -> PyErr {
unreachable!()
}
}
macro_rules! impl_to_pyerr {
($err: ty, $pyexc: ty) => {
impl PyErrArguments for $err {
fn arguments(self, py: Python) -> PyObject {
self.to_string().into_py(py)
}
}
impl std::convert::From<$err> for PyErr {
fn from(err: $err) -> PyErr {
<$pyexc>::new_err(err)
}
}
};
}
impl_to_pyerr!(std::array::TryFromSliceError, exceptions::PyValueError);
impl_to_pyerr!(std::num::ParseIntError, exceptions::PyValueError);
impl_to_pyerr!(std::num::ParseFloatError, exceptions::PyValueError);
impl_to_pyerr!(std::num::TryFromIntError, exceptions::PyValueError);
impl_to_pyerr!(std::str::ParseBoolError, exceptions::PyValueError);
impl_to_pyerr!(std::ffi::IntoStringError, exceptions::PyUnicodeDecodeError);
impl_to_pyerr!(std::ffi::NulError, exceptions::PyValueError);
impl_to_pyerr!(std::str::Utf8Error, exceptions::PyUnicodeDecodeError);
impl_to_pyerr!(std::string::FromUtf8Error, exceptions::PyUnicodeDecodeError);
impl_to_pyerr!(
std::string::FromUtf16Error,
exceptions::PyUnicodeDecodeError
);
impl_to_pyerr!(
std::char::DecodeUtf16Error,
exceptions::PyUnicodeDecodeError
);
impl_to_pyerr!(std::net::AddrParseError, exceptions::PyValueError);
#[cfg(test)]
mod tests {
use crate::PyErr;
use std::io;
#[test]
fn io_errors() {
let check_err = |kind, expected_ty| {
let py_err: PyErr = io::Error::new(kind, "some error msg").into();
let err_msg = format!("{}: some error msg", expected_ty);
assert_eq!(py_err.to_string(), err_msg);
let os_err: io::Error = py_err.into();
assert_eq!(os_err.to_string(), format!("Python exception: {}", err_msg));
};
check_err(io::ErrorKind::BrokenPipe, "BrokenPipeError");
check_err(io::ErrorKind::ConnectionRefused, "ConnectionRefusedError");
check_err(io::ErrorKind::ConnectionAborted, "ConnectionAbortedError");
check_err(io::ErrorKind::ConnectionReset, "ConnectionResetError");
check_err(io::ErrorKind::Interrupted, "InterruptedError");
check_err(io::ErrorKind::NotFound, "FileNotFoundError");
check_err(io::ErrorKind::WouldBlock, "BlockingIOError");
check_err(io::ErrorKind::TimedOut, "TimeoutError");
}
}

576
src/err/mod.rs Normal file
View File

@ -0,0 +1,576 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::panic::PanicException;
use crate::type_object::PyTypeObject;
use crate::types::PyType;
use crate::{
exceptions::{self, PyBaseException},
ffi,
};
use crate::{
AsPyPointer, FromPyPointer, IntoPy, Py, PyAny, PyNativeType, PyObject, Python,
ToBorrowedObject, ToPyObject,
};
use libc::c_int;
use std::borrow::Cow;
use std::cell::UnsafeCell;
use std::ffi::CString;
use std::os::raw::c_char;
use std::ptr::NonNull;
mod err_state;
mod impls;
pub use err_state::PyErrArguments;
use err_state::{boxed_args, PyErrState, PyErrStateNormalized};
/// Represents a Python exception that was raised.
pub struct PyErr {
// Safety: can only hand out references when in the "normalized" state. Will never change
// after normalization.
//
// The state is temporarily removed from the PyErr during normalization, to avoid
// concurrent modifications.
state: UnsafeCell<Option<PyErrState>>,
}
unsafe impl Send for PyErr {}
unsafe impl Sync for PyErr {}
/// Represents the result of a Python call.
pub type PyResult<T> = Result<T, PyErr>;
/// Marker type that indicates an error while downcasting
#[derive(Debug)]
pub struct PyDowncastError<'a> {
from: &'a PyAny,
to: Cow<'static, str>,
}
impl<'a> PyDowncastError<'a> {
pub fn new(from: &'a PyAny, to: impl Into<Cow<'static, str>>) -> Self {
PyDowncastError {
from,
to: to.into(),
}
}
}
impl PyErr {
/// Creates a new PyErr of type `T`.
///
/// `value` can be:
/// * a tuple: the exception instance will be created using Python `T(*tuple)`
/// * any other value: the exception instance will be created using Python `T(value)`
///
/// Note: if `value` is not `Send` or `Sync`, consider using `PyErr::from_instance` instead.
///
/// Panics if `T` is not a Python class derived from `BaseException`.
///
/// Example:
/// ```ignore
/// return Err(PyErr::new::<exceptions::PyTypeError, _>("Error message"));
/// ```
///
/// In most cases, you can use a concrete exception's constructor instead, which is equivalent:
/// ```ignore
/// return Err(exceptions::PyTypeError::new_err("Error message"));
/// ```
pub fn new<T, A>(args: A) -> PyErr
where
T: PyTypeObject,
A: PyErrArguments + Send + Sync + 'static,
{
Python::with_gil(|py| PyErr::from_type(T::type_object(py), args))
}
/// Constructs a new error, with the usual lazy initialization of Python exceptions.
///
/// `exc` is the exception type; usually one of the standard exceptions
/// like `exceptions::PyRuntimeError`.
/// `args` is the a tuple of arguments to pass to the exception constructor.
pub fn from_type<A>(ty: &PyType, args: A) -> PyErr
where
A: PyErrArguments + Send + Sync + 'static,
{
if unsafe { ffi::PyExceptionClass_Check(ty.as_ptr()) } == 0 {
return exceptions_must_derive_from_base_exception(ty.py());
}
PyErr::from_state(PyErrState::Lazy {
ptype: ty.into(),
pvalue: boxed_args(args),
})
}
/// Creates a new PyErr.
///
/// `obj` must be an Python exception instance, the PyErr will use that instance.
/// If `obj` is a Python exception type object, the PyErr will (lazily) create a new
/// instance of that type.
/// Otherwise, a `TypeError` is created instead.
///
/// # Example
/// ```rust
/// use pyo3::{Python, PyErr, IntoPy, exceptions::PyTypeError, types::PyType};
/// Python::with_gil(|py| {
/// // Case #1: Exception instance
/// let err = PyErr::from_instance(PyTypeError::new_err("some type error",).instance(py));
/// assert_eq!(err.to_string(), "TypeError: some type error");
///
/// // Case #2: Exception type
/// let err = PyErr::from_instance(PyType::new::<PyTypeError>(py));
/// assert_eq!(err.to_string(), "TypeError: ");
///
/// // Case #3: Invalid exception value
/// let err = PyErr::from_instance("foo".into_py(py).as_ref(py));
/// assert_eq!(err.to_string(), "TypeError: exceptions must derive from BaseException");
/// });
/// ```
pub fn from_instance(obj: &PyAny) -> PyErr {
let ptr = obj.as_ptr();
let state = if unsafe { ffi::PyExceptionInstance_Check(ptr) } != 0 {
PyErrState::Normalized(PyErrStateNormalized {
ptype: unsafe {
Py::from_borrowed_ptr(obj.py(), ffi::PyExceptionInstance_Class(ptr))
},
pvalue: unsafe { Py::from_borrowed_ptr(obj.py(), obj.as_ptr()) },
ptraceback: None,
})
} else if unsafe { ffi::PyExceptionClass_Check(obj.as_ptr()) } != 0 {
PyErrState::FfiTuple {
ptype: unsafe { Some(Py::from_borrowed_ptr(obj.py(), ptr)) },
pvalue: None,
ptraceback: None,
}
} else {
return exceptions_must_derive_from_base_exception(obj.py());
};
PyErr::from_state(state)
}
/// Get the type of this exception object.
///
/// The object will be normalized first if needed.
///
/// # Example
/// ```rust
/// use pyo3::{Python, PyErr, exceptions::PyTypeError, types::PyType};
/// Python::with_gil(|py| {
/// let err = PyTypeError::new_err(("some type error",));
/// assert_eq!(err.ptype(py), PyType::new::<PyTypeError>(py));
/// });
/// ```
pub fn ptype<'py>(&'py self, py: Python<'py>) -> &'py PyType {
self.normalized(py).ptype.as_ref(py)
}
/// Get the value of this exception object.
///
/// The object will be normalized first if needed.
///
/// # Example
/// ```rust
/// use pyo3::{Python, PyErr, exceptions::PyTypeError, types::PyType};
/// Python::with_gil(|py| {
/// let err = PyTypeError::new_err(("some type error",));
/// assert_eq!(err.pvalue(py).to_string(), "TypeError: some type error");
/// });
/// ```
pub fn pvalue<'py>(&'py self, py: Python<'py>) -> &'py PyBaseException {
self.normalized(py).pvalue.as_ref(py)
}
/// Get the value of this exception object.
///
/// The object will be normalized first if needed.
///
/// # Example
/// ```rust
/// use pyo3::{Python, PyErr, exceptions::PyTypeError, types::PyType};
/// Python::with_gil(|py| {
/// let err = PyTypeError::new_err(("some type error",));
/// assert_eq!(err.ptraceback(py), None);
/// });
/// ```
pub fn ptraceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyAny> {
self.normalized(py)
.ptraceback
.as_ref()
.map(|obj| obj.as_ref(py))
}
/// Gets whether an error is present in the Python interpreter's global state.
#[inline]
pub fn occurred(_: Python) -> bool {
unsafe { !ffi::PyErr_Occurred().is_null() }
}
/// Retrieves the current error from the Python interpreter's global state.
///
/// The error is cleared from the Python interpreter.
/// If no error is set, returns a `SystemError`.
///
/// If the error fetched is a `PanicException` (which would have originated from a panic in a
/// pyo3 callback) then this function will resume the panic.
pub fn fetch(py: Python) -> PyErr {
unsafe {
let mut ptype: *mut ffi::PyObject = std::ptr::null_mut();
let mut pvalue: *mut ffi::PyObject = std::ptr::null_mut();
let mut ptraceback: *mut ffi::PyObject = std::ptr::null_mut();
ffi::PyErr_Fetch(&mut ptype, &mut pvalue, &mut ptraceback);
let err = PyErr::new_from_ffi_tuple(py, ptype, pvalue, ptraceback);
if ptype == PanicException::type_object(py).as_ptr() {
let msg: String = PyAny::from_borrowed_ptr_or_opt(py, pvalue)
.and_then(|obj| obj.extract().ok())
.unwrap_or_else(|| String::from("Unwrapped panic from Python code"));
eprintln!(
"--- PyO3 is resuming a panic after fetching a PanicException from Python. ---"
);
eprintln!("Python stack trace below:");
err.print(py);
std::panic::resume_unwind(Box::new(msg))
}
err
}
}
/// Creates a new exception type with the given name, which must be of the form
/// `<module>.<ExceptionName>`, as required by `PyErr_NewException`.
///
/// `base` can be an existing exception type to subclass, or a tuple of classes
/// `dict` specifies an optional dictionary of class variables and methods
pub fn new_type<'p>(
_: Python<'p>,
name: &str,
base: Option<&PyType>,
dict: Option<PyObject>,
) -> NonNull<ffi::PyTypeObject> {
let base: *mut ffi::PyObject = match base {
None => std::ptr::null_mut(),
Some(obj) => obj.as_ptr(),
};
let dict: *mut ffi::PyObject = match dict {
None => std::ptr::null_mut(),
Some(obj) => obj.as_ptr(),
};
unsafe {
let null_terminated_name =
CString::new(name).expect("Failed to initialize nul terminated exception name");
NonNull::new_unchecked(ffi::PyErr_NewException(
null_terminated_name.as_ptr() as *mut c_char,
base,
dict,
) as *mut ffi::PyTypeObject)
}
}
/// Create a PyErr from an ffi tuple
///
/// # Safety
/// - `ptype` must be a pointer to valid Python exception type object.
/// - `pvalue` must be a pointer to a valid Python object, or NULL.
/// - `ptraceback` must be a pointer to a valid Python traceback object, or NULL.
unsafe fn new_from_ffi_tuple(
py: Python,
ptype: *mut ffi::PyObject,
pvalue: *mut ffi::PyObject,
ptraceback: *mut ffi::PyObject,
) -> PyErr {
// Note: must not panic to ensure all owned pointers get acquired correctly,
// and because we mustn't panic in normalize().
PyErr::from_state(PyErrState::FfiTuple {
ptype: Py::from_owned_ptr_or_opt(py, ptype),
pvalue: Py::from_owned_ptr_or_opt(py, pvalue),
ptraceback: Py::from_owned_ptr_or_opt(py, ptraceback),
})
}
/// Prints a standard traceback to `sys.stderr`.
pub fn print(&self, py: Python) {
self.clone_ref(py).restore(py);
unsafe { ffi::PyErr_PrintEx(0) }
}
/// Prints a standard traceback to `sys.stderr`, and sets
/// `sys.last_{type,value,traceback}` attributes to this exception's data.
pub fn print_and_set_sys_last_vars(&self, py: Python) {
self.clone_ref(py).restore(py);
unsafe { ffi::PyErr_PrintEx(1) }
}
/// Returns true if the current exception matches the exception in `exc`.
///
/// If `exc` is a class object, this also returns `true` when `self` is an instance of a subclass.
/// If `exc` is a tuple, all exceptions in the tuple (and recursively in subtuples) are searched for a match.
pub fn matches<T>(&self, py: Python, exc: T) -> bool
where
T: ToBorrowedObject,
{
exc.with_borrowed_ptr(py, |exc| unsafe {
ffi::PyErr_GivenExceptionMatches(self.ptype_ptr(), exc) != 0
})
}
/// Returns true if the current exception is instance of `T`.
pub fn is_instance<T>(&self, py: Python) -> bool
where
T: PyTypeObject,
{
unsafe {
ffi::PyErr_GivenExceptionMatches(self.ptype_ptr(), T::type_object(py).as_ptr()) != 0
}
}
/// Retrieves the exception instance for this error.
pub fn instance<'py>(&'py self, py: Python<'py>) -> &'py PyBaseException {
self.normalized(py).pvalue.as_ref(py)
}
/// Consumes self to take ownership of the exception instance for this error.
pub fn into_instance(self, py: Python) -> Py<PyBaseException> {
let out = self.normalized(py).pvalue.as_ref(py).into();
std::mem::forget(self);
out
}
/// Writes the error back to the Python interpreter's global state.
/// This is the opposite of `PyErr::fetch()`.
#[inline]
pub fn restore(self, py: Python) {
let (ptype, pvalue, ptraceback) = self
.state
.into_inner()
.expect("Cannot restore a PyErr while normalizing it")
.into_ffi_tuple(py);
unsafe { ffi::PyErr_Restore(ptype, pvalue, ptraceback) }
}
/// Issues a warning message.
/// May return a `PyErr` if warnings-as-errors is enabled.
pub fn warn(py: Python, category: &PyAny, message: &str, stacklevel: i32) -> PyResult<()> {
let message = CString::new(message)?;
unsafe {
error_on_minusone(
py,
ffi::PyErr_WarnEx(
category.as_ptr(),
message.as_ptr(),
stacklevel as ffi::Py_ssize_t,
),
)
}
}
/// Clone the PyErr. This requires the GIL, which is why PyErr does not implement Clone.
///
/// # Example
/// ```rust
/// use pyo3::{Python, PyErr, exceptions::PyTypeError, types::PyType};
/// Python::with_gil(|py| {
/// let err = PyTypeError::new_err(("some type error",));
/// let err_clone = err.clone_ref(py);
/// assert_eq!(err.ptype(py), err_clone.ptype(py));
/// assert_eq!(err.pvalue(py), err_clone.pvalue(py));
/// assert_eq!(err.ptraceback(py), err_clone.ptraceback(py));
/// });
/// ```
pub fn clone_ref(&self, py: Python) -> PyErr {
PyErr::from_state(PyErrState::Normalized(self.normalized(py).clone()))
}
fn from_state(state: PyErrState) -> PyErr {
PyErr {
state: UnsafeCell::new(Some(state)),
}
}
/// Returns borrowed reference to this Err's type
fn ptype_ptr(&self) -> *mut ffi::PyObject {
match unsafe { &*self.state.get() } {
Some(PyErrState::Lazy { ptype, .. }) => ptype.as_ptr(),
Some(PyErrState::FfiTuple { ptype, .. }) => ptype.as_ptr(),
Some(PyErrState::Normalized(n)) => n.ptype.as_ptr(),
None => panic!("Cannot access exception type while normalizing"),
}
}
fn normalized(&self, py: Python) -> &PyErrStateNormalized {
// This process is safe because:
// - Access is guaranteed not to be concurrent thanks to `Python` GIL token
// - Write happens only once, and then never will change again.
// - State is set to None during the normalization process, so that a second
// concurrent normalization attempt will panic before changing anything.
if let Some(PyErrState::Normalized(n)) = unsafe { &*self.state.get() } {
return n;
}
let state = unsafe {
(*self.state.get())
.take()
.expect("Cannot normalize a PyErr while already normalizing it.")
};
let (mut ptype, mut pvalue, mut ptraceback) = state.into_ffi_tuple(py);
unsafe {
ffi::PyErr_NormalizeException(&mut ptype, &mut pvalue, &mut ptraceback);
let self_state = &mut *self.state.get();
*self_state = Some(PyErrState::Normalized(PyErrStateNormalized {
ptype: Py::from_owned_ptr_or_opt(py, ptype)
.unwrap_or_else(|| exceptions::PySystemError::type_object(py).into()),
pvalue: Py::from_owned_ptr_or_opt(py, pvalue).unwrap_or_else(|| {
exceptions::PySystemError::new_err("Exception value missing")
.instance(py)
.into_py(py)
}),
ptraceback: PyObject::from_owned_ptr_or_opt(py, ptraceback),
}));
match self_state {
Some(PyErrState::Normalized(n)) => n,
_ => unreachable!(),
}
}
}
}
impl std::fmt::Debug for PyErr {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
f.write_str(format!("PyErr {{ type: {:?} }}", self.ptype_ptr()).as_str())
}
}
impl std::fmt::Display for PyErr {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
Python::with_gil(|py| self.instance(py).fmt(f))
}
}
impl std::error::Error for PyErr {}
impl IntoPy<PyObject> for PyErr {
fn into_py(self, py: Python) -> PyObject {
self.into_instance(py).into()
}
}
impl ToPyObject for PyErr {
fn to_object(&self, py: Python) -> PyObject {
self.clone_ref(py).into_py(py)
}
}
impl<'a> IntoPy<PyObject> for &'a PyErr {
fn into_py(self, py: Python) -> PyObject {
self.clone_ref(py).into_py(py)
}
}
/// Convert `PyDowncastError` to Python `TypeError`.
impl<'a> std::convert::From<PyDowncastError<'a>> for PyErr {
fn from(err: PyDowncastError) -> PyErr {
exceptions::PyTypeError::new_err(err.to_string())
}
}
impl<'a> std::error::Error for PyDowncastError<'a> {}
impl<'a> std::fmt::Display for PyDowncastError<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(
f,
"Can't convert {} to {}",
self.from
.repr()
.map(|s| s.to_string_lossy())
.unwrap_or_else(|_| self.from.get_type().name()),
self.to
)
}
}
pub fn panic_after_error(_py: Python) -> ! {
unsafe {
ffi::PyErr_Print();
}
panic!("Python API call failed");
}
/// Returns Ok if the error code is not -1.
#[inline]
pub fn error_on_minusone(py: Python, result: c_int) -> PyResult<()> {
if result != -1 {
Ok(())
} else {
Err(PyErr::fetch(py))
}
}
#[inline]
fn exceptions_must_derive_from_base_exception(py: Python) -> PyErr {
PyErr::from_state(PyErrState::Lazy {
ptype: exceptions::PyTypeError::type_object(py).into(),
pvalue: boxed_args("exceptions must derive from BaseException"),
})
}
#[cfg(test)]
mod tests {
use super::PyErrState;
use crate::exceptions;
use crate::panic::PanicException;
use crate::{PyErr, Python};
#[test]
fn set_typeerror() {
let gil = Python::acquire_gil();
let py = gil.python();
let err: PyErr = exceptions::PyTypeError::new_err(());
err.restore(py);
assert!(PyErr::occurred(py));
drop(PyErr::fetch(py));
}
#[test]
fn fetching_panic_exception_panics() {
// If -Cpanic=abort is specified, we can't catch panic.
if option_env!("RUSTFLAGS")
.map(|s| s.contains("-Cpanic=abort"))
.unwrap_or(false)
{
return;
}
let gil = Python::acquire_gil();
let py = gil.python();
let err: PyErr = PanicException::new_err("new panic");
err.restore(py);
assert!(PyErr::occurred(py));
let started_unwind =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| PyErr::fetch(py))).is_err();
assert!(started_unwind);
}
#[test]
fn test_pyerr_send_sync() {
fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}
is_send::<PyErr>();
is_sync::<PyErr>();
is_send::<PyErrState>();
is_sync::<PyErrState>();
}
}

View File

@ -3,36 +3,26 @@
//! Exception types defined by Python. //! Exception types defined by Python.
use crate::type_object::PySizedLayout; use crate::type_object::PySizedLayout;
use crate::types::{PyAny, PyTuple}; use crate::{ffi, PyResult, Python};
use crate::{ffi, AsPyPointer, PyResult, Python};
use std::ffi::CStr; use std::ffi::CStr;
use std::ops; use std::ops;
use std::os::raw::c_char;
/// The boilerplate to convert between a Rust type and a Python exception. /// The boilerplate to convert between a Rust type and a Python exception.
#[macro_export] #[macro_export]
macro_rules! impl_exception_boilerplate { macro_rules! impl_exception_boilerplate {
($name: ident) => { ($name: ident) => {
impl std::convert::From<$name> for $crate::PyErr { impl std::convert::From<&$name> for $crate::PyErr {
fn from(_err: $name) -> $crate::PyErr { fn from(err: &$name) -> $crate::PyErr {
$crate::PyErr::new::<$name, _>(()) $crate::PyErr::from_instance(err)
}
}
impl<T> std::convert::Into<$crate::PyResult<T>> for $name {
fn into(self) -> $crate::PyResult<T> {
$crate::PyErr::new::<$name, _>(()).into()
} }
} }
impl $name { impl $name {
pub fn py_err<V: $crate::ToPyObject + Send + Sync + 'static>(args: V) -> $crate::PyErr { pub fn new_err<A>(args: A) -> $crate::PyErr
$crate::PyErr::new::<$name, V>(args) where
} A: $crate::PyErrArguments + Send + Sync + 'static,
pub fn into<R, V: $crate::ToPyObject + Send + Sync + 'static>( {
args: V, $crate::PyErr::new::<$name, A>(args)
) -> $crate::PyResult<R> {
$crate::PyErr::new::<$name, V>(args).into()
} }
} }
@ -445,18 +435,17 @@ impl_native_exception!(PyIOError, IOError, PyExc_IOError);
impl_native_exception!(PyWindowsError, WindowsError, PyExc_WindowsError); impl_native_exception!(PyWindowsError, WindowsError, PyExc_WindowsError);
impl PyUnicodeDecodeError { impl PyUnicodeDecodeError {
pub fn new_err<'p>( pub fn new<'p>(
py: Python<'p>, py: Python<'p>,
encoding: &CStr, encoding: &CStr,
input: &[u8], input: &[u8],
range: ops::Range<usize>, range: ops::Range<usize>,
reason: &CStr, reason: &CStr,
) -> PyResult<&'p PyAny> { ) -> PyResult<&'p PyUnicodeDecodeError> {
unsafe { unsafe {
let input: &[c_char] = &*(input as *const [u8] as *const [c_char]);
py.from_owned_ptr_or_err(ffi::PyUnicodeDecodeError_Create( py.from_owned_ptr_or_err(ffi::PyUnicodeDecodeError_Create(
encoding.as_ptr(), encoding.as_ptr(),
input.as_ptr(), input.as_ptr() as *const i8,
input.len() as ffi::Py_ssize_t, input.len() as ffi::Py_ssize_t,
range.start as ffi::Py_ssize_t, range.start as ffi::Py_ssize_t,
range.end as ffi::Py_ssize_t, range.end as ffi::Py_ssize_t,
@ -465,34 +454,22 @@ impl PyUnicodeDecodeError {
} }
} }
#[allow(clippy::range_plus_one)] // False positive, ..= returns the wrong type
pub fn new_utf8<'p>( pub fn new_utf8<'p>(
py: Python<'p>, py: Python<'p>,
input: &[u8], input: &[u8],
err: std::str::Utf8Error, err: std::str::Utf8Error,
) -> PyResult<&'p PyAny> { ) -> PyResult<&'p PyUnicodeDecodeError> {
let pos = err.valid_up_to(); let pos = err.valid_up_to();
PyUnicodeDecodeError::new_err( PyUnicodeDecodeError::new(
py, py,
CStr::from_bytes_with_nul(b"utf-8\0").unwrap(), CStr::from_bytes_with_nul(b"utf-8\0").unwrap(),
input, input,
pos..pos + 1, pos..(pos + 1),
CStr::from_bytes_with_nul(b"invalid utf-8\0").unwrap(), CStr::from_bytes_with_nul(b"invalid utf-8\0").unwrap(),
) )
} }
} }
impl PyStopIteration {
pub fn stop_iteration(_py: Python, args: &PyTuple) {
unsafe {
ffi::PyErr_SetObject(
ffi::PyExc_StopIteration as *mut ffi::PyObject,
args.as_ptr(),
);
}
}
}
/// Exceptions defined in `asyncio` module /// Exceptions defined in `asyncio` module
pub mod asyncio { pub mod asyncio {
import_exception!(asyncio, CancelledError); import_exception!(asyncio, CancelledError);
@ -513,11 +490,10 @@ pub mod socket {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::exceptions::PyException; use super::{PyException, PyUnicodeDecodeError};
use crate::types::{IntoPyDict, PyDict}; use crate::types::{IntoPyDict, PyDict};
use crate::{AsPyPointer, PyErr, Python}; use crate::{PyErr, Python};
use std::error::Error; use std::error::Error;
use std::fmt::Write;
import_exception!(socket, gaierror); import_exception!(socket, gaierror);
import_exception!(email.errors, MessageError); import_exception!(email.errors, MessageError);
@ -527,7 +503,7 @@ mod test {
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
let py = gil.python(); let py = gil.python();
let err: PyErr = gaierror::py_err(()); let err: PyErr = gaierror::new_err(());
let socket = py let socket = py
.import("socket") .import("socket")
.map_err(|e| e.print(py)) .map_err(|e| e.print(py))
@ -537,6 +513,7 @@ mod test {
d.set_item("socket", socket) d.set_item("socket", socket)
.map_err(|e| e.print(py)) .map_err(|e| e.print(py))
.expect("could not setitem"); .expect("could not setitem");
d.set_item("exc", err) d.set_item("exc", err)
.map_err(|e| e.print(py)) .map_err(|e| e.print(py))
.expect("could not setitem"); .expect("could not setitem");
@ -551,7 +528,7 @@ mod test {
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
let py = gil.python(); let py = gil.python();
let err: PyErr = MessageError::py_err(()); let err: PyErr = MessageError::new_err(());
let email = py let email = py
.import("email") .import("email")
.map_err(|e| e.print(py)) .map_err(|e| e.print(py))
@ -597,40 +574,65 @@ mod test {
} }
#[test] #[test]
fn native_exception_display() { fn native_exception_debug() {
let mut out = String::new();
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
let py = gil.python(); let py = gil.python();
let err = py let exc = py
.run("raise Exception('banana')", None, None) .run("raise Exception('banana')", None, None)
.expect_err("raising should have given us an error") .expect_err("raising should have given us an error")
.instance(py); .into_instance(py);
write!(&mut out, "{}", err).expect("successful format"); assert_eq!(format!("{:?}", exc.as_ref(py)), "Exception");
assert_eq!(out, "Exception: banana"); }
#[test]
fn native_exception_display() {
let gil = Python::acquire_gil();
let py = gil.python();
let exc = py
.run("raise Exception('banana')", None, None)
.expect_err("raising should have given us an error")
.into_instance(py);
assert_eq!(exc.to_string(), "Exception: banana");
} }
#[test] #[test]
fn native_exception_chain() { fn native_exception_chain() {
let mut out = String::new();
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
let py = gil.python(); let py = gil.python();
let err = py let exc = py
.run( .run(
"raise Exception('banana') from TypeError('peach')", "raise Exception('banana') from TypeError('peach')",
None, None,
None, None,
) )
.expect_err("raising should have given us an error") .expect_err("raising should have given us an error")
.instance(py); .into_instance(py);
write!(&mut out, "{}", err).expect("successful format"); assert_eq!(exc.to_string(), "Exception: banana");
assert_eq!(out, "Exception: banana"); let source = exc.as_ref(py).source().expect("cause should exist");
out.clear(); assert_eq!(source.to_string(), "TypeError: peach");
let convert_ref: &super::PyBaseException =
unsafe { &*(err.as_ptr() as *const _ as *const _) };
let source = convert_ref.source().expect("cause should exist");
write!(&mut out, "{}", source).expect("successful format");
assert_eq!(out, "TypeError: peach");
let source_source = source.source(); let source_source = source.source();
assert!(source_source.is_none(), "source_source should be None"); assert!(source_source.is_none(), "source_source should be None");
} }
#[test]
fn unicode_decode_error() {
let invalid_utf8 = b"fo\xd8o";
let err = std::str::from_utf8(invalid_utf8).expect_err("should be invalid utf8");
Python::with_gil(|py| {
let decode_err = PyUnicodeDecodeError::new_utf8(py, invalid_utf8, err).unwrap();
assert_eq!(
decode_err.to_string(),
"UnicodeDecodeError: \'utf-8\' codec can\'t decode byte 0xd8 in position 2: invalid utf-8"
);
// Restoring should preserve the same error
let e: PyErr = decode_err.into();
e.restore(py);
assert_eq!(
PyErr::fetch(py).to_string(),
"UnicodeDecodeError: \'utf-8\' codec can\'t decode byte 0xd8 in position 2: invalid utf-8"
);
});
}
} }

34
src/ffi/funcobject.rs Normal file
View File

@ -0,0 +1,34 @@
use std::os::raw::c_int;
use crate::ffi::object::{PyObject, PyTypeObject, Py_TYPE};
#[cfg_attr(windows, link(name = "pythonXY"))]
extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyFunction_Type")]
pub static mut PyFunction_Type: PyTypeObject;
}
#[inline]
pub unsafe fn PyFunction_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyFunction_Type) as c_int
}
extern "C" {
pub fn PyFunction_NewWithQualName(
code: *mut PyObject,
globals: *mut PyObject,
qualname: *mut PyObject,
) -> *mut PyObject;
pub fn PyFunction_New(code: *mut PyObject, globals: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_Code(op: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_GetGlobals(op: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_GetModule(op: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_GetDefaults(op: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_SetDefaults(op: *mut PyObject, defaults: *mut PyObject) -> c_int;
pub fn PyFunction_GetKwDefaults(op: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_SetKwDefaults(op: *mut PyObject, defaults: *mut PyObject) -> c_int;
pub fn PyFunction_GetClosure(op: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_SetClosure(op: *mut PyObject, closure: *mut PyObject) -> c_int;
pub fn PyFunction_GetAnnotations(op: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_SetAnnotations(op: *mut PyObject, annotations: *mut PyObject) -> c_int;
}

View File

@ -19,6 +19,7 @@ pub use self::eval::*;
pub use self::fileobject::*; pub use self::fileobject::*;
pub use self::floatobject::*; pub use self::floatobject::*;
pub use self::frameobject::PyFrameObject; pub use self::frameobject::PyFrameObject;
pub use self::funcobject::*;
pub use self::genobject::*; pub use self::genobject::*;
pub use self::import::*; pub use self::import::*;
pub use self::intrcheck::*; pub use self::intrcheck::*;
@ -157,3 +158,5 @@ pub mod frameobject {
pub(crate) mod datetime; pub(crate) mod datetime;
pub(crate) mod marshal; pub(crate) mod marshal;
pub(crate) mod funcobject;

View File

@ -365,10 +365,8 @@ fn decrement_gil_count() {
}); });
} }
/// Ensure the GIL is held, useful in implementation of APIs like PyErr::new where it's /// Ensure the GIL is held, used in the implementation of Python::with_gil
/// inconvenient to force the user to acquire the GIL. pub(crate) fn ensure_gil() -> EnsureGIL {
#[doc(hidden)]
pub fn ensure_gil() -> EnsureGIL {
if gil_is_acquired() { if gil_is_acquired() {
EnsureGIL(None) EnsureGIL(None)
} else { } else {
@ -377,8 +375,7 @@ pub fn ensure_gil() -> EnsureGIL {
} }
/// Struct used internally which avoids acquiring the GIL where it's not necessary. /// Struct used internally which avoids acquiring the GIL where it's not necessary.
#[doc(hidden)] pub(crate) struct EnsureGIL(Option<GILGuard>);
pub struct EnsureGIL(Option<GILGuard>);
impl EnsureGIL { impl EnsureGIL {
/// Get the GIL token. /// Get the GIL token.

View File

@ -71,7 +71,7 @@
//! #[pymodule] //! #[pymodule]
//! /// A Python module implemented in Rust. //! /// A Python module implemented in Rust.
//! fn string_sum(py: Python, m: &PyModule) -> PyResult<()> { //! fn string_sum(py: Python, m: &PyModule) -> PyResult<()> {
//! m.add_function(wrap_pyfunction!(sum_as_string))?; //! m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
//! //!
//! Ok(()) //! Ok(())
//! } //! }
@ -138,7 +138,7 @@ pub use crate::conversion::{
AsPyPointer, FromPyObject, FromPyPointer, IntoPy, IntoPyPointer, PyTryFrom, PyTryInto, AsPyPointer, FromPyObject, FromPyPointer, IntoPy, IntoPyPointer, PyTryFrom, PyTryInto,
ToBorrowedObject, ToPyObject, ToBorrowedObject, ToPyObject,
}; };
pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyErrValue, PyResult}; pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyResult};
pub use crate::gil::{GILGuard, GILPool}; pub use crate::gil::{GILGuard, GILPool};
pub use crate::instance::{Py, PyNativeType, PyObject}; pub use crate::instance::{Py, PyNativeType, PyObject};
pub use crate::pycell::{PyCell, PyRef, PyRefMut}; pub use crate::pycell::{PyCell, PyRef, PyRefMut};
@ -196,12 +196,6 @@ mod python;
pub mod type_object; pub mod type_object;
pub mod types; pub mod types;
/// Internal utilities exposed for rust-numpy
#[doc(hidden)]
pub mod internal_utils {
pub use crate::gil::{ensure_gil, EnsureGIL};
}
/// The proc macros, which are also part of the prelude. /// The proc macros, which are also part of the prelude.
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
pub mod proc_macro { pub mod proc_macro {
@ -218,6 +212,20 @@ macro_rules! wrap_pyfunction {
($function_name: ident) => {{ ($function_name: ident) => {{
&pyo3::paste::expr! { [<__pyo3_get_function_ $function_name>] } &pyo3::paste::expr! { [<__pyo3_get_function_ $function_name>] }
}}; }};
($function_name: ident, $arg: expr) => {
pyo3::wrap_pyfunction!($function_name)(pyo3::derive_utils::PyFunctionArguments::from($arg))
};
}
/// Returns the function that is called in the C-FFI.
///
/// Use this together with `#[pyfunction]` and [types::PyCFunction].
#[macro_export]
macro_rules! raw_pycfunction {
($function_name: ident) => {{
pyo3::paste::expr! { [<__pyo3_raw_ $function_name>] }
}};
} }
/// Returns a function that takes a [Python] instance and returns a Python module. /// Returns a function that takes a [Python] instance and returns a Python module.

View File

@ -730,7 +730,7 @@ impl fmt::Display for PyBorrowError {
impl From<PyBorrowError> for PyErr { impl From<PyBorrowError> for PyErr {
fn from(other: PyBorrowError) -> Self { fn from(other: PyBorrowError) -> Self {
PyRuntimeError::py_err(other.to_string()) PyRuntimeError::new_err(other.to_string())
} }
} }
@ -755,6 +755,6 @@ impl fmt::Display for PyBorrowMutError {
impl From<PyBorrowMutError> for PyErr { impl From<PyBorrowMutError> for PyErr {
fn from(other: PyBorrowMutError) -> Self { fn from(other: PyBorrowMutError) -> Self {
PyRuntimeError::py_err(other.to_string()) PyRuntimeError::new_err(other.to_string())
} }
} }

View File

@ -126,7 +126,7 @@ impl<'p> Python<'p> {
/// .collect(); /// .collect();
/// let mut sum = 0; /// let mut sum = 0;
/// for t in threads { /// for t in threads {
/// sum += t.join().map_err(|_| PyErr::new::<PyRuntimeError, _>(()))?; /// sum += t.join().map_err(|_| PyRuntimeError::new_err(()))?;
/// } /// }
/// Ok(sum) /// Ok(sum)
/// }) /// })
@ -134,7 +134,7 @@ impl<'p> Python<'p> {
/// let gil = Python::acquire_gil(); /// let gil = Python::acquire_gil();
/// let py = gil.python(); /// let py = gil.python();
/// let m = PyModule::new(py, "pcount").unwrap(); /// let m = PyModule::new(py, "pcount").unwrap();
/// m.add_function(wrap_pyfunction!(parallel_count)).unwrap(); /// m.add_function(wrap_pyfunction!(parallel_count, m).unwrap()).unwrap();
/// let locals = [("pcount", m)].into_py_dict(py); /// let locals = [("pcount", m)].into_py_dict(py);
/// py.run(r#" /// py.run(r#"
/// s = ["Flow", "my", "tears", "the", "Policeman", "Said"] /// s = ["Flow", "my", "tears", "the", "Policeman", "Said"]

View File

@ -163,7 +163,7 @@ impl PyAny {
} else if do_compare(other, ffi::Py_GT)? { } else if do_compare(other, ffi::Py_GT)? {
Ok(Ordering::Greater) Ok(Ordering::Greater)
} else { } else {
Err(PyTypeError::py_err( Err(PyTypeError::new_err(
"PyAny::compare(): All comparisons returned false", "PyAny::compare(): All comparisons returned false",
)) ))
} }

View File

@ -295,7 +295,7 @@ mod test {
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
let py = gil.python(); let py = gil.python();
let py_bytearray_result = PyByteArray::new_with(py, 10, |_b: &mut [u8]| { let py_bytearray_result = PyByteArray::new_with(py, 10, |_b: &mut [u8]| {
Err(PyValueError::py_err("Hello Crustaceans!")) Err(PyValueError::new_err("Hello Crustaceans!"))
}); });
assert!(py_bytearray_result.is_err()); assert!(py_bytearray_result.is_err());
assert!(py_bytearray_result assert!(py_bytearray_result

View File

@ -159,7 +159,7 @@ mod test {
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
let py = gil.python(); let py = gil.python();
let py_bytes_result = PyBytes::new_with(py, 10, |_b: &mut [u8]| { let py_bytes_result = PyBytes::new_with(py, 10, |_b: &mut [u8]| {
Err(PyValueError::py_err("Hello Crustaceans!")) Err(PyValueError::new_err("Hello Crustaceans!"))
}); });
assert!(py_bytes_result.is_err()); assert!(py_bytes_result.is_err());
assert!(py_bytes_result assert!(py_bytes_result

90
src/types/function.rs Normal file
View File

@ -0,0 +1,90 @@
use std::ffi::{CStr, CString};
use crate::derive_utils::PyFunctionArguments;
use crate::exceptions::PyValueError;
use crate::prelude::*;
use crate::{class, ffi, AsPyPointer, PyMethodType};
/// Represents a builtin Python function object.
#[repr(transparent)]
pub struct PyCFunction(PyAny);
pyobject_native_var_type!(PyCFunction, ffi::PyCFunction_Type, ffi::PyCFunction_Check);
impl PyCFunction {
/// Create a new built-in function with keywords.
pub fn new_with_keywords<'a>(
fun: ffi::PyCFunctionWithKeywords,
name: &str,
doc: &'static str,
py_or_module: PyFunctionArguments<'a>,
) -> PyResult<&'a PyCFunction> {
let fun = PyMethodType::PyCFunctionWithKeywords(fun);
Self::new_(fun, name, doc, py_or_module)
}
/// Create a new built-in function without keywords.
pub fn new<'a>(
fun: ffi::PyCFunction,
name: &str,
doc: &'static str,
py_or_module: PyFunctionArguments<'a>,
) -> PyResult<&'a PyCFunction> {
let fun = PyMethodType::PyCFunction(fun);
Self::new_(fun, name, doc, py_or_module)
}
fn new_<'a>(
fun: class::PyMethodType,
name: &str,
doc: &'static str,
py_or_module: PyFunctionArguments<'a>,
) -> PyResult<&'a PyCFunction> {
let (py, module) = py_or_module.into_py_and_maybe_module();
let doc: &'static CStr = CStr::from_bytes_with_nul(doc.as_bytes())
.map_err(|_| PyValueError::new_err("docstring must end with NULL byte."))?;
let name = CString::new(name.as_bytes()).map_err(|_| {
PyValueError::new_err("Function name cannot contain contain NULL byte.")
})?;
let def = match fun {
PyMethodType::PyCFunction(fun) => ffi::PyMethodDef {
ml_name: name.into_raw() as _,
ml_meth: Some(fun),
ml_flags: ffi::METH_VARARGS,
ml_doc: doc.as_ptr() as _,
},
PyMethodType::PyCFunctionWithKeywords(fun) => ffi::PyMethodDef {
ml_name: name.into_raw() as _,
ml_meth: Some(unsafe { std::mem::transmute(fun) }),
ml_flags: ffi::METH_VARARGS | ffi::METH_KEYWORDS,
ml_doc: doc.as_ptr() as _,
},
_ => {
return Err(PyValueError::new_err(
"Only PyCFunction and PyCFunctionWithKeywords are valid.",
))
}
};
let (mod_ptr, module_name) = if let Some(m) = module {
let mod_ptr = m.as_ptr();
let name = m.name()?.into_py(py);
(mod_ptr, name.as_ptr())
} else {
(std::ptr::null_mut(), std::ptr::null_mut())
};
unsafe {
py.from_owned_ptr_or_err::<PyCFunction>(ffi::PyCFunction_NewEx(
Box::into_raw(Box::new(def)),
mod_ptr,
module_name,
))
}
}
}
/// Represents a Python function object.
#[repr(transparent)]
pub struct PyFunction(PyAny);
pyobject_native_var_type!(PyFunction, ffi::PyFunction_Type, ffi::PyFunction_Check);

View File

@ -13,6 +13,7 @@ pub use self::datetime::{
}; };
pub use self::dict::{IntoPyDict, PyDict}; pub use self::dict::{IntoPyDict, PyDict};
pub use self::floatob::PyFloat; pub use self::floatob::PyFloat;
pub use self::function::{PyCFunction, PyFunction};
pub use self::iterator::PyIterator; pub use self::iterator::PyIterator;
pub use self::list::PyList; pub use self::list::PyList;
pub use self::module::PyModule; pub use self::module::PyModule;
@ -226,6 +227,7 @@ mod complex;
mod datetime; mod datetime;
mod dict; mod dict;
mod floatob; mod floatob;
mod function;
mod iterator; mod iterator;
mod list; mod list;
mod module; mod module;

View File

@ -9,8 +9,8 @@ use crate::ffi;
use crate::instance::PyNativeType; use crate::instance::PyNativeType;
use crate::pyclass::PyClass; use crate::pyclass::PyClass;
use crate::type_object::PyTypeObject; use crate::type_object::PyTypeObject;
use crate::types::PyTuple;
use crate::types::{PyAny, PyDict, PyList}; use crate::types::{PyAny, PyDict, PyList};
use crate::types::{PyCFunction, PyTuple};
use crate::{AsPyPointer, IntoPy, Py, PyObject, Python}; use crate::{AsPyPointer, IntoPy, Py, PyObject, Python};
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::os::raw::c_char; use std::os::raw::c_char;
@ -258,7 +258,7 @@ impl PyModule {
/// } /// }
/// #[pymodule] /// #[pymodule]
/// fn double_mod(_py: Python, module: &PyModule) -> PyResult<()> { /// fn double_mod(_py: Python, module: &PyModule) -> PyResult<()> {
/// module.add_function(pyo3::wrap_pyfunction!(double)) /// module.add_function(pyo3::wrap_pyfunction!(double, module)?)
/// } /// }
/// ``` /// ```
/// ///
@ -272,17 +272,11 @@ impl PyModule {
/// } /// }
/// #[pymodule] /// #[pymodule]
/// fn double_mod(_py: Python, module: &PyModule) -> PyResult<()> { /// fn double_mod(_py: Python, module: &PyModule) -> PyResult<()> {
/// module.add("also_double", pyo3::wrap_pyfunction!(double)(module)?) /// module.add("also_double", pyo3::wrap_pyfunction!(double, module)?)
/// } /// }
/// ``` /// ```
pub fn add_function<'a>( pub fn add_function<'a>(&'a self, fun: &'a PyCFunction) -> PyResult<()> {
&'a self, let name = fun.getattr("__name__")?.extract()?;
wrapper: &impl Fn(&'a Self) -> PyResult<PyObject>, self.add(name, fun)
) -> PyResult<()> {
let py = self.py();
let function = wrapper(self)?;
let name = function.getattr(py, "__name__")?;
let name = name.extract(py)?;
self.add(name, function)
} }
} }

View File

@ -40,7 +40,7 @@ macro_rules! int_fits_larger_int {
fn extract(obj: &'source PyAny) -> PyResult<Self> { fn extract(obj: &'source PyAny) -> PyResult<Self> {
let val: $larger_type = obj.extract()?; let val: $larger_type = obj.extract()?;
<$rust_type>::try_from(val) <$rust_type>::try_from(val)
.map_err(|e| exceptions::PyOverflowError::py_err(e.to_string())) .map_err(|e| exceptions::PyOverflowError::new_err(e.to_string()))
} }
} }
}; };
@ -86,7 +86,7 @@ macro_rules! int_fits_c_long {
} }
}?; }?;
<$rust_type>::try_from(val) <$rust_type>::try_from(val)
.map_err(|e| exceptions::PyOverflowError::py_err(e.to_string())) .map_err(|e| exceptions::PyOverflowError::new_err(e.to_string()))
} }
} }
}; };

View File

@ -361,7 +361,7 @@ where
{ {
let seq = <PySequence as PyTryFrom>::try_from(obj)?; let seq = <PySequence as PyTryFrom>::try_from(obj)?;
if seq.len()? as usize != slice.len() { if seq.len()? as usize != slice.len() {
return Err(exceptions::PyBufferError::py_err( return Err(exceptions::PyBufferError::new_err(
"Slice length does not match buffer length.", "Slice length does not match buffer length.",
)); ));
} }

View File

@ -139,7 +139,7 @@ fn wrong_tuple_length(t: &PyTuple, expected_length: usize) -> PyErr {
expected_length, expected_length,
t.len() t.len()
); );
exceptions::PyValueError::py_err(msg) exceptions::PyValueError::new_err(msg)
} }
macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+} => { macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+} => {

View File

@ -21,11 +21,11 @@ struct TestBufferClass {
impl PyBufferProtocol for TestBufferClass { impl PyBufferProtocol for TestBufferClass {
fn bf_getbuffer(slf: PyRefMut<Self>, view: *mut ffi::Py_buffer, flags: c_int) -> PyResult<()> { fn bf_getbuffer(slf: PyRefMut<Self>, view: *mut ffi::Py_buffer, flags: c_int) -> PyResult<()> {
if view.is_null() { if view.is_null() {
return Err(PyBufferError::py_err("View is null")); return Err(PyBufferError::new_err("View is null"));
} }
if (flags & ffi::PyBUF_WRITABLE) == ffi::PyBUF_WRITABLE { if (flags & ffi::PyBUF_WRITABLE) == ffi::PyBUF_WRITABLE {
return Err(PyBufferError::py_err("Object is not writable")); return Err(PyBufferError::new_err("Object is not writable"));
} }
unsafe { unsafe {

View File

@ -170,7 +170,7 @@ impl PySequenceProtocol for Sequence {
if let Some(s) = self.fields.get(idx) { if let Some(s) = self.fields.get(idx) {
Ok(s.clone()) Ok(s.clone())
} else { } else {
Err(PyErr::new::<PyIndexError, _>(())) Err(PyIndexError::new_err(()))
} }
} }
@ -180,7 +180,7 @@ impl PySequenceProtocol for Sequence {
*elem = value; *elem = value;
Ok(()) Ok(())
} else { } else {
Err(PyErr::new::<PyIndexError, _>(())) Err(PyIndexError::new_err(()))
} }
} }
} }
@ -436,7 +436,7 @@ impl<'p> PyMappingProtocol<'p> for Test {
return Ok("int"); return Ok("int");
} }
} }
Err(PyErr::new::<PyValueError, _>("error")) Err(PyValueError::new_err("error"))
} }
} }

View File

@ -46,7 +46,7 @@ impl fmt::Display for CustomError {
impl std::convert::From<CustomError> for PyErr { impl std::convert::From<CustomError> for PyErr {
fn from(err: CustomError) -> PyErr { fn from(err: CustomError) -> PyErr {
exceptions::PyOSError::py_err(err.to_string()) exceptions::PyOSError::new_err(err.to_string())
} }
} }

View File

@ -1,7 +1,7 @@
use pyo3::exceptions::PyValueError; use pyo3::exceptions::PyValueError;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::{PyDict, PyString, PyTuple}; use pyo3::types::{PyDict, PyString, PyTuple};
use pyo3::{PyErrValue, PyMappingProtocol}; use pyo3::PyMappingProtocol;
#[macro_use] #[macro_use]
mod common; mod common;
@ -30,7 +30,7 @@ impl PyMappingProtocol for PyA {
if key == "t" { if key == "t" {
Ok("bar".into()) Ok("bar".into())
} else { } else {
Err(PyValueError::py_err("Failed")) Err(PyValueError::new_err("Failed"))
} }
} }
} }
@ -277,7 +277,7 @@ fn test_enum() {
} }
} }
#[derive(FromPyObject)] #[derive(Debug, FromPyObject)]
pub enum Bar { pub enum Bar {
#[pyo3(annotation = "str")] #[pyo3(annotation = "str")]
A(String), A(String),
@ -294,15 +294,8 @@ fn test_err_rename() {
let dict = PyDict::new(py); let dict = PyDict::new(py);
let f = Bar::extract(dict.as_ref()); let f = Bar::extract(dict.as_ref());
assert!(f.is_err()); assert!(f.is_err());
match f { assert_eq!(
Ok(_) => {} f.unwrap_err().to_string(),
Err(e) => match e.pvalue { "TypeError: Can't convert {} (dict) to Union[str, uint, int]"
PyErrValue::ToObject(to) => { );
let o = to.to_object(py);
let s = String::extract(o.as_ref(py)).expect("Err val is not a string");
assert_eq!(s, "Can't convert {} (dict) to Union[str, uint, int]")
}
_ => panic!("Expected PyErrValue::ToObject"),
},
}
} }

View File

@ -40,7 +40,7 @@ impl PyMappingProtocol for Mapping {
self.index self.index
.get(&query) .get(&query)
.copied() .copied()
.ok_or_else(|| PyKeyError::py_err("unknown key")) .ok_or_else(|| PyKeyError::new_err("unknown key"))
} }
fn __setitem__(&mut self, key: String, value: usize) { fn __setitem__(&mut self, key: String, value: usize) {
@ -49,7 +49,7 @@ impl PyMappingProtocol for Mapping {
fn __delitem__(&mut self, key: String) -> PyResult<()> { fn __delitem__(&mut self, key: String) -> PyResult<()> {
if self.index.remove(&key).is_none() { if self.index.remove(&key).is_none() {
PyKeyError::py_err("unknown key").into() Err(PyKeyError::new_err("unknown key"))
} else { } else {
Ok(()) Ok(())
} }

View File

@ -65,8 +65,8 @@ fn module_with_functions(_py: Python, m: &PyModule) -> PyResult<()> {
m.add("foo", "bar").unwrap(); m.add("foo", "bar").unwrap();
m.add_function(wrap_pyfunction!(double)).unwrap(); m.add_function(wrap_pyfunction!(double, m)?).unwrap();
m.add("also_double", wrap_pyfunction!(double)(m)?).unwrap(); m.add("also_double", wrap_pyfunction!(double, m)?).unwrap();
Ok(()) Ok(())
} }
@ -163,7 +163,7 @@ fn r#move() -> usize {
fn raw_ident_module(_py: Python, module: &PyModule) -> PyResult<()> { fn raw_ident_module(_py: Python, module: &PyModule) -> PyResult<()> {
use pyo3::wrap_pyfunction; use pyo3::wrap_pyfunction;
module.add_function(wrap_pyfunction!(r#move)) module.add_function(wrap_pyfunction!(r#move, module)?)
} }
#[test] #[test]
@ -188,7 +188,7 @@ fn custom_named_fn() -> usize {
fn foobar_module(_py: Python, m: &PyModule) -> PyResult<()> { fn foobar_module(_py: Python, m: &PyModule) -> PyResult<()> {
use pyo3::wrap_pyfunction; use pyo3::wrap_pyfunction;
m.add_function(wrap_pyfunction!(custom_named_fn))?; m.add_function(wrap_pyfunction!(custom_named_fn, m)?)?;
m.dict().set_item("yay", "me")?; m.dict().set_item("yay", "me")?;
Ok(()) Ok(())
} }
@ -221,7 +221,7 @@ fn subfunction() -> String {
fn submodule(module: &PyModule) -> PyResult<()> { fn submodule(module: &PyModule) -> PyResult<()> {
use pyo3::wrap_pyfunction; use pyo3::wrap_pyfunction;
module.add_function(wrap_pyfunction!(subfunction))?; module.add_function(wrap_pyfunction!(subfunction, module)?)?;
Ok(()) Ok(())
} }
@ -229,7 +229,7 @@ fn submodule(module: &PyModule) -> PyResult<()> {
fn submodule_with_init_fn(_py: Python, module: &PyModule) -> PyResult<()> { fn submodule_with_init_fn(_py: Python, module: &PyModule) -> PyResult<()> {
use pyo3::wrap_pyfunction; use pyo3::wrap_pyfunction;
module.add_function(wrap_pyfunction!(subfunction))?; module.add_function(wrap_pyfunction!(subfunction, module)?)?;
Ok(()) Ok(())
} }
@ -242,7 +242,7 @@ fn superfunction() -> String {
fn supermodule(py: Python, module: &PyModule) -> PyResult<()> { fn supermodule(py: Python, module: &PyModule) -> PyResult<()> {
use pyo3::wrap_pyfunction; use pyo3::wrap_pyfunction;
module.add_function(wrap_pyfunction!(superfunction))?; module.add_function(wrap_pyfunction!(superfunction, module)?)?;
let module_to_add = PyModule::new(py, "submodule")?; let module_to_add = PyModule::new(py, "submodule")?;
submodule(module_to_add)?; submodule(module_to_add)?;
module.add_submodule(module_to_add)?; module.add_submodule(module_to_add)?;
@ -291,7 +291,7 @@ fn vararg_module(_py: Python, m: &PyModule) -> PyResult<()> {
ext_vararg_fn(py, a, vararg) ext_vararg_fn(py, a, vararg)
} }
m.add_function(pyo3::wrap_pyfunction!(ext_vararg_fn)) m.add_function(pyo3::wrap_pyfunction!(ext_vararg_fn, m)?)
.unwrap(); .unwrap();
Ok(()) Ok(())
} }
@ -368,15 +368,17 @@ fn pyfunction_with_module_and_args_kwargs<'a>(
#[pymodule] #[pymodule]
fn module_with_functions_with_module(_py: Python, m: &PyModule) -> PyResult<()> { fn module_with_functions_with_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(pyo3::wrap_pyfunction!(pyfunction_with_module))?; m.add_function(pyo3::wrap_pyfunction!(pyfunction_with_module, m)?)?;
m.add_function(pyo3::wrap_pyfunction!(pyfunction_with_module_and_py))?; m.add_function(pyo3::wrap_pyfunction!(pyfunction_with_module_and_py, m)?)?;
m.add_function(pyo3::wrap_pyfunction!(pyfunction_with_module_and_arg))?; m.add_function(pyo3::wrap_pyfunction!(pyfunction_with_module_and_arg, m)?)?;
m.add_function(pyo3::wrap_pyfunction!( m.add_function(pyo3::wrap_pyfunction!(
pyfunction_with_module_and_default_arg pyfunction_with_module_and_default_arg,
))?; m
)?)?;
m.add_function(pyo3::wrap_pyfunction!( m.add_function(pyo3::wrap_pyfunction!(
pyfunction_with_module_and_args_kwargs pyfunction_with_module_and_args_kwargs,
)) m
)?)
} }
#[test] #[test]

View File

@ -1,6 +1,7 @@
use pyo3::buffer::PyBuffer; use pyo3::buffer::PyBuffer;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::wrap_pyfunction; use pyo3::types::{PyCFunction, PyFunction};
use pyo3::{raw_pycfunction, wrap_pyfunction};
mod common; mod common;
@ -62,3 +63,59 @@ assert a, array.array("i", [2, 4, 6, 8])
"# "#
); );
} }
#[pyfunction]
fn function_with_pyfunction_arg(fun: &PyFunction) -> PyResult<&PyAny> {
fun.call((), None)
}
#[pyfunction]
fn function_with_pycfunction_arg(fun: &PyCFunction) -> PyResult<&PyAny> {
fun.call((), None)
}
#[test]
fn test_functions_with_function_args() {
let gil = Python::acquire_gil();
let py = gil.python();
let py_func_arg = wrap_pyfunction!(function_with_pyfunction_arg)(py).unwrap();
let py_cfunc_arg = wrap_pyfunction!(function_with_pycfunction_arg)(py).unwrap();
let bool_to_string = wrap_pyfunction!(optional_bool)(py).unwrap();
pyo3::py_run!(
py,
py_func_arg
py_cfunc_arg
bool_to_string,
r#"
def foo(): return "bar"
assert py_func_arg(foo) == "bar"
assert py_cfunc_arg(bool_to_string) == "Some(true)"
"#
)
}
#[test]
fn test_raw_function() {
let gil = Python::acquire_gil();
let py = gil.python();
let raw_func = raw_pycfunction!(optional_bool);
let fun = PyCFunction::new_with_keywords(raw_func, "fun", "\0", py.into()).unwrap();
let res = fun.call((), None).unwrap().extract::<&str>().unwrap();
assert_eq!(res, "Some(true)");
let res = fun.call((false,), None).unwrap().extract::<&str>().unwrap();
assert_eq!(res, "Some(false)");
let no_module = fun.getattr("__module__").unwrap().is_none();
assert!(no_module);
let module = PyModule::new(py, "cool_module").unwrap();
module.add_function(fun).unwrap();
let res = module
.getattr("fun")
.unwrap()
.call((), None)
.unwrap()
.extract::<&str>()
.unwrap();
assert_eq!(res, "Some(true)");
}

View File

@ -41,7 +41,7 @@ impl PySequenceProtocol for ByteSequence {
self.elements self.elements
.get(idx as usize) .get(idx as usize)
.copied() .copied()
.ok_or_else(|| PyIndexError::py_err("list index out of range")) .ok_or_else(|| PyIndexError::new_err("list index out of range"))
} }
fn __setitem__(&mut self, idx: isize, value: u8) { fn __setitem__(&mut self, idx: isize, value: u8) {
@ -53,7 +53,7 @@ impl PySequenceProtocol for ByteSequence {
self.elements.remove(idx as usize); self.elements.remove(idx as usize);
Ok(()) Ok(())
} else { } else {
Err(PyIndexError::py_err("list index out of range")) Err(PyIndexError::new_err("list index out of range"))
} }
} }
@ -78,7 +78,7 @@ impl PySequenceProtocol for ByteSequence {
} }
Ok(Self { elements }) Ok(Self { elements })
} else { } else {
Err(PyValueError::py_err("invalid repeat count")) Err(PyValueError::new_err("invalid repeat count"))
} }
} }
} }
@ -242,7 +242,7 @@ impl PySequenceProtocol for OptionList {
fn __getitem__(&self, idx: isize) -> PyResult<Option<i64>> { fn __getitem__(&self, idx: isize) -> PyResult<Option<i64>> {
match self.items.get(idx as usize) { match self.items.get(idx as usize) {
Some(x) => Ok(*x), Some(x) => Ok(*x),
None => Err(PyIndexError::py_err("Index out of bounds")), None => Err(PyIndexError::new_err("Index out of bounds")),
} }
} }
} }

View File

@ -191,7 +191,7 @@ impl fmt::Display for MyError {
/// Important for the automatic conversion to `PyErr`. /// Important for the automatic conversion to `PyErr`.
impl From<MyError> for PyErr { impl From<MyError> for PyErr {
fn from(err: MyError) -> pyo3::PyErr { fn from(err: MyError) -> pyo3::PyErr {
pyo3::exceptions::PyOSError::py_err(err.to_string()) pyo3::exceptions::PyOSError::new_err(err.to_string())
} }
} }