diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..c1ce3c3b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "cargo" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2cb2acd0..c7698ce6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: - master pull_request: +env: + CARGO_TERM_COLOR: always + jobs: fmt: runs-on: ubuntu-latest @@ -39,6 +42,7 @@ jobs: name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} ${{ matrix.msrv }} runs-on: ${{ matrix.platform.os }} strategy: + fail-fast: false # If one platform fails, allow the rest to keep testing. matrix: rust: [stable] python-version: [3.5, 3.6, 3.7, 3.8, 3.9-dev, pypy3] diff --git a/.github/workflows/guide.yml b/.github/workflows/guide.yml index 336c867f..27458e63 100644 --- a/.github/workflows/guide.yml +++ b/.github/workflows/guide.yml @@ -6,6 +6,9 @@ on: - master release: +env: + CARGO_TERM_COLOR: always + jobs: deploy: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index e3886d0e..f5d77293 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Add optional implementations of `ToPyObject`, `IntoPy`, and `FromPyObject` for [hashbrown](https://crates.io/crates/hashbrown)'s `HashMap` and `HashSet` types. The `hashbrown` feature must be enabled for these implementations to be built. [#1114](https://github.com/PyO3/pyo3/pull/1114/) - Allow other `Result` types when using `#[pyfunction]`. [#1106](https://github.com/PyO3/pyo3/issues/1106). - Add `#[derive(FromPyObject)]` macro for enums and structs. [#1065](https://github.com/PyO3/pyo3/pull/1065) +- 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) +- Add `add_function()` and `add_submodule()` functions to `PyModule` [#1143](https://github.com/PyO3/pyo3/pull/1143) ### Changed - Exception types have been renamed from e.g. `RuntimeError` to `PyRuntimeError`, and are now only accessible by `&T` or `Py` similar to other Python-native types. The old names continue to exist but are deprecated. [#1024](https://github.com/PyO3/pyo3/pull/1024) @@ -30,6 +33,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - 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) - 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` 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) ### Removed - Remove `PyString::as_bytes`. [#1023](https://github.com/PyO3/pyo3/pull/1023) @@ -50,6 +54,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Link against libpython on android with `extension-module` set. [#1095](https://github.com/PyO3/pyo3/pull/1095) - Fix support for both `__add__` and `__radd__` in the `+` operator when both are defined in `PyNumberProtocol` (and similar for all other reversible operators). [#1107](https://github.com/PyO3/pyo3/pull/1107) +- Associate Python functions with their module by passing the Module and Module name [#1143](https://github.com/PyO3/pyo3/pull/1143) ## [0.11.1] - 2020-06-30 ### Added @@ -481,8 +486,8 @@ Yanked - Initial release [Unreleased]: https://github.com/pyo3/pyo3/compare/v0.11.1...HEAD -[0.11.0] https://github.com/pyo3/pyo3/compare/v0.11.0...v0.11.1 -[0.11.0] https://github.com/pyo3/pyo3/compare/v0.10.1...v0.11.0 +[0.11.1]: https://github.com/pyo3/pyo3/compare/v0.11.0...v0.11.1 +[0.11.0]: https://github.com/pyo3/pyo3/compare/v0.10.1...v0.11.0 [0.10.1]: https://github.com/pyo3/pyo3/compare/v0.10.0...v0.10.1 [0.10.0]: https://github.com/pyo3/pyo3/compare/v0.9.2...v0.10.0 [0.9.2]: https://github.com/pyo3/pyo3/compare/v0.9.1...v0.9.2 diff --git a/Cargo.toml b/Cargo.toml index c4a437d9..93e8491a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ num-complex = { version = "0.3", optional = true } paste = { version = "0.1.6", optional = true } pyo3cls = { path = "pyo3cls", version = "=0.11.1", optional = true } unindent = { version = "0.1.4", optional = true } -hashbrown = { version = "0.8", optional = true } +hashbrown = { version = "0.9", optional = true } [dev-dependencies] assert_approx_eq = "1.1.0" diff --git a/README.md b/README.md index bf06df9e..ff041bf6 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ fn sum_as_string(a: usize, b: usize) -> PyResult { /// A Python module implemented in Rust. #[pymodule] fn string_sum(py: Python, m: &PyModule) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(sum_as_string))?; + m.add_function(wrap_pyfunction!(sum_as_string))?; Ok(()) } diff --git a/benches/bench_set.rs b/benches/bench_set.rs new file mode 100644 index 00000000..1303e28c --- /dev/null +++ b/benches/bench_set.rs @@ -0,0 +1,21 @@ +#![feature(test)] + +extern crate test; +use pyo3::prelude::*; +use pyo3::types::PySet; +use test::Bencher; + +#[bench] +fn iter_set(b: &mut Bencher) { + let gil = Python::acquire_gil(); + let py = gil.python(); + const LEN: usize = 100_000; + let set = PySet::new(py, &(0..LEN).collect::>()).unwrap(); + let mut sum = 0; + b.iter(|| { + for x in set.iter() { + let i: u64 = x.extract().unwrap(); + sum += i; + } + }); +} diff --git a/codecov.yml b/codecov.yml index 50979600..4e2196bd 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,2 +1,9 @@ comment: off +coverage: + status: + project: + default: + target: auto + # Allow a tiny drop of overall project coverage in PR to reduce spurious failures. + threshold: 0.25% diff --git a/examples/rustapi_module/src/datetime.rs b/examples/rustapi_module/src/datetime.rs index 3181ae79..3ccb7c69 100644 --- a/examples/rustapi_module/src/datetime.rs +++ b/examples/rustapi_module/src/datetime.rs @@ -215,29 +215,29 @@ impl TzClass { #[pymodule] fn datetime(_py: Python<'_>, m: &PyModule) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(make_date))?; - m.add_wrapped(wrap_pyfunction!(get_date_tuple))?; - m.add_wrapped(wrap_pyfunction!(date_from_timestamp))?; - m.add_wrapped(wrap_pyfunction!(make_time))?; - m.add_wrapped(wrap_pyfunction!(get_time_tuple))?; - m.add_wrapped(wrap_pyfunction!(make_delta))?; - m.add_wrapped(wrap_pyfunction!(get_delta_tuple))?; - m.add_wrapped(wrap_pyfunction!(make_datetime))?; - m.add_wrapped(wrap_pyfunction!(get_datetime_tuple))?; - m.add_wrapped(wrap_pyfunction!(datetime_from_timestamp))?; + m.add_function(wrap_pyfunction!(make_date))?; + m.add_function(wrap_pyfunction!(get_date_tuple))?; + m.add_function(wrap_pyfunction!(date_from_timestamp))?; + m.add_function(wrap_pyfunction!(make_time))?; + m.add_function(wrap_pyfunction!(get_time_tuple))?; + m.add_function(wrap_pyfunction!(make_delta))?; + m.add_function(wrap_pyfunction!(get_delta_tuple))?; + m.add_function(wrap_pyfunction!(make_datetime))?; + m.add_function(wrap_pyfunction!(get_datetime_tuple))?; + m.add_function(wrap_pyfunction!(datetime_from_timestamp))?; // Python 3.6+ functions #[cfg(Py_3_6)] { - m.add_wrapped(wrap_pyfunction!(time_with_fold))?; + m.add_function(wrap_pyfunction!(time_with_fold))?; #[cfg(not(PyPy))] { - m.add_wrapped(wrap_pyfunction!(get_time_tuple_fold))?; - m.add_wrapped(wrap_pyfunction!(get_datetime_tuple_fold))?; + m.add_function(wrap_pyfunction!(get_time_tuple_fold))?; + m.add_function(wrap_pyfunction!(get_datetime_tuple_fold))?; } } - m.add_wrapped(wrap_pyfunction!(issue_219))?; + m.add_function(wrap_pyfunction!(issue_219))?; m.add_class::()?; Ok(()) diff --git a/examples/rustapi_module/src/othermod.rs b/examples/rustapi_module/src/othermod.rs index 20745b29..b9955806 100644 --- a/examples/rustapi_module/src/othermod.rs +++ b/examples/rustapi_module/src/othermod.rs @@ -31,7 +31,7 @@ fn double(x: i32) -> i32 { #[pymodule] fn othermod(_py: Python<'_>, m: &PyModule) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(double))?; + m.add_function(wrap_pyfunction!(double))?; m.add_class::()?; diff --git a/examples/word-count/src/lib.rs b/examples/word-count/src/lib.rs index 06d696e8..50a00780 100644 --- a/examples/word-count/src/lib.rs +++ b/examples/word-count/src/lib.rs @@ -56,8 +56,8 @@ fn count_line(line: &str, needle: &str) -> usize { #[pymodule] fn word_count(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(search))?; - m.add_wrapped(wrap_pyfunction!(search_sequential))?; - m.add_wrapped(wrap_pyfunction!(search_sequential_allow_threads))?; + m.add_function(wrap_pyfunction!(search_sequential))?; + m.add_function(wrap_pyfunction!(search_sequential_allow_threads))?; Ok(()) } diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 2b7570fa..35857738 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -7,7 +7,10 @@ - [Python Modules](module.md) - [Python Functions](function.md) - [Python Classes](class.md) + - [Class customizations](class/protocols.md) - [Type Conversions](conversions.md) + - [Mapping of Rust types to Python types](conversions/tables.md)] + - [Conversion traits](conversions/traits.md)] - [Python Exceptions](exception.md) - [Calling Python from Rust](python_from_rust.md) - [GIL, mutability and object types](types.md) @@ -20,7 +23,8 @@ --- -[Appendix A: PyO3 and rust-cpython](rust_cpython.md) -[Appendix B: Migration Guide](migration.md) +[Appendix A: Migration Guide](migration.md) +[Appendix B: PyO3 and rust-cpython](rust_cpython.md) [Appendix C: Trait bounds](trait_bounds.md) [Appendix D: Logging](logging.md) +[CHANGELOG](changelog.md) diff --git a/guide/src/changelog.md b/guide/src/changelog.md new file mode 100644 index 00000000..c16cb03d --- /dev/null +++ b/guide/src/changelog.md @@ -0,0 +1 @@ +{{#include ../../CHANGELOG.md}} diff --git a/guide/src/class.md b/guide/src/class.md index d63d2bda..1fd1009d 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -15,7 +15,7 @@ For ease of discovery, below is a list of all custom attributes with links to th - [`#[call]`](#callable-objects) - [`#[classattr]`](#class-attributes) - [`#[args]`](#method-arguments) -- [`#[pyproto]`](#class-customizations) +- [`#[pyproto]`](class/protocols.html) ## Defining a new class @@ -695,307 +695,6 @@ num=44, debug=false num=-1, debug=false ``` -## Class customizations - -Python's object model defines several protocols for different object behavior, like sequence, -mapping or number protocols. PyO3 defines separate traits for each of them. To provide specific -Python object behavior, you need to implement the specific trait for your struct. Important note, -each protocol implementation block has to be annotated with the `#[pyproto]` attribute. - -All `#[pyproto]` methods which can be defined below can return `T` instead of `PyResult` if the -method implementation is infallible. In addition, if the return type is `()`, it can be omitted altogether. - -### Basic object customization - -The [`PyObjectProtocol`] trait provides several basic customizations. - -#### Attribute access - -To customize object attribute access, define the following methods: - - * `fn __getattr__(&self, name: FromPyObject) -> PyResult>` - * `fn __setattr__(&mut self, name: FromPyObject, value: FromPyObject) -> PyResult<()>` - * `fn __delattr__(&mut self, name: FromPyObject) -> PyResult<()>` - -Each method corresponds to Python's `self.attr`, `self.attr = value` and `del self.attr` code. - -#### String Conversions - - * `fn __repr__(&self) -> PyResult>` - * `fn __str__(&self) -> PyResult>` - - Possible return types for `__str__` and `__repr__` are `PyResult` or `PyResult`. - - * `fn __bytes__(&self) -> PyResult` - - Provides the conversion to `bytes`. - - * `fn __format__(&self, format_spec: &str) -> PyResult>` - - Special method that is used by the `format()` builtin and the `str.format()` method. - Possible return types are `PyResult` or `PyResult`. - -#### Comparison operators - - * `fn __richcmp__(&self, other: impl FromPyObject, op: CompareOp) -> PyResult` - - Overloads Python comparison operations (`==`, `!=`, `<`, `<=`, `>`, and `>=`). - The `op` argument indicates the comparison operation being performed. - The return type will normally be `PyResult`, but any Python object can be returned. - If `other` is not of the type specified in the signature, the generated code will - automatically `return NotImplemented`. - - * `fn __hash__(&self) -> PyResult` - - Objects that compare equal must have the same hash value. - The return type must be `PyResult` where `T` is one of Rust's primitive integer types. - -#### Other methods - - * `fn __bool__(&self) -> PyResult` - - Determines the "truthyness" of the object. - -### Emulating numeric types - -The [`PyNumberProtocol`] trait allows [emulate numeric types](https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types). - - * `fn __add__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __sub__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __mul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __matmul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __truediv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __floordiv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __mod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __divmod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __pow__(lhs: impl FromPyObject, rhs: impl FromPyObject, modulo: Option) -> PyResult` - * `fn __lshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __rshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __and__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __or__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __xor__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - -These methods are called to implement the binary arithmetic operations -(`+`, `-`, `*`, `@`, `/`, `//`, `%`, `divmod()`, `pow()` and `**`, `<<`, `>>`, `&`, `^`, and `|`). - -If `rhs` is not of the type specified in the signature, the generated code -will automatically `return NotImplemented`. This is not the case for `lhs` -which must match signature or else raise a TypeError. - - -The reflected operations are also available: - - * `fn __radd__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __rsub__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __rmul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __rmatmul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __rtruediv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __rfloordiv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __rmod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __rdivmod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __rpow__(lhs: impl FromPyObject, rhs: impl FromPyObject, modulo: Option) -> PyResult` - * `fn __rlshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __rrshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __rand__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __ror__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - * `fn __rxor__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` - -The code generated for these methods expect that all arguments match the -signature, or raise a TypeError. - -*Note*: Currently implementing the method for a binary arithmetic operations -(e.g, `__add__`) shadows the reflected operation (e.g, `__radd__`). This is -being addressed in [#844](https://github.com/PyO3/pyo3/issues/844). to make -these methods - - -This trait also has support the augmented arithmetic assignments (`+=`, `-=`, -`*=`, `@=`, `/=`, `//=`, `%=`, `**=`, `<<=`, `>>=`, `&=`, `^=`, `|=`): - - * `fn __iadd__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` - * `fn __isub__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` - * `fn __imul__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` - * `fn __imatmul__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` - * `fn __itruediv__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` - * `fn __ifloordiv__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` - * `fn __imod__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` - * `fn __ipow__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` - * `fn __ilshift__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` - * `fn __irshift__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` - * `fn __iand__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` - * `fn __ior__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` - * `fn __ixor__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` - -The following methods implement the unary arithmetic operations (`-`, `+`, `abs()` and `~`): - - * `fn __neg__(&'p self) -> PyResult` - * `fn __pos__(&'p self) -> PyResult` - * `fn __abs__(&'p self) -> PyResult` - * `fn __invert__(&'p self) -> PyResult` - -Support for coercions: - - * `fn __complex__(&'p self) -> PyResult` - * `fn __int__(&'p self) -> PyResult` - * `fn __float__(&'p self) -> PyResult` - -Other: - - * `fn __index__(&'p self) -> PyResult` - * `fn __round__(&'p self, ndigits: Option) -> PyResult` - -### Garbage Collector Integration - -If your type owns references to other Python objects, you will need to -integrate with Python's garbage collector so that the GC is aware of -those references. -To do this, implement the [`PyGCProtocol`] trait for your struct. -It includes two methods `__traverse__` and `__clear__`. -These correspond to the slots `tp_traverse` and `tp_clear` in the Python C API. -`__traverse__` must call `visit.call()` for each reference to another Python object. -`__clear__` must clear out any mutable references to other Python objects -(thus breaking reference cycles). Immutable references do not have to be cleared, -as every cycle must contain at least one mutable reference. -Example: -```rust -extern crate pyo3; - -use pyo3::prelude::*; -use pyo3::PyTraverseError; -use pyo3::gc::{PyGCProtocol, PyVisit}; - -#[pyclass] -struct ClassWithGCSupport { - obj: Option, -} - -#[pyproto] -impl PyGCProtocol for ClassWithGCSupport { - fn __traverse__(&self, visit: PyVisit) -> Result<(), PyTraverseError> { - if let Some(ref obj) = self.obj { - visit.call(obj)? - } - Ok(()) - } - - fn __clear__(&mut self) { - // Clear reference, this decrements ref counter. - self.obj = None; - } -} -``` - -Special protocol trait implementations have to be annotated with the `#[pyproto]` attribute. - -It is also possible to enable GC for custom classes using the `gc` parameter of the `pyclass` attribute. -i.e. `#[pyclass(gc)]`. In that case instances of custom class participate in Python garbage -collection, and it is possible to track them with `gc` module methods. When using the `gc` parameter, -it is *required* to implement the `PyGCProtocol` trait, failure to do so will result in an error -at compile time: - -```compile_fail -#[pyclass(gc)] -struct GCTracked {} // Fails because it does not implement PyGCProtocol -``` - -### Iterator Types - -Iterators can be defined using the -[`PyIterProtocol`](https://docs.rs/pyo3/latest/pyo3/class/iter/trait.PyIterProtocol.html) trait. -It includes two methods `__iter__` and `__next__`: - * `fn __iter__(slf: PyRefMut) -> PyResult>` - * `fn __next__(slf: PyRefMut) -> PyResult>>` - -Returning `None` from `__next__` indicates that that there are no further items. -These two methods can be take either `PyRef` or `PyRefMut` as their -first argument, so that mutable borrow can be avoided if needed. - -Example: - -```rust -use pyo3::prelude::*; -use pyo3::PyIterProtocol; - -#[pyclass] -struct MyIterator { - iter: Box + Send>, -} - -#[pyproto] -impl PyIterProtocol for MyIterator { - fn __iter__(slf: PyRef) -> PyRef { - slf - } - fn __next__(mut slf: PyRefMut) -> Option { - slf.iter.next() - } -} -``` - -In many cases you'll have a distinction between the type being iterated over (i.e. the *iterable*) and the iterator it -provides. In this case, you should implement `PyIterProtocol` for both the iterable and the iterator, but the iterable -only needs to support `__iter__()` while the iterator must support both `__iter__()` and `__next__()`. The default -implementations in `PyIterProtocol` will ensure that the objects behave correctly in Python. For example: - -```rust -# use pyo3::prelude::*; -# use pyo3::PyIterProtocol; - -#[pyclass] -struct Iter { - inner: std::vec::IntoIter, -} - -#[pyproto] -impl PyIterProtocol for Iter { - fn __iter__(slf: PyRef) -> PyRef { - slf - } - - fn __next__(mut slf: PyRefMut) -> Option { - slf.inner.next() - } -} - -#[pyclass] -struct Container { - iter: Vec, -} - -#[pyproto] -impl PyIterProtocol for Container { - fn __iter__(slf: PyRef) -> PyResult> { - let iter = Iter { - inner: slf.iter.clone().into_iter(), - }; - Py::new(slf.py(), iter) - } -} - -# let gil = Python::acquire_gil(); -# let py = gil.python(); -# let inst = pyo3::PyCell::new( -# py, -# Container { -# iter: vec![1, 2, 3, 4], -# }, -# ) -# .unwrap(); -# pyo3::py_run!(py, inst, "assert list(inst) == [1, 2, 3, 4]"); -# pyo3::py_run!(py, inst, "assert list(iter(iter(inst))) == [1, 2, 3, 4]"); -``` - -For more details on Python's iteration protocols, check out [the "Iterator Types" section of the library -documentation](https://docs.python.org/3/library/stdtypes.html#iterator-types). - -#### Returning a value from iteration - -This guide has so far shown how to use `Option` to implement yielding values during iteration. -In Python a generator can also return a value. To express this in Rust, PyO3 provides the -[`IterNextOutput`](https://docs.rs/pyo3/latest/pyo3/class/iter/enum.IterNextOutput.html) enum to -both `Yield` values and `Return` a final value - see its docs for further details and an example. - - ## How methods are implemented Users should be able to define a `#[pyclass]` with or without `#[pymethods]`, while PyO3 needs a @@ -1089,8 +788,6 @@ impl pyo3::pyclass::PyClassSend for MyClass { [`GILGuard`]: https://docs.rs/pyo3/latest/pyo3/struct.GILGuard.html -[`PyGCProtocol`]: https://docs.rs/pyo3/latest/pyo3/class/gc/trait.PyGCProtocol.html -[`PyObjectProtocol`]: https://docs.rs/pyo3/latest/pyo3/class/basic/trait.PyObjectProtocol.html [`PyTypeInfo`]: https://docs.rs/pyo3/latest/pyo3/type_object/trait.PyTypeInfo.html [`PyTypeObject`]: https://docs.rs/pyo3/latest/pyo3/type_object/trait.PyTypeObject.html diff --git a/guide/src/class/protocols.md b/guide/src/class/protocols.md new file mode 100644 index 00000000..efefa48d --- /dev/null +++ b/guide/src/class/protocols.md @@ -0,0 +1,303 @@ +## Class customizations + +Python's object model defines several protocols for different object behavior, like sequence, +mapping or number protocols. PyO3 defines separate traits for each of them. To provide specific +Python object behavior, you need to implement the specific trait for your struct. Important note, +each protocol implementation block has to be annotated with the `#[pyproto]` attribute. + +All `#[pyproto]` methods which can be defined below can return `T` instead of `PyResult` if the +method implementation is infallible. In addition, if the return type is `()`, it can be omitted altogether. + +### Basic object customization + +The [`PyObjectProtocol`] trait provides several basic customizations. + +#### Attribute access + +To customize object attribute access, define the following methods: + + * `fn __getattr__(&self, name: FromPyObject) -> PyResult>` + * `fn __setattr__(&mut self, name: FromPyObject, value: FromPyObject) -> PyResult<()>` + * `fn __delattr__(&mut self, name: FromPyObject) -> PyResult<()>` + +Each method corresponds to Python's `self.attr`, `self.attr = value` and `del self.attr` code. + +#### String Conversions + + * `fn __repr__(&self) -> PyResult>` + * `fn __str__(&self) -> PyResult>` + + Possible return types for `__str__` and `__repr__` are `PyResult` or `PyResult`. + + * `fn __bytes__(&self) -> PyResult` + + Provides the conversion to `bytes`. + + * `fn __format__(&self, format_spec: &str) -> PyResult>` + + Special method that is used by the `format()` builtin and the `str.format()` method. + Possible return types are `PyResult` or `PyResult`. + +#### Comparison operators + + * `fn __richcmp__(&self, other: impl FromPyObject, op: CompareOp) -> PyResult` + + Overloads Python comparison operations (`==`, `!=`, `<`, `<=`, `>`, and `>=`). + The `op` argument indicates the comparison operation being performed. + The return type will normally be `PyResult`, but any Python object can be returned. + If `other` is not of the type specified in the signature, the generated code will + automatically `return NotImplemented`. + + * `fn __hash__(&self) -> PyResult` + + Objects that compare equal must have the same hash value. + The return type must be `PyResult` where `T` is one of Rust's primitive integer types. + +#### Other methods + + * `fn __bool__(&self) -> PyResult` + + Determines the "truthyness" of the object. + +### Emulating numeric types + +The [`PyNumberProtocol`] trait allows [emulate numeric types](https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types). + + * `fn __add__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __sub__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __mul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __matmul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __truediv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __floordiv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __mod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __divmod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __pow__(lhs: impl FromPyObject, rhs: impl FromPyObject, modulo: Option) -> PyResult` + * `fn __lshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __rshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __and__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __or__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __xor__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + +These methods are called to implement the binary arithmetic operations +(`+`, `-`, `*`, `@`, `/`, `//`, `%`, `divmod()`, `pow()` and `**`, `<<`, `>>`, `&`, `^`, and `|`). + +If `rhs` is not of the type specified in the signature, the generated code +will automatically `return NotImplemented`. This is not the case for `lhs` +which must match signature or else raise a TypeError. + + +The reflected operations are also available: + + * `fn __radd__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __rsub__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __rmul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __rmatmul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __rtruediv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __rfloordiv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __rmod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __rdivmod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __rpow__(lhs: impl FromPyObject, rhs: impl FromPyObject, modulo: Option) -> PyResult` + * `fn __rlshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __rrshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __rand__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __ror__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + * `fn __rxor__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult` + +The code generated for these methods expect that all arguments match the +signature, or raise a TypeError. + +*Note*: Currently implementing the method for a binary arithmetic operations +(e.g, `__add__`) shadows the reflected operation (e.g, `__radd__`). This is +being addressed in [#844](https://github.com/PyO3/pyo3/issues/844). to make +these methods + + +This trait also has support the augmented arithmetic assignments (`+=`, `-=`, +`*=`, `@=`, `/=`, `//=`, `%=`, `**=`, `<<=`, `>>=`, `&=`, `^=`, `|=`): + + * `fn __iadd__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` + * `fn __isub__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` + * `fn __imul__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` + * `fn __imatmul__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` + * `fn __itruediv__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` + * `fn __ifloordiv__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` + * `fn __imod__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` + * `fn __ipow__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` + * `fn __ilshift__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` + * `fn __irshift__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` + * `fn __iand__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` + * `fn __ior__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` + * `fn __ixor__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` + +The following methods implement the unary arithmetic operations (`-`, `+`, `abs()` and `~`): + + * `fn __neg__(&'p self) -> PyResult` + * `fn __pos__(&'p self) -> PyResult` + * `fn __abs__(&'p self) -> PyResult` + * `fn __invert__(&'p self) -> PyResult` + +Support for coercions: + + * `fn __complex__(&'p self) -> PyResult` + * `fn __int__(&'p self) -> PyResult` + * `fn __float__(&'p self) -> PyResult` + +Other: + + * `fn __index__(&'p self) -> PyResult` + * `fn __round__(&'p self, ndigits: Option) -> PyResult` + +### Garbage Collector Integration + +If your type owns references to other Python objects, you will need to +integrate with Python's garbage collector so that the GC is aware of +those references. +To do this, implement the [`PyGCProtocol`] trait for your struct. +It includes two methods `__traverse__` and `__clear__`. +These correspond to the slots `tp_traverse` and `tp_clear` in the Python C API. +`__traverse__` must call `visit.call()` for each reference to another Python object. +`__clear__` must clear out any mutable references to other Python objects +(thus breaking reference cycles). Immutable references do not have to be cleared, +as every cycle must contain at least one mutable reference. +Example: +```rust +extern crate pyo3; + +use pyo3::prelude::*; +use pyo3::PyTraverseError; +use pyo3::gc::{PyGCProtocol, PyVisit}; + +#[pyclass] +struct ClassWithGCSupport { + obj: Option, +} + +#[pyproto] +impl PyGCProtocol for ClassWithGCSupport { + fn __traverse__(&self, visit: PyVisit) -> Result<(), PyTraverseError> { + if let Some(ref obj) = self.obj { + visit.call(obj)? + } + Ok(()) + } + + fn __clear__(&mut self) { + // Clear reference, this decrements ref counter. + self.obj = None; + } +} +``` + +Special protocol trait implementations have to be annotated with the `#[pyproto]` attribute. + +It is also possible to enable GC for custom classes using the `gc` parameter of the `pyclass` attribute. +i.e. `#[pyclass(gc)]`. In that case instances of custom class participate in Python garbage +collection, and it is possible to track them with `gc` module methods. When using the `gc` parameter, +it is *required* to implement the `PyGCProtocol` trait, failure to do so will result in an error +at compile time: + +```compile_fail +#[pyclass(gc)] +struct GCTracked {} // Fails because it does not implement PyGCProtocol +``` + +### Iterator Types + +Iterators can be defined using the +[`PyIterProtocol`](https://docs.rs/pyo3/latest/pyo3/class/iter/trait.PyIterProtocol.html) trait. +It includes two methods `__iter__` and `__next__`: + * `fn __iter__(slf: PyRefMut) -> PyResult>` + * `fn __next__(slf: PyRefMut) -> PyResult>>` + +Returning `None` from `__next__` indicates that that there are no further items. +These two methods can be take either `PyRef` or `PyRefMut` as their +first argument, so that mutable borrow can be avoided if needed. + +Example: + +```rust +use pyo3::prelude::*; +use pyo3::PyIterProtocol; + +#[pyclass] +struct MyIterator { + iter: Box + Send>, +} + +#[pyproto] +impl PyIterProtocol for MyIterator { + fn __iter__(slf: PyRef) -> PyRef { + slf + } + fn __next__(mut slf: PyRefMut) -> Option { + slf.iter.next() + } +} +``` + +In many cases you'll have a distinction between the type being iterated over (i.e. the *iterable*) and the iterator it +provides. In this case, you should implement `PyIterProtocol` for both the iterable and the iterator, but the iterable +only needs to support `__iter__()` while the iterator must support both `__iter__()` and `__next__()`. The default +implementations in `PyIterProtocol` will ensure that the objects behave correctly in Python. For example: + +```rust +# use pyo3::prelude::*; +# use pyo3::PyIterProtocol; + +#[pyclass] +struct Iter { + inner: std::vec::IntoIter, +} + +#[pyproto] +impl PyIterProtocol for Iter { + fn __iter__(slf: PyRef) -> PyRef { + slf + } + + fn __next__(mut slf: PyRefMut) -> Option { + slf.inner.next() + } +} + +#[pyclass] +struct Container { + iter: Vec, +} + +#[pyproto] +impl PyIterProtocol for Container { + fn __iter__(slf: PyRef) -> PyResult> { + let iter = Iter { + inner: slf.iter.clone().into_iter(), + }; + Py::new(slf.py(), iter) + } +} + +# let gil = Python::acquire_gil(); +# let py = gil.python(); +# let inst = pyo3::PyCell::new( +# py, +# Container { +# iter: vec![1, 2, 3, 4], +# }, +# ) +# .unwrap(); +# pyo3::py_run!(py, inst, "assert list(inst) == [1, 2, 3, 4]"); +# pyo3::py_run!(py, inst, "assert list(iter(iter(inst))) == [1, 2, 3, 4]"); +``` + +For more details on Python's iteration protocols, check out [the "Iterator Types" section of the library +documentation](https://docs.python.org/3/library/stdtypes.html#iterator-types). + +#### Returning a value from iteration + +This guide has so far shown how to use `Option` to implement yielding values during iteration. +In Python a generator can also return a value. To express this in Rust, PyO3 provides the +[`IterNextOutput`](https://docs.rs/pyo3/latest/pyo3/class/iter/enum.IterNextOutput.html) enum to +both `Yield` values and `Return` a final value - see its docs for further details and an example. + +[`PyGCProtocol`]: https://docs.rs/pyo3/latest/pyo3/class/gc/trait.PyGCProtocol.html +[`PyGCProtocol`]: https://docs.rs/pyo3/latest/pyo3/class/number/trait.PyNumberProtocol.html +[`PyObjectProtocol`]: https://docs.rs/pyo3/latest/pyo3/class/basic/trait.PyObjectProtocol.html diff --git a/guide/src/conversions.md b/guide/src/conversions.md index f46da697..f535e083 100644 --- a/guide/src/conversions.md +++ b/guide/src/conversions.md @@ -1,422 +1,3 @@ # Type Conversions In this portion of the guide we'll talk about the mapping of Python types to Rust types offered by PyO3, as well as the traits available to perform conversions between them. - -## Mapping of Rust types to Python types - -When writing functions callable from Python (such as a `#[pyfunction]` or in a `#[pymethods]` block), the trait `FromPyObject` is required for function arguments, and `IntoPy` is required for function return values. - -Consult the tables in the following section to find the Rust types provided by PyO3 which implement these traits. - -### Argument Types - -When accepting a function argument, it is possible to either use Rust library types or PyO3's Python-native types. (See the next section for discussion on when to use each.) - -The table below contains the Python type and the corresponding function argument types that will accept them: - -| Python | Rust | Rust (Python-native) | -| ------------- |:-------------------------------:|:--------------------:| -| `object` | - | `&PyAny` | -| `str` | `String`, `Cow`, `&str` | `&PyUnicode` | -| `bytes` | `Vec`, `&[u8]` | `&PyBytes` | -| `bool` | `bool` | `&PyBool` | -| `int` | Any integer type (`i32`, `u32`, `usize`, etc) | `&PyLong` | -| `float` | `f32`, `f64` | `&PyFloat` | -| `complex` | `num_complex::Complex`[^1] | `&PyComplex` | -| `list[T]` | `Vec` | `&PyList` | -| `dict[K, V]` | `HashMap`, `BTreeMap`, `hashbrown::HashMap`[^2] | `&PyDict` | -| `tuple[T, U]` | `(T, U)`, `Vec` | `&PyTuple` | -| `set[T]` | `HashSet`, `BTreeSet`, `hashbrown::HashSet`[^2] | `&PySet` | -| `frozenset[T]` | `HashSet`, `BTreeSet`, `hashbrown::HashSet`[^2] | `&PyFrozenSet` | -| `bytearray` | `Vec` | `&PyByteArray` | -| `slice` | - | `&PySlice` | -| `type` | - | `&PyType` | -| `module` | - | `&PyModule` | -| `datetime.datetime` | - | `&PyDateTime` | -| `datetime.date` | - | `&PyDate` | -| `datetime.time` | - | `&PyTime` | -| `datetime.tzinfo` | - | `&PyTzInfo` | -| `datetime.timedelta` | - | `&PyDelta` | -| `typing.Optional[T]` | `Option` | - | -| `typing.Sequence[T]` | `Vec` | `&PySequence` | -| `typing.Iterator[Any]` | - | `&PyIterator` | - -There are also a few special types related to the GIL and Rust-defined `#[pyclass]`es which may come in useful: - -| What | Description | -| ------------- | ------------------------------------- | -| `Python` | A GIL token, used to pass to PyO3 constructors to prove ownership of the GIL | -| `Py` | A Python object isolated from the GIL lifetime. This can be sent to other threads. | -| `PyObject` | An alias for `Py` | -| `&PyCell` | A `#[pyclass]` value owned by Python. | -| `PyRef` | A `#[pyclass]` borrowed immutably. | -| `PyRefMut` | A `#[pyclass]` borrowed mutably. | - -For more detail on accepting `#[pyclass]` values as function arguments, see [the section of this guide on Python Classes](class.md). - -#### Using Rust library types vs Python-native types - -Using Rust library types as function arguments will incur a conversion cost compared to using the Python-native types. Using the Python-native types is almost zero-cost (they just require a type check similar to the Python builtin function `isinstance()`). - -However, once that conversion cost has been paid, the Rust standard library types offer a number of benefits: -- You can write functionality in native-speed Rust code (free of Python's runtime costs). -- You get better interoperability with the rest of the Rust ecosystem. -- You can use `Python::allow_threads` to release the Python GIL and let other Python threads make progress while your Rust code is executing. -- You also benefit from stricter type checking. For example you can specify `Vec`, which will only accept a Python `list` containing integers. The Python-native equivalent, `&PyList`, would accept a Python `list` containing Python objects of any type. - -For most PyO3 usage the conversion cost is worth paying to get these benefits. As always, if you're not sure it's worth it in your case, benchmark it! - -### Returning Rust values to Python - -When returning values from functions callable from Python, Python-native types (`&PyAny`, `&PyDict` etc.) can be used with zero cost. - -Because these types are references, in some situations the Rust compiler may ask for lifetime annotations. If this is the case, you should use `Py`, `Py` etc. instead - which are also zero-cost. For all of these Python-native types `T`, `Py` can be created from `T` with an `.into()` conversion. - -If your function is fallible, it should return `PyResult` or `Result` where `E` implements `From for PyErr`. This will raise a `Python` exception if the `Err` variant is returned. - -Finally, the following Rust types are also able to convert to Python as return values: - -| Rust type | Resulting Python Type | -| ------------- |:-------------------------------:| -| `String` | `str` | -| `&str` | `str` | -| `bool` | `bool` | -| Any integer type (`i32`, `u32`, `usize`, etc) | `int` | -| `f32`, `f64` | `float` | -| `Option` | `Optional[T]` | -| `(T, U)` | `Tuple[T, U]` | -| `Vec` | `List[T]` | -| `HashMap` | `Dict[K, V]` | -| `BTreeMap` | `Dict[K, V]` | -| `HashSet` | `Set[T]` | -| `BTreeSet` | `Set[T]` | -| `&PyCell` | `T` | -| `PyRef` | `T` | -| `PyRefMut` | `T` | - -## Traits - -PyO3 provides some handy traits to convert between Python types and Rust types. - -### `.extract()` and the `FromPyObject` trait - -The easiest way to convert a Python object to a Rust value is using -`.extract()`. It returns a `PyResult` with a type error if the conversion -fails, so usually you will use something like - -```ignore -let v: Vec = obj.extract()?; -``` - -This method is available for many Python object types, and can produce a wide -variety of Rust types, which you can check out in the implementor list of -[`FromPyObject`]. - -[`FromPyObject`] is also implemented for your own Rust types wrapped as Python -objects (see [the chapter about classes](class.md)). There, in order to both be -able to operate on mutable references *and* satisfy Rust's rules of non-aliasing -mutable references, you have to extract the PyO3 reference wrappers [`PyRef`] -and [`PyRefMut`]. They work like the reference wrappers of -`std::cell::RefCell` and ensure (at runtime) that Rust borrows are allowed. - -#### Deriving [`FromPyObject`] - -[`FromPyObject`] can be automatically derived for many kinds of structs and enums -if the member types themselves implement `FromPyObject`. This even includes members -with a generic type `T: FromPyObject`. Derivation for empty enums, enum variants and -structs is not supported. - -#### Deriving [`FromPyObject`] for structs - -``` -use pyo3::prelude::*; - -#[derive(FromPyObject)] -struct RustyStruct { - my_string: String, -} -``` - -The derivation generates code that will per default access the attribute `my_string` on -the Python object, i.e. `obj.getattr("my_string")`, and call `extract()` on the attribute. -It is also possible to access the value on the Python object through `obj.get_item("my_string")` -by setting the attribute `pyo3(item)` on the field: -``` -use pyo3::prelude::*; - -#[derive(FromPyObject)] -struct RustyStruct { - #[pyo3(item)] - my_string: String, -} -``` - -The argument passed to `getattr` and `get_item` can also be configured: - -``` -use pyo3::prelude::*; - -#[derive(FromPyObject)] -struct RustyStruct { - #[pyo3(item("key"))] - string_in_mapping: String, - #[pyo3(attribute("name"))] - string_attr: String, -} -``` - -This tries to extract `string_attr` from the attribute `name` and `string_in_mapping` -from a mapping with the key `"key"`. The arguments for `attribute` are restricted to -non-empty string literals while `item` can take any valid literal that implements -`ToBorrowedObject`. - -#### Deriving [`FromPyObject`] for tuple structs - -Tuple structs are also supported but do not allow customizing the extraction. The input is -always assumed to be a Python tuple with the same length as the Rust type, the `n`th field -is extracted from the `n`th item in the Python tuple. - -``` -use pyo3::prelude::*; - -#[derive(FromPyObject)] -struct RustyTuple(String, String); -``` - -Tuple structs with a single field are treated as wrapper types which are described in the -following section. To override this behaviour and ensure that the input is in fact a tuple, -specify the struct as -``` -use pyo3::prelude::*; - -#[derive(FromPyObject)] -struct RustyTuple((String,)); -``` - -#### Deriving [`FromPyObject`] for wrapper types - -The `pyo3(transparent)` attribute can be used on structs with exactly one field. This results -in extracting directly from the input object, i.e. `obj.extract()`, rather than trying to access -an item or attribute. This behaviour is enabled per default for newtype structs and tuple-variants -with a single field. - -``` -use pyo3::prelude::*; - -#[derive(FromPyObject)] -struct RustyTransparentTupleStruct(String); - -#[derive(FromPyObject)] -#[pyo3(transparent)] -struct RustyTransparentStruct { - inner: String, -} -``` - -#### Deriving [`FromPyObject`] for enums - -The `FromPyObject` derivation for enums generates code that tries to extract the variants in the -order of the fields. As soon as a variant can be extracted succesfully, that variant is returned. - -The same customizations and restrictions described for struct derivations apply to enum variants, -i.e. a tuple variant assumes that the input is a Python tuple, and a struct variant defaults to -extracting fields as attributes but can be configured in the same manner. The `transparent` -attribute can be applied to single-field-variants. - -``` -use pyo3::prelude::*; - -#[derive(FromPyObject)] -enum RustyEnum<'a> { - Int(usize), // input is a positive int - String(String), // input is a string - IntTuple(usize, usize), // input is a 2-tuple with positive ints - StringIntTuple(String, usize), // input is a 2-tuple with String and int - Coordinates3d { // needs to be in front of 2d - x: usize, - y: usize, - z: usize, - }, - Coordinates2d { // only gets checked if the input did not have `z` - #[pyo3(attribute("x"))] - a: usize, - #[pyo3(attribute("y"))] - b: usize, - }, - #[pyo3(transparent)] - CatchAll(&'a PyAny), // This extraction never fails -} -``` - -If none of the enum variants match, a `PyValueError` containing the names of the -tested variants is returned. The names reported in the error message can be customized -through the `pyo3(annotation = "name")` attribute, e.g. to use conventional Python type -names: - -``` -use pyo3::prelude::*; - -#[derive(FromPyObject)] -enum RustyEnum { - #[pyo3(transparent, annotation = "str")] - String(String), - #[pyo3(transparent, annotation = "int")] - Int(isize), -} -``` - -If the input is neither a string nor an integer, the error message will be: -`"Can't convert to Union[str, int]"`, where `` is replaced by the type name and -`repr()` of the input object. - -#### `#[derive(FromPyObject)]` Container Attributes -- `pyo3(transparent)` - - extract the field directly from the object as `obj.extract()` instead of `get_item()` or - `getattr()` - - Newtype structs and tuple-variants are treated as transparent per default. - - only supported for single-field structs and enum variants -- `pyo3(annotation = "name")` - - changes the name of the failed variant in the generated error message in case of failure. - - e.g. `pyo3("int")` reports the variant's type as `int`. - - only supported for enum variants - -#### `#[derive(FromPyObject)]` Field Attributes -- `pyo3(attribute)`, `pyo3(attribute("name"))` - - retrieve the field from an attribute, possibly with a custom name specified as an argument - - argument must be a string-literal. -- `pyo3(item)`, `pyo3(item("key"))` - - retrieve the field from a mapping, possibly with the custom key specified as an argument. - - can be any literal that implements `ToBorrowedObject` - -### `IntoPy` - -This trait defines the to-python conversion for a Rust type. It is usually implemented as -`IntoPy`, which is the trait needed for returning a value from `#[pyfunction]` and -`#[pymethods]`. - -All types in PyO3 implement this trait, as does a `#[pyclass]` which doesn't use `extends`. - -Occasionally you may choose to implement this for custom types which are mapped to Python types -_without_ having a unique python type. - -``` -use pyo3::prelude::*; - -struct MyPyObjectWrapper(PyObject); - -impl IntoPy for MyPyObjectWrapper { - fn into_py(self, py: Python) -> PyObject { - self.0 - } -} -``` - -### The `ToPyObject` trait - -[`ToPyObject`] is a conversion trait that allows various objects to be -converted into [`PyObject`]. `IntoPy` serves the -same purpose, except that it consumes `self`. - - -### `*args` and `**kwargs` for Python object calls - -There are several ways how to pass positional and keyword arguments to a Python object call. -[`PyAny`] provides two methods: - -* `call` - call any callable Python object. -* `call_method` - call a specific method on the object, shorthand for `get_attr` then `call`. - -Both methods need `args` and `kwargs` arguments, but there are variants for less -complex calls, such as `call1` for only `args` and `call0` for no arguments at all. - -```rust -use pyo3::prelude::*; -use pyo3::types::{PyDict, PyTuple}; - -struct SomeObject; -impl SomeObject { - fn new(py: Python) -> PyObject { - PyDict::new(py).to_object(py) - } -} - -fn main() { - let arg1 = "arg1"; - let arg2 = "arg2"; - let arg3 = "arg3"; - - let gil = Python::acquire_gil(); - let py = gil.python(); - - let obj = SomeObject::new(py); - - // call object without empty arguments - obj.call0(py); - - // call object with PyTuple - let args = PyTuple::new(py, &[arg1, arg2, arg3]); - obj.call1(py, args); - - // pass arguments as rust tuple - let args = (arg1, arg2, arg3); - obj.call1(py, args); -} -``` - -`kwargs` can be `None` or `Some(&PyDict)`. You can use the -[`IntoPyDict`] trait to convert other dict-like containers, -e.g. `HashMap` or `BTreeMap`, as well as tuples with up to 10 elements and -`Vec`s where each element is a two-element tuple. - -```rust -use pyo3::prelude::*; -use pyo3::types::{IntoPyDict, PyDict}; -use std::collections::HashMap; - -struct SomeObject; - -impl SomeObject { - fn new(py: Python) -> PyObject { - PyDict::new(py).to_object(py) - } -} - -fn main() { - let key1 = "key1"; - let val1 = 1; - let key2 = "key2"; - let val2 = 2; - - let gil = Python::acquire_gil(); - let py = gil.python(); - - let obj = SomeObject::new(py); - - // call object with PyDict - let kwargs = [(key1, val1)].into_py_dict(py); - obj.call(py, (), Some(kwargs)); - - // pass arguments as Vec - let kwargs = vec![(key1, val1), (key2, val2)]; - obj.call(py, (), Some(kwargs.into_py_dict(py))); - - // pass arguments as HashMap - let mut kwargs = HashMap::<&str, i32>::new(); - kwargs.insert(key1, 1); - obj.call(py, (), Some(kwargs.into_py_dict(py))); -} -``` - -[`IntoPy`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.IntoPy.html -[`FromPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.FromPyObject.html -[`ToPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.ToPyObject.html -[`PyObject`]: https://docs.rs/pyo3/latest/pyo3/type.PyObject.html -[`PyTuple`]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyTuple.html -[`PyAny`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html -[`IntoPyDict`]: https://docs.rs/pyo3/latest/pyo3/types/trait.IntoPyDict.html - -[`PyRef`]: https://docs.rs/pyo3/latest/pyo3/pycell/struct.PyRef.html -[`PyRefMut`]: https://docs.rs/pyo3/latest/pyo3/pycell/struct.PyRefMut.html - -[^1]: Requires the `num-complex` optional feature. -[^2]: Requires the `hashbrown` optional feature. diff --git a/guide/src/conversions/tables.md b/guide/src/conversions/tables.md new file mode 100644 index 00000000..be1750af --- /dev/null +++ b/guide/src/conversions/tables.md @@ -0,0 +1,96 @@ +## Mapping of Rust types to Python types + +When writing functions callable from Python (such as a `#[pyfunction]` or in a `#[pymethods]` block), the trait `FromPyObject` is required for function arguments, and `IntoPy` is required for function return values. + +Consult the tables in the following section to find the Rust types provided by PyO3 which implement these traits. + +### Argument Types + +When accepting a function argument, it is possible to either use Rust library types or PyO3's Python-native types. (See the next section for discussion on when to use each.) + +The table below contains the Python type and the corresponding function argument types that will accept them: + +| Python | Rust | Rust (Python-native) | +| ------------- |:-------------------------------:|:--------------------:| +| `object` | - | `&PyAny` | +| `str` | `String`, `Cow`, `&str` | `&PyUnicode` | +| `bytes` | `Vec`, `&[u8]` | `&PyBytes` | +| `bool` | `bool` | `&PyBool` | +| `int` | Any integer type (`i32`, `u32`, `usize`, etc) | `&PyLong` | +| `float` | `f32`, `f64` | `&PyFloat` | +| `complex` | `num_complex::Complex`[^1] | `&PyComplex` | +| `list[T]` | `Vec` | `&PyList` | +| `dict[K, V]` | `HashMap`, `BTreeMap`, `hashbrown::HashMap`[^2] | `&PyDict` | +| `tuple[T, U]` | `(T, U)`, `Vec` | `&PyTuple` | +| `set[T]` | `HashSet`, `BTreeSet`, `hashbrown::HashSet`[^2] | `&PySet` | +| `frozenset[T]` | `HashSet`, `BTreeSet`, `hashbrown::HashSet`[^2] | `&PyFrozenSet` | +| `bytearray` | `Vec` | `&PyByteArray` | +| `slice` | - | `&PySlice` | +| `type` | - | `&PyType` | +| `module` | - | `&PyModule` | +| `datetime.datetime` | - | `&PyDateTime` | +| `datetime.date` | - | `&PyDate` | +| `datetime.time` | - | `&PyTime` | +| `datetime.tzinfo` | - | `&PyTzInfo` | +| `datetime.timedelta` | - | `&PyDelta` | +| `typing.Optional[T]` | `Option` | - | +| `typing.Sequence[T]` | `Vec` | `&PySequence` | +| `typing.Iterator[Any]` | - | `&PyIterator` | +| `typing.Union[...]` | See [`#[derive(FromPyObject)]`](traits.html#deriving-a-hrefhttpsdocsrspyo3latestpyo3conversiontraitfrompyobjecthtmlfrompyobjecta-for-enums) | - | + +There are also a few special types related to the GIL and Rust-defined `#[pyclass]`es which may come in useful: + +| What | Description | +| ------------- | ------------------------------------- | +| `Python` | A GIL token, used to pass to PyO3 constructors to prove ownership of the GIL | +| `Py` | A Python object isolated from the GIL lifetime. This can be sent to other threads. | +| `PyObject` | An alias for `Py` | +| `&PyCell` | A `#[pyclass]` value owned by Python. | +| `PyRef` | A `#[pyclass]` borrowed immutably. | +| `PyRefMut` | A `#[pyclass]` borrowed mutably. | + +For more detail on accepting `#[pyclass]` values as function arguments, see [the section of this guide on Python Classes](class.md). + +#### Using Rust library types vs Python-native types + +Using Rust library types as function arguments will incur a conversion cost compared to using the Python-native types. Using the Python-native types is almost zero-cost (they just require a type check similar to the Python builtin function `isinstance()`). + +However, once that conversion cost has been paid, the Rust standard library types offer a number of benefits: +- You can write functionality in native-speed Rust code (free of Python's runtime costs). +- You get better interoperability with the rest of the Rust ecosystem. +- You can use `Python::allow_threads` to release the Python GIL and let other Python threads make progress while your Rust code is executing. +- You also benefit from stricter type checking. For example you can specify `Vec`, which will only accept a Python `list` containing integers. The Python-native equivalent, `&PyList`, would accept a Python `list` containing Python objects of any type. + +For most PyO3 usage the conversion cost is worth paying to get these benefits. As always, if you're not sure it's worth it in your case, benchmark it! + +### Returning Rust values to Python + +When returning values from functions callable from Python, Python-native types (`&PyAny`, `&PyDict` etc.) can be used with zero cost. + +Because these types are references, in some situations the Rust compiler may ask for lifetime annotations. If this is the case, you should use `Py`, `Py` etc. instead - which are also zero-cost. For all of these Python-native types `T`, `Py` can be created from `T` with an `.into()` conversion. + +If your function is fallible, it should return `PyResult` or `Result` where `E` implements `From for PyErr`. This will raise a `Python` exception if the `Err` variant is returned. + +Finally, the following Rust types are also able to convert to Python as return values: + +| Rust type | Resulting Python Type | +| ------------- |:-------------------------------:| +| `String` | `str` | +| `&str` | `str` | +| `bool` | `bool` | +| Any integer type (`i32`, `u32`, `usize`, etc) | `int` | +| `f32`, `f64` | `float` | +| `Option` | `Optional[T]` | +| `(T, U)` | `Tuple[T, U]` | +| `Vec` | `List[T]` | +| `HashMap` | `Dict[K, V]` | +| `BTreeMap` | `Dict[K, V]` | +| `HashSet` | `Set[T]` | +| `BTreeSet` | `Set[T]` | +| `&PyCell` | `T` | +| `PyRef` | `T` | +| `PyRefMut` | `T` | + +[^1]: Requires the `num-complex` optional feature. + +[^2]: Requires the `hashbrown` optional feature. diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md new file mode 100644 index 00000000..7d997538 --- /dev/null +++ b/guide/src/conversions/traits.md @@ -0,0 +1,324 @@ +## Conversion traits + +PyO3 provides some handy traits to convert between Python types and Rust types. + +### `.extract()` and the `FromPyObject` trait + +The easiest way to convert a Python object to a Rust value is using +`.extract()`. It returns a `PyResult` with a type error if the conversion +fails, so usually you will use something like + +```ignore +let v: Vec = obj.extract()?; +``` + +This method is available for many Python object types, and can produce a wide +variety of Rust types, which you can check out in the implementor list of +[`FromPyObject`]. + +[`FromPyObject`] is also implemented for your own Rust types wrapped as Python +objects (see [the chapter about classes](class.md)). There, in order to both be +able to operate on mutable references *and* satisfy Rust's rules of non-aliasing +mutable references, you have to extract the PyO3 reference wrappers [`PyRef`] +and [`PyRefMut`]. They work like the reference wrappers of +`std::cell::RefCell` and ensure (at runtime) that Rust borrows are allowed. + +#### Deriving [`FromPyObject`] + +[`FromPyObject`] can be automatically derived for many kinds of structs and enums +if the member types themselves implement `FromPyObject`. This even includes members +with a generic type `T: FromPyObject`. Derivation for empty enums, enum variants and +structs is not supported. + +#### Deriving [`FromPyObject`] for structs + +``` +use pyo3::prelude::*; + +#[derive(FromPyObject)] +struct RustyStruct { + my_string: String, +} +``` + +The derivation generates code that will per default access the attribute `my_string` on +the Python object, i.e. `obj.getattr("my_string")`, and call `extract()` on the attribute. +It is also possible to access the value on the Python object through `obj.get_item("my_string")` +by setting the attribute `pyo3(item)` on the field: +``` +use pyo3::prelude::*; + +#[derive(FromPyObject)] +struct RustyStruct { + #[pyo3(item)] + my_string: String, +} +``` + +The argument passed to `getattr` and `get_item` can also be configured: + +``` +use pyo3::prelude::*; + +#[derive(FromPyObject)] +struct RustyStruct { + #[pyo3(item("key"))] + string_in_mapping: String, + #[pyo3(attribute("name"))] + string_attr: String, +} +``` + +This tries to extract `string_attr` from the attribute `name` and `string_in_mapping` +from a mapping with the key `"key"`. The arguments for `attribute` are restricted to +non-empty string literals while `item` can take any valid literal that implements +`ToBorrowedObject`. + +#### Deriving [`FromPyObject`] for tuple structs + +Tuple structs are also supported but do not allow customizing the extraction. The input is +always assumed to be a Python tuple with the same length as the Rust type, the `n`th field +is extracted from the `n`th item in the Python tuple. + +``` +use pyo3::prelude::*; + +#[derive(FromPyObject)] +struct RustyTuple(String, String); +``` + +Tuple structs with a single field are treated as wrapper types which are described in the +following section. To override this behaviour and ensure that the input is in fact a tuple, +specify the struct as +``` +use pyo3::prelude::*; + +#[derive(FromPyObject)] +struct RustyTuple((String,)); +``` + +#### Deriving [`FromPyObject`] for wrapper types + +The `pyo3(transparent)` attribute can be used on structs with exactly one field. This results +in extracting directly from the input object, i.e. `obj.extract()`, rather than trying to access +an item or attribute. This behaviour is enabled per default for newtype structs and tuple-variants +with a single field. + +``` +use pyo3::prelude::*; + +#[derive(FromPyObject)] +struct RustyTransparentTupleStruct(String); + +#[derive(FromPyObject)] +#[pyo3(transparent)] +struct RustyTransparentStruct { + inner: String, +} +``` + +#### Deriving [`FromPyObject`] for enums + +The `FromPyObject` derivation for enums generates code that tries to extract the variants in the +order of the fields. As soon as a variant can be extracted succesfully, that variant is returned. +This makes it possible to extract Python types like `Union[str, int]`. + +The same customizations and restrictions described for struct derivations apply to enum variants, +i.e. a tuple variant assumes that the input is a Python tuple, and a struct variant defaults to +extracting fields as attributes but can be configured in the same manner. The `transparent` +attribute can be applied to single-field-variants. + +``` +use pyo3::prelude::*; + +#[derive(FromPyObject)] +enum RustyEnum<'a> { + Int(usize), // input is a positive int + String(String), // input is a string + IntTuple(usize, usize), // input is a 2-tuple with positive ints + StringIntTuple(String, usize), // input is a 2-tuple with String and int + Coordinates3d { // needs to be in front of 2d + x: usize, + y: usize, + z: usize, + }, + Coordinates2d { // only gets checked if the input did not have `z` + #[pyo3(attribute("x"))] + a: usize, + #[pyo3(attribute("y"))] + b: usize, + }, + #[pyo3(transparent)] + CatchAll(&'a PyAny), // This extraction never fails +} +``` + +If none of the enum variants match, a `PyValueError` containing the names of the +tested variants is returned. The names reported in the error message can be customized +through the `pyo3(annotation = "name")` attribute, e.g. to use conventional Python type +names: + +``` +use pyo3::prelude::*; + +#[derive(FromPyObject)] +enum RustyEnum { + #[pyo3(transparent, annotation = "str")] + String(String), + #[pyo3(transparent, annotation = "int")] + Int(isize), +} +``` + +If the input is neither a string nor an integer, the error message will be: +`"Can't convert to Union[str, int]"`, where `` is replaced by the type name and +`repr()` of the input object. + +#### `#[derive(FromPyObject)]` Container Attributes +- `pyo3(transparent)` + - extract the field directly from the object as `obj.extract()` instead of `get_item()` or + `getattr()` + - Newtype structs and tuple-variants are treated as transparent per default. + - only supported for single-field structs and enum variants +- `pyo3(annotation = "name")` + - changes the name of the failed variant in the generated error message in case of failure. + - e.g. `pyo3("int")` reports the variant's type as `int`. + - only supported for enum variants + +#### `#[derive(FromPyObject)]` Field Attributes +- `pyo3(attribute)`, `pyo3(attribute("name"))` + - retrieve the field from an attribute, possibly with a custom name specified as an argument + - argument must be a string-literal. +- `pyo3(item)`, `pyo3(item("key"))` + - retrieve the field from a mapping, possibly with the custom key specified as an argument. + - can be any literal that implements `ToBorrowedObject` + +### `IntoPy` + +This trait defines the to-python conversion for a Rust type. It is usually implemented as +`IntoPy`, which is the trait needed for returning a value from `#[pyfunction]` and +`#[pymethods]`. + +All types in PyO3 implement this trait, as does a `#[pyclass]` which doesn't use `extends`. + +Occasionally you may choose to implement this for custom types which are mapped to Python types +_without_ having a unique python type. + +``` +use pyo3::prelude::*; + +struct MyPyObjectWrapper(PyObject); + +impl IntoPy for MyPyObjectWrapper { + fn into_py(self, py: Python) -> PyObject { + self.0 + } +} +``` + +### The `ToPyObject` trait + +[`ToPyObject`] is a conversion trait that allows various objects to be +converted into [`PyObject`]. `IntoPy` serves the +same purpose, except that it consumes `self`. + + +### `*args` and `**kwargs` for Python object calls + +There are several ways how to pass positional and keyword arguments to a Python object call. +[`PyAny`] provides two methods: + +* `call` - call any callable Python object. +* `call_method` - call a specific method on the object, shorthand for `get_attr` then `call`. + +Both methods need `args` and `kwargs` arguments, but there are variants for less +complex calls, such as `call1` for only `args` and `call0` for no arguments at all. + +```rust +use pyo3::prelude::*; +use pyo3::types::{PyDict, PyTuple}; + +struct SomeObject; +impl SomeObject { + fn new(py: Python) -> PyObject { + PyDict::new(py).to_object(py) + } +} + +fn main() { + let arg1 = "arg1"; + let arg2 = "arg2"; + let arg3 = "arg3"; + + let gil = Python::acquire_gil(); + let py = gil.python(); + + let obj = SomeObject::new(py); + + // call object without empty arguments + obj.call0(py); + + // call object with PyTuple + let args = PyTuple::new(py, &[arg1, arg2, arg3]); + obj.call1(py, args); + + // pass arguments as rust tuple + let args = (arg1, arg2, arg3); + obj.call1(py, args); +} +``` + +`kwargs` can be `None` or `Some(&PyDict)`. You can use the +[`IntoPyDict`] trait to convert other dict-like containers, +e.g. `HashMap` or `BTreeMap`, as well as tuples with up to 10 elements and +`Vec`s where each element is a two-element tuple. + +```rust +use pyo3::prelude::*; +use pyo3::types::{IntoPyDict, PyDict}; +use std::collections::HashMap; + +struct SomeObject; + +impl SomeObject { + fn new(py: Python) -> PyObject { + PyDict::new(py).to_object(py) + } +} + +fn main() { + let key1 = "key1"; + let val1 = 1; + let key2 = "key2"; + let val2 = 2; + + let gil = Python::acquire_gil(); + let py = gil.python(); + + let obj = SomeObject::new(py); + + // call object with PyDict + let kwargs = [(key1, val1)].into_py_dict(py); + obj.call(py, (), Some(kwargs)); + + // pass arguments as Vec + let kwargs = vec![(key1, val1), (key2, val2)]; + obj.call(py, (), Some(kwargs.into_py_dict(py))); + + // pass arguments as HashMap + let mut kwargs = HashMap::<&str, i32>::new(); + kwargs.insert(key1, 1); + obj.call(py, (), Some(kwargs.into_py_dict(py))); +} +``` + +[`IntoPy`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.IntoPy.html +[`FromPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.FromPyObject.html +[`ToPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.ToPyObject.html +[`PyObject`]: https://docs.rs/pyo3/latest/pyo3/type.PyObject.html +[`PyTuple`]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyTuple.html +[`PyAny`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html +[`IntoPyDict`]: https://docs.rs/pyo3/latest/pyo3/types/trait.IntoPyDict.html + +[`PyRef`]: https://docs.rs/pyo3/latest/pyo3/pycell/struct.PyRef.html +[`PyRefMut`]: https://docs.rs/pyo3/latest/pyo3/pycell/struct.PyRefMut.html diff --git a/guide/src/function.md b/guide/src/function.md index 1a12d8ec..b33221c9 100644 --- a/guide/src/function.md +++ b/guide/src/function.md @@ -36,7 +36,7 @@ fn double(x: usize) -> usize { #[pymodule] fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(double)).unwrap(); + m.add_function(wrap_pyfunction!(double)).unwrap(); Ok(()) } @@ -65,7 +65,7 @@ fn num_kwds(kwds: Option<&PyDict>) -> usize { #[pymodule] fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(num_kwds)).unwrap(); + m.add_function(wrap_pyfunction!(num_kwds)).unwrap(); Ok(()) } @@ -189,3 +189,47 @@ If you have a static function, you can expose it with `#[pyfunction]` and use [` [`PyAny::call1`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html#tymethod.call1 [`PyObject`]: https://docs.rs/pyo3/latest/pyo3/type.PyObject.html [`wrap_pyfunction!`]: https://docs.rs/pyo3/latest/pyo3/macro.wrap_pyfunction.html + +### Accessing the module of a function + +It is possible to access the module of a `#[pyfunction]` and `#[pyfn]` in the +function body by passing the `pass_module` argument to the attribute: + +```rust +use pyo3::wrap_pyfunction; +use pyo3::prelude::*; + +#[pyfunction(pass_module)] +fn pyfunction_with_module(module: &PyModule) -> PyResult<&str> { + module.name() +} + +#[pymodule] +fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(pyfunction_with_module)) +} + +# fn main() {} +``` + +If `pass_module` is set, the first argument **must** be the `&PyModule`. It is then possible to use the module +in the function body. + +The same works for `#[pyfn]`: + +```rust +use pyo3::wrap_pyfunction; +use pyo3::prelude::*; + +#[pymodule] +fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> { + + #[pyfn(m, "module_name", pass_module)] + fn module_name(module: &PyModule) -> PyResult<&str> { + module.name() + } + Ok(()) +} + +# fn main() {} +``` diff --git a/guide/src/index.md b/guide/src/index.md index b7e12841..80534cae 100644 --- a/guide/src/index.md +++ b/guide/src/index.md @@ -1,4 +1,4 @@ -# The PyO3 Guide +# The PyO3 user guide Welcome to the PyO3 user guide! This book is a companion to [PyO3's API docs](https://docs.rs/pyo3). It contains examples and documentation to explain all of PyO3's use cases in detail. diff --git a/guide/src/logging.md b/guide/src/logging.md index a9078b5d..500a8804 100644 --- a/guide/src/logging.md +++ b/guide/src/logging.md @@ -35,7 +35,7 @@ fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> { // A good place to install the Rust -> Python logger. pyo3_log::init(); - m.add_wrapped(wrap_pyfunction!(log_something))?; + m.add_function(wrap_pyfunction!(log_something))?; Ok(()) } ``` diff --git a/guide/src/migration.md b/guide/src/migration.md index bd0dddba..df84f1fd 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -1,7 +1,7 @@ -# Appendix B: Migrating from older PyO3 versions +# Migrating from older PyO3 versions This guide can help you upgrade code through breaking changes from one PyO3 version to the next. -For a detailed list of all changes, see [CHANGELOG.md](https://github.com/PyO3/pyo3/blob/master/CHANGELOG.md) +For a detailed list of all changes, see the [CHANGELOG](changelog.md). ## from 0.11.* to 0.12 @@ -248,7 +248,7 @@ impl MyClass { ``` Basically you can return `Self` or `Result` directly. -For more, see [the constructor section](https://pyo3.rs/master/class.html#constructor) of this guide. +For more, see [the constructor section](class.html#constructor) of this guide. ### PyCell PyO3 0.9 introduces [`PyCell`], which is a [`RefCell`]-like object wrapper diff --git a/guide/src/module.md b/guide/src/module.md index 4dea21b1..6b1d4581 100644 --- a/guide/src/module.md +++ b/guide/src/module.md @@ -32,16 +32,22 @@ fn sum_as_string(a: i64, b: i64) -> String { # fn main() {} ``` -The `#[pymodule]` procedural macro attribute takes care of exporting the initialization function of your module to Python. It can take as an argument the name of your module, which must be the name of the `.so` or `.pyd` file; the default is the Rust function's name. +The `#[pymodule]` procedural macro attribute takes care of exporting the initialization function of your +module to Python. It can take as an argument the name of your module, which must be the name of the `.so` +or `.pyd` file; the default is the Rust function's name. -If the name of the module (the default being the function name) does not match the name of the `.so` or `.pyd` file, you will get an import error in Python with the following message: +If the name of the module (the default being the function name) does not match the name of the `.so` or +`.pyd` file, you will get an import error in Python with the following message: `ImportError: dynamic module does not define module export function (PyInit_name_of_your_module)` -To import the module, either copy the shared library as described in [the README](https://github.com/PyO3/pyo3) or use a tool, e.g. `maturin develop` with [maturin](https://github.com/PyO3/maturin) or `python setup.py develop` with [setuptools-rust](https://github.com/PyO3/setuptools-rust). +To import the module, either copy the shared library as described in [the README](https://github.com/PyO3/pyo3) +or use a tool, e.g. `maturin develop` with [maturin](https://github.com/PyO3/maturin) or +`python setup.py develop` with [setuptools-rust](https://github.com/PyO3/setuptools-rust). ## Documentation -The [Rust doc comments](https://doc.rust-lang.org/stable/book/first-edition/comments.html) of the module initialization function will be applied automatically as the Python docstring of your module. +The [Rust doc comments](https://doc.rust-lang.org/stable/book/first-edition/comments.html) of the module +initialization function will be applied automatically as the Python docstring of your module. ```python import rust2py @@ -53,7 +59,8 @@ Which means that the above Python code will print `This module is implemented in ## Modules as objects -In Python, modules are first class objects. This means that you can store them as values or add them to dicts or other modules: +In Python, modules are first class objects. This means that you can store them as values or add them to +dicts or other modules: ```rust use pyo3::prelude::*; @@ -65,15 +72,16 @@ fn subfunction() -> String { "Subfunction".to_string() } -#[pymodule] -fn submodule(_py: Python, module: &PyModule) -> PyResult<()> { - module.add_wrapped(wrap_pyfunction!(subfunction))?; +fn init_submodule(module: &PyModule) -> PyResult<()> { + module.add_function(wrap_pyfunction!(subfunction))?; Ok(()) } #[pymodule] -fn supermodule(_py: Python, module: &PyModule) -> PyResult<()> { - module.add_wrapped(wrap_pymodule!(submodule))?; +fn supermodule(py: Python, module: &PyModule) -> PyResult<()> { + let submod = PyModule::new(py, "submodule")?; + init_submodule(submod)?; + module.add_submodule(submod)?; Ok(()) } @@ -86,3 +94,5 @@ fn supermodule(_py: Python, module: &PyModule) -> PyResult<()> { ``` This way, you can create a module hierarchy within a single extension module. + +It is not necessary to add `#[pymodule]` on nested modules, this is only required on the top-level module. \ No newline at end of file diff --git a/guide/src/rust_cpython.md b/guide/src/rust_cpython.md index 3017b957..91f33933 100644 --- a/guide/src/rust_cpython.md +++ b/guide/src/rust_cpython.md @@ -1,8 +1,6 @@ -# Appendix A: PyO3 and rust-cpython +# PyO3 and rust-cpython -PyO3 began as fork of [rust-cpython](https://github.com/dgrunwald/rust-cpython) when rust-cpython wasn't maintained. Over the time PyO3 has become fundamentally different from rust-cpython. - -This chapter is based on the discussion in [PyO3/pyo3#55](https://github.com/PyO3/pyo3/issues/55). +PyO3 began as fork of [rust-cpython](https://github.com/dgrunwald/rust-cpython) when rust-cpython wasn't maintained. Over time PyO3 has become fundamentally different from rust-cpython. ## Macros diff --git a/guide/src/trait_bounds.md b/guide/src/trait_bounds.md index 8dde99e0..ff2fa810 100644 --- a/guide/src/trait_bounds.md +++ b/guide/src/trait_bounds.md @@ -1,6 +1,6 @@ # Using in Python a Rust function with trait bounds -PyO3 allows for easy conversion from Rust to Python for certain functions and classes (see the [conversion table](https://pyo3.rs/master/conversions.html)). +PyO3 allows for easy conversion from Rust to Python for certain functions and classes (see the [conversion table](conversions/tables.html). However, it is not always straightforward to convert Rust code that requires a given trait implementation as an argument. This tutorial explains how to convert a Rust function that takes a trait as argument for use in Python with classes implementing the same methods as the trait. @@ -488,7 +488,7 @@ pub struct UserModel { #[pymodule] fn trait_exposure(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; - m.add_wrapped(wrap_pyfunction!(solve_wrapper))?; + m.add_function(wrap_pyfunction!(solve_wrapper))?; Ok(()) } diff --git a/guide/src/types.md b/guide/src/types.md index 6f2d97e1..18ba0a1a 100644 --- a/guide/src/types.md +++ b/guide/src/types.md @@ -61,23 +61,54 @@ such as `getattr`, `setattr`, and `.call`. **Conversions:** +For a `&PyAny` object reference `any` where the underlying object is a Python-native type such as +a list: + ```rust # use pyo3::prelude::*; -# use pyo3::types::PyList; -# let gil = Python::acquire_gil(); -# let py = gil.python(); +# use pyo3::{Py, Python, PyAny, PyResult, types::PyList}; +# Python::with_gil(|py| -> PyResult<()> { let obj: &PyAny = PyList::empty(py); -// Convert to &ConcreteType using PyAny::downcast -let _: &PyList = obj.downcast().unwrap(); +// To &PyList with PyAny::downcast +let _: &PyList = obj.downcast()?; -// Convert to Py (aka PyObject) using .into() +// To Py (aka PyObject) with .into() let _: Py = obj.into(); -// Convert to Py using PyAny::extract -let _: Py = obj.extract().unwrap(); +// To Py with PyAny::extract +let _: Py = obj.extract()?; +# Ok(()) +# }).unwrap(); ``` +For a `&PyAny` object reference `any` where the underlying object is a `#[pyclass]`: + +```rust +# use pyo3::prelude::*; +# use pyo3::{Py, Python, PyAny, PyResult, types::PyList}; +# #[pyclass] #[derive(Clone)] struct MyClass { } +# Python::with_gil(|py| -> PyResult<()> { +let obj: &PyAny = Py::new(py, MyClass { })?.into_ref(py); + +// To &PyCell with PyAny::downcast +let _: &PyCell = obj.downcast()?; + +// To Py (aka PyObject) with .into() +let _: Py = obj.into(); + +// To Py with PyAny::extract +let _: Py = obj.extract()?; + +// To MyClass with PyAny::extract, if MyClass: Clone +let _: MyClass = obj.extract()?; + +// To PyRef or PyRefMut with PyAny::extract +let _: PyRef = obj.extract()?; +let _: PyRefMut = obj.extract()?; +# Ok(()) +# }).unwrap(); +``` ### `PyTuple`, `PyDict`, and many more @@ -99,29 +130,30 @@ To see all Python types exposed by `PyO3` you should consult the ```rust # use pyo3::prelude::*; # use pyo3::types::PyList; -# let gil = Python::acquire_gil(); -# let py = gil.python(); +# Python::with_gil(|py| -> PyResult<()> { let list = PyList::empty(py); -// Can use methods from PyAny on all Python types due to Deref implementation -let _ = list.repr(); +// Use methods from PyAny on all Python types with Deref implementation +let _ = list.repr()?; -// Rust will convert &PyList etc. to &PyAny automatically due to Deref implementation +// To &PyAny automatically with Deref implementation let _: &PyAny = list; -// For more explicit &PyAny conversion, use .as_ref() +// To &PyAny explicitly with .as_ref() let _: &PyAny = list.as_ref(); -// To convert to Py use .into() or Py::from() +// To Py with .into() or Py::from() let _: Py = list.into(); -// To convert to PyObject use .into() or .to_object(py) +// To PyObject with .into() or .to_object(py) let _: PyObject = list.into(); +# Ok(()) +# }).unwrap(); ``` -### `Py` +### `Py` and `PyObject` -**Represents:** a GIL independent reference to a Python object. This can be a Python native type +**Represents:** a GIL-independent reference to a Python object. This can be a Python native type (like `PyTuple`), or a `pyclass` type implemented in Rust. The most commonly-used variant, `Py`, is also known as `PyObject`. @@ -133,6 +165,8 @@ Can be cloned using Python reference counts with `.clone()`. **Conversions:** +For a `Py`, the conversions are as below: + ```rust # use pyo3::prelude::*; # use pyo3::types::PyList; @@ -140,20 +174,47 @@ Can be cloned using Python reference counts with `.clone()`. # let py = gil.python(); let list: Py = PyList::empty(py).into(); -// Access to the native type using Py::as_ref(py) or Py::into_ref(py) -// (For #[pyclass] types T, these will return &PyCell) - -// Py::as_ref() borrows the object +// To &PyList with Py::as_ref() (borrows from the Py) let _: &PyList = list.as_ref(py); -# let list_clone = list.clone(); // Just so that the .into() example for PyObject compiles. -// Py::into_ref() moves the reference into pyo3's "object storage"; useful for making APIs -// which return gil-bound references. +# let list_clone = list.clone(); // Because `.into_ref()` will consume `list`. +// To &PyList with Py::into_ref() (moves the pointer into PyO3's object storage) let _: &PyList = list.into_ref(py); # let list = list_clone; -// Convert to PyObject with .into() -let _: PyObject = list.into(); +// To Py (aka PyObject) with .into() +let _: Py = list.into(); +``` + +For a `#[pyclass] struct MyClass`, the conversions for `Py` are below: + +```rust +# use pyo3::prelude::*; +# let gil = Python::acquire_gil(); +# let py = gil.python(); +# #[pyclass] struct MyClass { } +# Python::with_gil(|py| -> PyResult<()> { +let my_class: Py = Py::new(py, MyClass { })?; + +// To &PyCell with Py::as_ref() (borrows from the Py) +let _: &PyCell = my_class.as_ref(py); + +# let my_class_clone = my_class.clone(); // Because `.into_ref()` will consume `my_class`. +// To &PyCell with Py::into_ref() (moves the pointer into PyO3's object storage) +let _: &PyCell = my_class.into_ref(py); + +# let my_class = my_class_clone.clone(); +// To Py (aka PyObject) with .into_py(py) +let _: Py = my_class.into_py(py); + +# let my_class = my_class_clone; +// To PyRef with Py::borrow or Py::try_borrow +let _: PyRef = my_class.try_borrow(py)?; + +// To PyRefMut with Py::borrow_mut or Py::try_borrow_mut +let _: PyRefMut = my_class.try_borrow_mut(py)?; +# Ok(()) +# }).unwrap(); ``` ### `PyCell` @@ -171,33 +232,46 @@ so it also exposes all of the methods on `PyAny`. **Conversions:** +`PyCell` can be used to access `&T` and `&mut T` via `PyRef` and `PyRefMut` respectively. + ```rust # use pyo3::prelude::*; # use pyo3::types::PyList; # #[pyclass] struct MyClass { } -# let gil = Python::acquire_gil(); -# let py = gil.python(); -let cell: &PyCell = PyCell::new(py, MyClass { }).unwrap(); +# Python::with_gil(|py| -> PyResult<()> { +let cell: &PyCell = PyCell::new(py, MyClass { })?; -// Obtain PyRef with .try_borrow() -let pr: PyRef = cell.try_borrow().unwrap(); -# drop(pr); +// To PyRef with .borrow() or .try_borrow() +let py_ref: PyRef = cell.try_borrow()?; +let _: &MyClass = &*py_ref; +# drop(py_ref); -// Obtain PyRefMut with .try_borrow_mut() -let prm: PyRefMut = cell.try_borrow_mut().unwrap(); -# drop(prm); +// To PyRefMut with .borrow_mut() or .try_borrow_mut() +let mut py_ref_mut: PyRefMut = cell.try_borrow_mut()?; +let _: &mut MyClass = &mut *py_ref_mut; +# Ok(()) +# }).unwrap(); +``` -// Can use methods from PyAny on PyCell due to Deref implementation -let _ = cell.repr(); +`PyCell` can also be accessed like a Python-native type. -// Rust will convert &PyCell to &PyAny automatically due to Deref implementation +```rust +# use pyo3::prelude::*; +# use pyo3::types::PyList; +# #[pyclass] struct MyClass { } +# Python::with_gil(|py| -> PyResult<()> { +let cell: &PyCell = PyCell::new(py, MyClass { })?; + +// Use methods from PyAny on PyCell with Deref implementation +let _ = cell.repr()?; + +// To &PyAny automatically with Deref implementation let _: &PyAny = cell; -// For more explicit &PyAny conversion, use .as_ref() -let any: &PyAny = cell.as_ref(); - -// To obtain a PyCell from PyAny, use PyAny::downcast -let _: &PyCell = any.downcast().unwrap(); +// To &PyAny explicitly with .as_ref() +let _: &PyAny = cell.as_ref(); +# Ok(()) +# }).unwrap(); ``` ### `PyRef` and `PyRefMut` diff --git a/pyo3-derive-backend/src/method.rs b/pyo3-derive-backend/src/method.rs index 0ba69007..5697df95 100644 --- a/pyo3-derive-backend/src/method.rs +++ b/pyo3-derive-backend/src/method.rs @@ -17,7 +17,6 @@ pub struct FnArg<'a> { pub ty: &'a syn::Type, pub optional: Option<&'a syn::Type>, pub py: bool, - pub reference: bool, } #[derive(Clone, PartialEq, Debug, Copy, Eq)] @@ -214,17 +213,13 @@ impl<'a> FnSpec<'a> { } }; - let py = crate::utils::if_type_is_python(ty); - - let opt = check_ty_optional(ty); arguments.push(FnArg { name: ident, by_ref, mutability, ty, - optional: opt, - py, - reference: is_ref(name, ty), + optional: utils::option_type_argument(ty), + py: utils::is_python(ty), }); } } @@ -323,55 +318,6 @@ impl<'a> FnSpec<'a> { } } -pub fn is_ref(name: &syn::Ident, ty: &syn::Type) -> bool { - match ty { - syn::Type::Reference(_) => return true, - syn::Type::Path(syn::TypePath { ref path, .. }) => { - if let Some(segment) = path.segments.last() { - if "Option" == segment.ident.to_string().as_str() { - match segment.arguments { - syn::PathArguments::AngleBracketed(ref params) => { - if params.args.len() != 1 { - panic!("argument type is not supported by python method: {:?} ({:?}) {:?}", - name, - ty, - path); - } - let last = ¶ms.args[params.args.len() - 1]; - if let syn::GenericArgument::Type(syn::Type::Reference(_)) = last { - return true; - } - } - _ => { - panic!( - "argument type is not supported by python method: {:?} ({:?}) {:?}", - name, ty, path - ); - } - } - } - } - } - _ => (), - } - false -} - -pub(crate) fn check_ty_optional(ty: &syn::Type) -> Option<&syn::Type> { - let path = match ty { - syn::Type::Path(syn::TypePath { ref path, .. }) => path, - _ => return None, - }; - let seg = path.segments.last().filter(|s| s.ident == "Option")?; - match seg.arguments { - syn::PathArguments::AngleBracketed(ref params) => match params.args.first() { - Some(syn::GenericArgument::Type(ref ty)) => Some(ty), - _ => None, - }, - _ => None, - } -} - #[derive(Clone, PartialEq, Debug)] struct MethodAttributes { ty: Option, diff --git a/pyo3-derive-backend/src/module.rs b/pyo3-derive-backend/src/module.rs index 264f8345..a706100e 100644 --- a/pyo3-derive-backend/src/module.rs +++ b/pyo3-derive-backend/src/module.rs @@ -2,7 +2,6 @@ //! Code generation for the function that initializes a python module and adds classes and function. use crate::method; -use crate::pyfunction; use crate::pyfunction::PyFunctionAttr; use crate::pymethod; use crate::pymethod::get_arg_names; @@ -26,8 +25,6 @@ pub fn py_init(fnname: &Ident, name: &Ident, doc: syn::LitStr) -> TokenStream { const NAME: &'static str = concat!(stringify!(#name), "\0"); static MODULE_DEF: ModuleDef = unsafe { ModuleDef::new(NAME) }; - let pool = pyo3::GILPool::new(); - let py = pool.python(); pyo3::callback_body!(_py, { MODULE_DEF.make_module(#doc, #fnname) }) } } @@ -40,14 +37,14 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> { for stmt in func.block.stmts.iter_mut() { if let syn::Stmt::Item(syn::Item::Fn(ref mut func)) = stmt { if let Some((module_name, python_name, pyfn_attrs)) = - extract_pyfn_attrs(&mut func.attrs) + extract_pyfn_attrs(&mut func.attrs)? { let function_to_python = add_fn_to_module(func, python_name, pyfn_attrs)?; let function_wrapper_ident = function_wrapper_ident(&func.sig.ident); let item: syn::ItemFn = syn::parse_quote! { fn block_wrapper() { #function_to_python - #module_name.add_wrapped(&#function_wrapper_ident)?; + #module_name.add_function(&#function_wrapper_ident)?; } }; stmts.extend(item.block.stmts.into_iter()); @@ -61,33 +58,30 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> { } /// Transforms a rust fn arg parsed with syn into a method::FnArg -fn wrap_fn_argument<'a>(cap: &'a syn::PatType, name: &'a Ident) -> syn::Result> { +fn wrap_fn_argument<'a>(cap: &'a syn::PatType) -> syn::Result> { let (mutability, by_ref, ident) = match *cap.pat { syn::Pat::Ident(ref patid) => (&patid.mutability, &patid.by_ref, &patid.ident), _ => return Err(syn::Error::new_spanned(&cap.pat, "Unsupported argument")), }; - let py = crate::utils::if_type_is_python(&cap.ty); - let opt = method::check_ty_optional(&cap.ty); Ok(method::FnArg { name: ident, mutability, by_ref, ty: &cap.ty, - optional: opt, - py, - reference: method::is_ref(&name, &cap.ty), + optional: utils::option_type_argument(&cap.ty), + py: utils::is_python(&cap.ty), }) } /// Extracts the data from the #[pyfn(...)] attribute of a function fn extract_pyfn_attrs( attrs: &mut Vec, -) -> Option<(syn::Path, Ident, Vec)> { +) -> syn::Result> { let mut new_attrs = Vec::new(); let mut fnname = None; let mut modname = None; - let mut fn_attrs = Vec::new(); + let mut fn_attrs = PyFunctionAttr::default(); for attr in attrs.iter() { match attr.parse_meta() { @@ -99,23 +93,34 @@ fn extract_pyfn_attrs( syn::NestedMeta::Meta(syn::Meta::Path(ref path)) => { modname = Some(path.clone()) } - _ => panic!("The first parameter of pyfn must be a MetaItem"), + _ => { + return Err(syn::Error::new_spanned( + &meta[0], + "The first parameter of pyfn must be a MetaItem", + )) + } } // read Python function name match meta[1] { syn::NestedMeta::Lit(syn::Lit::Str(ref lits)) => { fnname = Some(syn::Ident::new(&lits.value(), lits.span())); } - _ => panic!("The second parameter of pyfn must be a Literal"), + _ => { + return Err(syn::Error::new_spanned( + &meta[1], + "The second parameter of pyfn must be a Literal", + )) + } } // Read additional arguments if list.nested.len() >= 3 { - fn_attrs = PyFunctionAttr::from_meta(&meta[2..meta.len()]) - .unwrap() - .arguments; + fn_attrs = PyFunctionAttr::from_meta(&meta[2..meta.len()])?; } } else { - panic!("can not parse 'pyfn' params {:?}", attr); + return Err(syn::Error::new_spanned( + attr, + format!("can not parse 'pyfn' params {:?}", attr), + )); } } _ => new_attrs.push(attr.clone()), @@ -123,7 +128,10 @@ fn extract_pyfn_attrs( } *attrs = new_attrs; - Some((modname?, fnname?, fn_attrs)) + match (modname, fnname) { + (Some(modname), Some(fnname)) => Ok(Some((modname, fnname, fn_attrs))), + _ => Ok(None), + } } /// Coordinates the naming of a the add-function-to-python-module function @@ -137,11 +145,11 @@ fn function_wrapper_ident(name: &Ident) -> Ident { pub fn add_fn_to_module( func: &mut syn::ItemFn, python_name: Ident, - pyfn_attrs: Vec, + pyfn_attrs: PyFunctionAttr, ) -> syn::Result { let mut arguments = Vec::new(); - for input in func.sig.inputs.iter() { + for (i, input) in func.sig.inputs.iter().enumerate() { match input { syn::FnArg::Receiver(_) => { return Err(syn::Error::new_spanned( @@ -150,7 +158,27 @@ pub fn add_fn_to_module( )) } syn::FnArg::Typed(ref cap) => { - arguments.push(wrap_fn_argument(cap, &func.sig.ident)?); + if pyfn_attrs.pass_module && i == 0 { + if let syn::Type::Reference(tyref) = cap.ty.as_ref() { + if let syn::Type::Path(typath) = tyref.elem.as_ref() { + if typath + .path + .segments + .last() + .map(|seg| seg.ident == "PyModule") + .unwrap_or(false) + { + continue; + } + } + } + return Err(syn::Error::new_spanned( + cap, + "Expected &PyModule as first argument with `pass_module`.", + )); + } else { + arguments.push(wrap_fn_argument(cap)?); + } } } } @@ -166,7 +194,7 @@ pub fn add_fn_to_module( tp: method::FnType::FnStatic, name: &function_wrapper_ident, python_name, - attrs: pyfn_attrs, + attrs: pyfn_attrs.arguments, args: arguments, output: ty, doc, @@ -176,10 +204,14 @@ pub fn add_fn_to_module( let python_name = &spec.python_name; - let wrapper = function_c_wrapper(&func.sig.ident, &spec); + let wrapper = function_c_wrapper(&func.sig.ident, &spec, pyfn_attrs.pass_module); Ok(quote! { - fn #function_wrapper_ident(py: pyo3::Python) -> pyo3::PyObject { + fn #function_wrapper_ident<'a>( + args: impl Into> + ) -> pyo3::PyResult { + let arg = args.into(); + let (py, maybe_module) = arg.into_py_and_maybe_module(); #wrapper let _def = pyo3::class::PyMethodDef { @@ -189,28 +221,49 @@ pub fn add_fn_to_module( ml_doc: #doc, }; + let (mod_ptr, name) = if let Some(m) = maybe_module { + let mod_ptr = ::as_ptr(m); + let name = m.name()?; + let name = <&str as pyo3::conversion::IntoPy>::into_py(name, py); + (mod_ptr, ::as_ptr(&name)) + } else { + (std::ptr::null_mut(), std::ptr::null_mut()) + }; + let function = unsafe { pyo3::PyObject::from_owned_ptr( py, - pyo3::ffi::PyCFunction_New( + pyo3::ffi::PyCFunction_NewEx( Box::into_raw(Box::new(_def.as_method_def())), - ::std::ptr::null_mut() + mod_ptr, + name ) ) }; - function + Ok(function) } }) } /// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords) -fn function_c_wrapper(name: &Ident, spec: &method::FnSpec<'_>) -> TokenStream { +fn function_c_wrapper(name: &Ident, spec: &method::FnSpec<'_>, pass_module: bool) -> TokenStream { let names: Vec = get_arg_names(&spec); - let cb = quote! { - #name(#(#names),*) + let cb; + let slf_module; + if pass_module { + cb = quote! { + #name(_slf, #(#names),*) + }; + slf_module = Some(quote! { + let _slf = _py.from_borrowed_ptr::(_slf); + }); + } else { + cb = quote! { + #name(#(#names),*) + }; + slf_module = None; }; - let body = pymethod::impl_arg_params(spec, None, cb); quote! { @@ -221,6 +274,7 @@ fn function_c_wrapper(name: &Ident, spec: &method::FnSpec<'_>) -> TokenStream { { const _LOCATION: &'static str = concat!(stringify!(#name), "()"); pyo3::callback_body!(_py, { + #slf_module let _args = _py.from_borrowed_ptr::(_args); let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); diff --git a/pyo3-derive-backend/src/proto_method.rs b/pyo3-derive-backend/src/proto_method.rs index a4c49071..77a1e291 100644 --- a/pyo3-derive-backend/src/proto_method.rs +++ b/pyo3-derive-backend/src/proto_method.rs @@ -73,7 +73,7 @@ pub(crate) fn impl_method_proto( cls: &syn::Type, sig: &mut syn::Signature, meth: &MethodProto, -) -> TokenStream { +) -> syn::Result { let ret_ty = match &sig.output { syn::ReturnType::Default => quote! { () }, syn::ReturnType::Type(_, ty) => { @@ -83,7 +83,7 @@ pub(crate) fn impl_method_proto( } }; - match *meth { + let toks = match *meth { MethodProto::Free { proto, .. } => { let p: syn::Path = syn::parse_str(proto).unwrap(); quote! { @@ -109,7 +109,7 @@ pub(crate) fn impl_method_proto( let p: syn::Path = syn::parse_str(proto).unwrap(); let slf_name = syn::Ident::new(arg, Span::call_site()); - let slf_ty = get_arg_ty(sig, 0); + let slf_ty = get_arg_ty(sig, 0)?; let tmp: syn::ItemFn = syn::parse_quote! { fn test(&self) -> <#cls as #p<'p>>::Result {} }; @@ -132,12 +132,12 @@ pub(crate) fn impl_method_proto( MethodProto::Binary { name, arg, proto } => { if sig.inputs.len() <= 1 { println!("Not enough arguments for {}", name); - return TokenStream::new(); + return Ok(TokenStream::new()); } let p: syn::Path = syn::parse_str(proto).unwrap(); let arg_name = syn::Ident::new(arg, Span::call_site()); - let arg_ty = get_arg_ty(sig, 1); + let arg_ty = get_arg_ty(sig, 1)?; let tmp = extract_decl(syn::parse_quote! { fn test(&self,arg: <#cls as #p<'p>>::#arg_name)-> <#cls as #p<'p>>::Result {} @@ -147,7 +147,7 @@ pub(crate) fn impl_method_proto( fn test(&self, arg: Option<<#cls as #p<'p>>::#arg_name>) -> <#cls as #p<'p>>::Result {} }); - modify_arg_ty(sig, 1, &tmp, &tmp2); + modify_arg_ty(sig, 1, &tmp, &tmp2)?; modify_self_ty(sig); quote! { @@ -165,13 +165,13 @@ pub(crate) fn impl_method_proto( } => { if sig.inputs.len() <= 1 { print_err(format!("Not enough arguments {}", name), quote!(sig)); - return TokenStream::new(); + return Ok(TokenStream::new()); } let p: syn::Path = syn::parse_str(proto).unwrap(); let arg1_name = syn::Ident::new(arg1, Span::call_site()); - let arg1_ty = get_arg_ty(sig, 0); + let arg1_ty = get_arg_ty(sig, 0)?; let arg2_name = syn::Ident::new(arg2, Span::call_site()); - let arg2_ty = get_arg_ty(sig, 1); + let arg2_ty = get_arg_ty(sig, 1)?; // rewrite ty let tmp = extract_decl(syn::parse_quote! {fn test( @@ -182,8 +182,8 @@ pub(crate) fn impl_method_proto( arg1: Option<<#cls as #p<'p>>::#arg1_name>, arg2: Option<<#cls as #p<'p>>::#arg2_name>) -> <#cls as #p<'p>>::Result {}}); - modify_arg_ty(sig, 0, &tmp, &tmp2); - modify_arg_ty(sig, 1, &tmp, &tmp2); + modify_arg_ty(sig, 0, &tmp, &tmp2)?; + modify_arg_ty(sig, 1, &tmp, &tmp2)?; quote! { impl<'p> #p<'p> for #cls { @@ -201,13 +201,13 @@ pub(crate) fn impl_method_proto( } => { if sig.inputs.len() <= 2 { print_err(format!("Not enough arguments {}", name), quote!(sig)); - return TokenStream::new(); + return Ok(TokenStream::new()); } let p: syn::Path = syn::parse_str(proto).unwrap(); let arg1_name = syn::Ident::new(arg1, Span::call_site()); - let arg1_ty = get_arg_ty(sig, 1); + let arg1_ty = get_arg_ty(sig, 1)?; let arg2_name = syn::Ident::new(arg2, Span::call_site()); - let arg2_ty = get_arg_ty(sig, 2); + let arg2_ty = get_arg_ty(sig, 2)?; // rewrite ty let tmp = extract_decl(syn::parse_quote! {fn test( @@ -220,8 +220,8 @@ pub(crate) fn impl_method_proto( arg1: Option<<#cls as #p<'p>>::#arg1_name>, arg2: Option<<#cls as #p<'p>>::#arg2_name>) -> <#cls as #p<'p>>::Result {}}); - modify_arg_ty(sig, 1, &tmp, &tmp2); - modify_arg_ty(sig, 2, &tmp, &tmp2); + modify_arg_ty(sig, 1, &tmp, &tmp2)?; + modify_arg_ty(sig, 2, &tmp, &tmp2)?; modify_self_ty(sig); quote! { @@ -241,15 +241,15 @@ pub(crate) fn impl_method_proto( } => { if sig.inputs.len() <= 2 { print_err(format!("Not enough arguments {}", name), quote!(sig)); - return TokenStream::new(); + return Ok(TokenStream::new()); } let p: syn::Path = syn::parse_str(proto).unwrap(); let arg1_name = syn::Ident::new(arg1, Span::call_site()); - let arg1_ty = get_arg_ty(sig, 0); + let arg1_ty = get_arg_ty(sig, 0)?; let arg2_name = syn::Ident::new(arg2, Span::call_site()); - let arg2_ty = get_arg_ty(sig, 1); + let arg2_ty = get_arg_ty(sig, 1)?; let arg3_name = syn::Ident::new(arg3, Span::call_site()); - let arg3_ty = get_arg_ty(sig, 2); + let arg3_ty = get_arg_ty(sig, 2)?; // rewrite ty let tmp = extract_decl(syn::parse_quote! {fn test( @@ -262,9 +262,9 @@ pub(crate) fn impl_method_proto( arg2: Option<<#cls as #p<'p>>::#arg2_name>, arg3: Option<<#cls as #p<'p>>::#arg3_name>) -> <#cls as #p<'p>>::Result {}}); - modify_arg_ty(sig, 0, &tmp, &tmp2); - modify_arg_ty(sig, 1, &tmp, &tmp2); - modify_arg_ty(sig, 2, &tmp, &tmp2); + modify_arg_ty(sig, 0, &tmp, &tmp2)?; + modify_arg_ty(sig, 1, &tmp, &tmp2)?; + modify_arg_ty(sig, 2, &tmp, &tmp2)?; quote! { impl<'p> #p<'p> for #cls { @@ -284,15 +284,15 @@ pub(crate) fn impl_method_proto( } => { if sig.inputs.len() <= 3 { print_err(format!("Not enough arguments {}", name), quote!(sig)); - return TokenStream::new(); + return Ok(TokenStream::new()); } let p: syn::Path = syn::parse_str(proto).unwrap(); let arg1_name = syn::Ident::new(arg1, Span::call_site()); - let arg1_ty = get_arg_ty(sig, 1); + let arg1_ty = get_arg_ty(sig, 1)?; let arg2_name = syn::Ident::new(arg2, Span::call_site()); - let arg2_ty = get_arg_ty(sig, 2); + let arg2_ty = get_arg_ty(sig, 2)?; let arg3_name = syn::Ident::new(arg3, Span::call_site()); - let arg3_ty = get_arg_ty(sig, 3); + let arg3_ty = get_arg_ty(sig, 3)?; // rewrite ty let tmp = extract_decl(syn::parse_quote! {fn test( @@ -307,9 +307,9 @@ pub(crate) fn impl_method_proto( arg2: Option<<#cls as #p<'p>>::#arg2_name>, arg3: Option<<#cls as #p<'p>>::#arg3_name>) -> <#cls as #p<'p>>::Result {}}); - modify_arg_ty(sig, 1, &tmp, &tmp2); - modify_arg_ty(sig, 2, &tmp, &tmp2); - modify_arg_ty(sig, 3, &tmp, &tmp2); + modify_arg_ty(sig, 1, &tmp, &tmp2)?; + modify_arg_ty(sig, 2, &tmp, &tmp2)?; + modify_arg_ty(sig, 3, &tmp, &tmp2)?; modify_self_ty(sig); quote! { @@ -321,11 +321,12 @@ pub(crate) fn impl_method_proto( } } } - } + }; + Ok(toks) } /// Some hacks for arguments: get `T` from `Option` and insert lifetime -fn get_arg_ty(sig: &syn::Signature, idx: usize) -> syn::Type { +fn get_arg_ty(sig: &syn::Signature, idx: usize) -> syn::Result { fn get_option_ty(path: &syn::Path) -> Option { let seg = path.segments.last()?; if seg.ident == "Option" { @@ -344,10 +345,15 @@ fn get_arg_ty(sig: &syn::Signature, idx: usize) -> syn::Type { syn::Type::Path(ref ty) => get_option_ty(&ty.path).unwrap_or_else(|| *cap.ty.clone()), _ => *cap.ty.clone(), }, - ty => panic!("Unsupported argument type: {:?}", ty), + ty => { + return Err(syn::Error::new_spanned( + ty, + format!("Unsupported argument type: {:?}", ty), + )) + } }; insert_lifetime(&mut ty); - ty + Ok(ty) } /// Insert lifetime `'p` to `PyRef` or references (e.g., `&PyType`). @@ -395,26 +401,27 @@ fn modify_arg_ty( idx: usize, decl1: &syn::Signature, decl2: &syn::Signature, -) { +) -> syn::Result<()> { let arg = sig.inputs[idx].clone(); match arg { syn::FnArg::Typed(ref cap) => match *cap.ty { syn::Type::Path(ref typath) => { let seg = typath.path.segments.last().unwrap().clone(); if seg.ident == "Option" { - sig.inputs[idx] = fix_name(&cap.pat, &decl2.inputs[idx]); + sig.inputs[idx] = fix_name(&cap.pat, &decl2.inputs[idx])?; } else { - sig.inputs[idx] = fix_name(&cap.pat, &decl1.inputs[idx]); + sig.inputs[idx] = fix_name(&cap.pat, &decl1.inputs[idx])?; } } _ => { - sig.inputs[idx] = fix_name(&cap.pat, &decl1.inputs[idx]); + sig.inputs[idx] = fix_name(&cap.pat, &decl1.inputs[idx])?; } }, - _ => panic!("not supported"), + _ => return Err(syn::Error::new_spanned(arg, "not supported")), } sig.output = decl1.output.clone(); + Ok(()) } fn modify_self_ty(sig: &mut syn::Signature) { @@ -426,15 +433,15 @@ fn modify_self_ty(sig: &mut syn::Signature) { } } -fn fix_name(pat: &syn::Pat, arg: &syn::FnArg) -> syn::FnArg { +fn fix_name(pat: &syn::Pat, arg: &syn::FnArg) -> syn::Result { if let syn::FnArg::Typed(ref cap) = arg { - syn::FnArg::Typed(syn::PatType { + Ok(syn::FnArg::Typed(syn::PatType { attrs: cap.attrs.clone(), pat: Box::new(pat.clone()), colon_token: cap.colon_token, ty: cap.ty.clone(), - }) + })) } else { - panic!("func.rs::296") + Err(syn::Error::new_spanned(arg, "Expected a typed argument")) } } diff --git a/pyo3-derive-backend/src/pyfunction.rs b/pyo3-derive-backend/src/pyfunction.rs index 96ef584a..80ac1cf3 100644 --- a/pyo3-derive-backend/src/pyfunction.rs +++ b/pyo3-derive-backend/src/pyfunction.rs @@ -24,6 +24,7 @@ pub struct PyFunctionAttr { has_kw: bool, has_varargs: bool, has_kwargs: bool, + pub pass_module: bool, } impl syn::parse::Parse for PyFunctionAttr { @@ -45,6 +46,9 @@ impl PyFunctionAttr { pub fn add_item(&mut self, item: &NestedMeta) -> syn::Result<()> { match item { + NestedMeta::Meta(syn::Meta::Path(ref ident)) if ident.is_ident("pass_module") => { + self.pass_module = true; + } NestedMeta::Meta(syn::Meta::Path(ref ident)) => self.add_work(item, ident)?, NestedMeta::Meta(syn::Meta::NameValue(ref nv)) => { self.add_name_value(item, nv)?; @@ -204,7 +208,7 @@ pub fn parse_name_attribute(attrs: &mut Vec) -> syn::Result syn::Result { let python_name = parse_name_attribute(&mut ast.attrs)?.unwrap_or_else(|| ast.sig.ident.unraw()); - add_fn_to_module(ast, python_name, args.arguments) + add_fn_to_module(ast, python_name, args) } #[cfg(test)] diff --git a/pyo3-derive-backend/src/pymethod.rs b/pyo3-derive-backend/src/pymethod.rs index fbda2efb..62ced447 100644 --- a/pyo3-derive-backend/src/pymethod.rs +++ b/pyo3-derive-backend/src/pymethod.rs @@ -488,58 +488,48 @@ fn impl_arg_param( let arg_value = quote!(output[#option_pos]); *option_pos += 1; - return if let Some(ty) = arg.optional.as_ref() { - let default = if let Some(d) = spec.default_value(name).filter(|d| d.to_string() != "None") - { - quote! { Some(#d) } - } else { - quote! { None } - }; - if let syn::Type::Reference(tref) = ty { - let (tref, mut_) = preprocess_tref(tref, self_); - // To support Rustc 1.39.0, we don't use as_deref here... - let tmp_as_deref = if mut_.is_some() { - quote! { _tmp.as_mut().map(std::ops::DerefMut::deref_mut) } - } else { - quote! { _tmp.as_ref().map(std::ops::Deref::deref) } - }; + let default = match (spec.default_value(name), arg.optional.is_some()) { + (Some(default), true) if default.to_string() != "None" => quote! { Some(#default) }, + (Some(default), _) => quote! { #default }, + (None, true) => quote! { None }, + (None, false) => quote! { panic!("Failed to extract required method argument") }, + }; + + return if let syn::Type::Reference(tref) = arg.optional.as_ref().unwrap_or(&ty) { + let (tref, mut_) = preprocess_tref(tref, self_); + let (target_ty, borrow_tmp) = if arg.optional.is_some() { // Get Option<&T> from Option> - quote! { - let #mut_ _tmp = match #arg_value { - Some(_obj) => { - _obj.extract::::Target>>()? - }, - None => #default, - }; - let #arg_name = #tmp_as_deref; - } + ( + quote! { Option<<#tref as pyo3::derive_utils::ExtractExt>::Target> }, + // To support Rustc 1.39.0, we don't use as_deref here... + if mut_.is_some() { + quote! { _tmp.as_mut().map(std::ops::DerefMut::deref_mut) } + } else { + quote! { _tmp.as_ref().map(std::ops::Deref::deref) } + }, + ) } else { - quote! { - let #arg_name = match #arg_value { - Some(_obj) => _obj.extract()?, - None => #default, - }; - } + // Get &T from PyRef + ( + quote! { <#tref as pyo3::derive_utils::ExtractExt>::Target }, + quote! { &#mut_ *_tmp }, + ) + }; + + quote! { + let #mut_ _tmp: #target_ty = match #arg_value { + Some(_obj) => _obj.extract()?, + None => #default, + }; + let #arg_name = #borrow_tmp; } - } else if let Some(default) = spec.default_value(name) { + } else { quote! { let #arg_name = match #arg_value { Some(_obj) => _obj.extract()?, None => #default, }; } - } else if let syn::Type::Reference(tref) = arg.ty { - let (tref, mut_) = preprocess_tref(tref, self_); - // Get &T from PyRef - quote! { - let #mut_ _tmp: <#tref as pyo3::derive_utils::ExtractExt>::Target - = #arg_value.unwrap().extract()?; - let #arg_name = &#mut_ *_tmp; - } - } else { - quote! { - let #arg_name = #arg_value.unwrap().extract()?; - } }; /// Replace `Self`, remove lifetime and get mutability from the type @@ -739,7 +729,11 @@ pub(crate) fn impl_py_getter_def( /// Split an argument of pyo3::Python from the front of the arg list, if present fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&FnArg>, &[FnArg]) { - if args.get(0).map(|py| utils::if_type_is_python(&py.ty)) == Some(true) { + if args + .get(0) + .map(|py| utils::is_python(&py.ty)) + .unwrap_or(false) + { (Some(&args[0]), &args[1..]) } else { (None, args) diff --git a/pyo3-derive-backend/src/pyproto.rs b/pyo3-derive-backend/src/pyproto.rs index b1a19281..c9bebbe0 100644 --- a/pyo3-derive-backend/src/pyproto.rs +++ b/pyo3-derive-backend/src/pyproto.rs @@ -67,7 +67,7 @@ fn impl_proto_impl( if let syn::ImplItem::Method(ref mut met) = iimpl { // impl Py~Protocol<'p> { type = ... } if let Some(m) = proto.get_proto(&met.sig.ident) { - impl_method_proto(ty, &mut met.sig, m).to_tokens(&mut trait_impls); + impl_method_proto(ty, &mut met.sig, m)?.to_tokens(&mut trait_impls); // Insert the method to the HashSet method_names.insert(met.sig.ident.to_string()); } @@ -152,6 +152,7 @@ fn slot_initialization( Span::call_site(), ); Ok(quote! { + #[allow(non_snake_case)] #[pyo3::ctor::ctor] fn #init() { let mut table = #table::default(); diff --git a/pyo3-derive-backend/src/utils.rs b/pyo3-derive-backend/src/utils.rs index 6bd78570..1bfc1a05 100644 --- a/pyo3-derive-backend/src/utils.rs +++ b/pyo3-derive-backend/src/utils.rs @@ -8,7 +8,7 @@ pub fn print_err(msg: String, t: TokenStream) { } /// Check if the given type `ty` is `pyo3::Python`. -pub fn if_type_is_python(ty: &syn::Type) -> bool { +pub fn is_python(ty: &syn::Type) -> bool { match ty { syn::Type::Path(ref typath) => typath .path @@ -20,6 +20,19 @@ pub fn if_type_is_python(ty: &syn::Type) -> bool { } } +/// If `ty` is Option, return `Some(T)`, else None. +pub fn option_type_argument(ty: &syn::Type) -> Option<&syn::Type> { + if let syn::Type::Path(syn::TypePath { path, .. }) = ty { + let seg = path.segments.last().filter(|s| s.ident == "Option")?; + if let syn::PathArguments::AngleBracketed(params) = &seg.arguments { + if let syn::GenericArgument::Type(ty) = params.args.first()? { + return Some(ty); + } + } + } + None +} + pub fn is_text_signature_attr(attr: &syn::Attribute) -> bool { attr.path.is_ident("text_signature") } diff --git a/src/derive_utils.rs b/src/derive_utils.rs index 2a736d7e..cef90fd1 100644 --- a/src/derive_utils.rs +++ b/src/derive_utils.rs @@ -207,3 +207,34 @@ where >>::try_from(cell) } } + +/// Enum to abstract over the arguments of Python function wrappers. +#[doc(hidden)] +pub enum WrapPyFunctionArguments<'a> { + Python(Python<'a>), + PyModule(&'a PyModule), +} + +impl<'a> WrapPyFunctionArguments<'a> { + pub fn into_py_and_maybe_module(self) -> (Python<'a>, Option<&'a PyModule>) { + match self { + WrapPyFunctionArguments::Python(py) => (py, None), + WrapPyFunctionArguments::PyModule(module) => { + let py = module.py(); + (py, Some(module)) + } + } + } +} + +impl<'a> From> for WrapPyFunctionArguments<'a> { + fn from(py: Python<'a>) -> WrapPyFunctionArguments<'a> { + WrapPyFunctionArguments::Python(py) + } +} + +impl<'a> From<&'a PyModule> for WrapPyFunctionArguments<'a> { + fn from(module: &'a PyModule) -> WrapPyFunctionArguments<'a> { + WrapPyFunctionArguments::PyModule(module) + } +} diff --git a/src/ffi/import.rs b/src/ffi/import.rs index fdf431f1..037c54a4 100644 --- a/src/ffi/import.rs +++ b/src/ffi/import.rs @@ -63,6 +63,7 @@ pub unsafe fn PyImport_ImportModuleEx( extern "C" { pub fn PyImport_GetImporter(path: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyImport_Import")] pub fn PyImport_Import(name: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyImport_ReloadModule")] pub fn PyImport_ReloadModule(m: *mut PyObject) -> *mut PyObject; diff --git a/src/ffi/methodobject.rs b/src/ffi/methodobject.rs index 7c3872c5..921cca84 100644 --- a/src/ffi/methodobject.rs +++ b/src/ffi/methodobject.rs @@ -1,6 +1,6 @@ use crate::ffi::object::{PyObject, PyTypeObject, Py_TYPE}; +use std::mem; use std::os::raw::{c_char, c_int}; -use std::{mem, ptr}; #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { @@ -96,19 +96,16 @@ impl Default for PyMethodDef { } } -#[inline] -pub unsafe fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject { - #[cfg_attr(PyPy, link_name = "PyPyCFunction_NewEx")] - PyCFunction_NewEx(ml, slf, ptr::null_mut()) -} - extern "C" { #[cfg_attr(PyPy, link_name = "PyPyCFunction_NewEx")] pub fn PyCFunction_NewEx( - arg1: *mut PyMethodDef, - arg2: *mut PyObject, - arg3: *mut PyObject, + ml: *mut PyMethodDef, + slf: *mut PyObject, + module: *mut PyObject, ) -> *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyCFunction_NewEx")] + pub fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject; } /* Flag passed to newmethodobject */ diff --git a/src/ffi/moduleobject.rs b/src/ffi/moduleobject.rs index 005705b5..4a403683 100644 --- a/src/ffi/moduleobject.rs +++ b/src/ffi/moduleobject.rs @@ -25,6 +25,7 @@ extern "C" { pub fn PyModule_New(name: *const c_char) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyModule_GetDict")] pub fn PyModule_GetDict(arg1: *mut PyObject) -> *mut PyObject; + #[cfg(not(PyPy))] pub fn PyModule_GetNameObject(arg1: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyModule_GetName")] pub fn PyModule_GetName(arg1: *mut PyObject) -> *const c_char; diff --git a/src/lib.rs b/src/lib.rs index 10f3e768..4c2313e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,7 +71,7 @@ //! #[pymodule] //! /// A Python module implemented in Rust. //! fn string_sum(py: Python, m: &PyModule) -> PyResult<()> { -//! m.add_wrapped(wrap_pyfunction!(sum_as_string))?; +//! m.add_function(wrap_pyfunction!(sum_as_string))?; //! //! Ok(()) //! } diff --git a/src/python.rs b/src/python.rs index 901a426b..9b71ebf0 100644 --- a/src/python.rs +++ b/src/python.rs @@ -134,7 +134,7 @@ impl<'p> Python<'p> { /// let gil = Python::acquire_gil(); /// let py = gil.python(); /// let m = PyModule::new(py, "pcount").unwrap(); - /// m.add_wrapped(wrap_pyfunction!(parallel_count)).unwrap(); + /// m.add_function(wrap_pyfunction!(parallel_count)).unwrap(); /// let locals = [("pcount", m)].into_py_dict(py); /// py.run(r#" /// s = ["Flow", "my", "tears", "the", "Policeman", "Said"] @@ -280,13 +280,11 @@ impl<'p> Python<'p> { .unwrap_or_else(|| ffi::PyModule_GetDict(mptr)); let locals = locals.map(AsPyPointer::as_ptr).unwrap_or(globals); - let res_ptr = ffi::PyRun_StringFlags( - code.as_ptr(), - start, - globals, - locals, - ::std::ptr::null_mut(), - ); + let code_obj = ffi::Py_CompileString(code.as_ptr(), "\0".as_ptr() as _, start); + if code_obj.is_null() { + return Err(PyErr::fetch(self)); + } + let res_ptr = ffi::PyEval_EvalCode(code_obj, globals, locals); self.from_owned_ptr_or_err(res_ptr) } diff --git a/src/type_object.rs b/src/type_object.rs index 6e8e7900..d923d10c 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -224,17 +224,16 @@ fn initialize_tp_dict( type_object: *mut ffi::PyObject, items: Vec<(&'static str, PyObject)>, ) -> PyResult<()> { - use std::ffi::CString; - // We hold the GIL: the dictionary update can be considered atomic from // the POV of other threads. for (key, val) in items { - let ret = unsafe { - ffi::PyObject_SetAttrString(type_object, CString::new(key)?.as_ptr(), val.into_ptr()) - }; - if ret < 0 { - return Err(PyErr::fetch(py)); - } + crate::types::with_tmp_string(py, key, |key| { + let ret = unsafe { ffi::PyObject_SetAttr(type_object, key, val.into_ptr()) }; + if ret < 0 { + return Err(PyErr::fetch(py)); + } + Ok(()) + })?; } Ok(()) } diff --git a/src/types/floatob.rs b/src/types/floatob.rs index 583fc9ea..0b0b5341 100644 --- a/src/types/floatob.rs +++ b/src/types/floatob.rs @@ -81,6 +81,7 @@ impl<'source> FromPyObject<'source> for f32 { #[cfg(test)] mod test { + #[cfg(not(Py_LIMITED_API))] use crate::ffi::PyFloat_AS_DOUBLE; use crate::{AsPyPointer, Python, ToPyObject}; @@ -103,6 +104,7 @@ mod test { num_to_py_object_and_back!(to_from_f32, f32, f32); num_to_py_object_and_back!(int_to_float, i32, f64); + #[cfg(not(Py_LIMITED_API))] #[test] fn test_as_double_macro() { use assert_approx_eq::assert_approx_eq; diff --git a/src/types/mod.rs b/src/types/mod.rs index 32bbfe8e..abdcdd00 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -21,6 +21,7 @@ pub use self::num::PyLong as PyInt; pub use self::sequence::PySequence; pub use self::set::{PyFrozenSet, PySet}; pub use self::slice::{PySlice, PySliceIndices}; +pub(crate) use self::string::with_tmp_string; pub use self::string::{PyString, PyString as PyUnicode}; pub use self::tuple::PyTuple; pub use self::typeobject::PyType; diff --git a/src/types/module.rs b/src/types/module.rs index b345fcab..b77a6fa4 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -2,6 +2,7 @@ // // based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython +use crate::callback::IntoPyCallbackOutput; use crate::err::{PyErr, PyResult}; use crate::exceptions; use crate::ffi; @@ -24,14 +25,16 @@ pyobject_native_var_type!(PyModule, ffi::PyModule_Type, ffi::PyModule_Check); impl PyModule { /// Creates a new module object with the `__name__` attribute set to name. pub fn new<'p>(py: Python<'p>, name: &str) -> PyResult<&'p PyModule> { + // Could use PyModule_NewObject, but it doesn't exist on PyPy. let name = CString::new(name)?; unsafe { py.from_owned_ptr_or_err(ffi::PyModule_New(name.as_ptr())) } } /// Imports the Python module with the specified name. pub fn import<'p>(py: Python<'p>, name: &str) -> PyResult<&'p PyModule> { - let name = CString::new(name)?; - unsafe { py.from_owned_ptr_or_err(ffi::PyImport_ImportModule(name.as_ptr())) } + crate::types::with_tmp_string(py, name, |name| unsafe { + py.from_owned_ptr_or_err(ffi::PyImport_Import(name)) + }) } /// Loads the Python code specified into a new module. @@ -184,21 +187,102 @@ impl PyModule { /// Use this together with the`#[pyfunction]` and [wrap_pyfunction!] or `#[pymodule]` and /// [wrap_pymodule!]. /// - /// ```rust,ignore - /// m.add_wrapped(wrap_pyfunction!(double)); - /// m.add_wrapped(wrap_pymodule!(utils)); + /// ```rust + /// use pyo3::prelude::*; + /// #[pymodule] + /// fn utils(_py: Python, _module: &PyModule) -> PyResult<()> { + /// Ok(()) + /// } + /// + /// #[pyfunction] + /// fn double(x: usize) -> usize { + /// x * 2 + /// } + /// #[pymodule] + /// fn top_level(_py: Python, module: &PyModule) -> PyResult<()> { + /// module.add_wrapped(pyo3::wrap_pymodule!(utils))?; + /// module.add_wrapped(pyo3::wrap_pyfunction!(double)) + /// } /// ``` /// /// You can also add a function with a custom name using [add](PyModule::add): /// /// ```rust,ignore - /// m.add("also_double", wrap_pyfunction!(double)(py)); + /// m.add("also_double", wrap_pyfunction!(double)(m)?)?; /// ``` - pub fn add_wrapped(&self, wrapper: &impl Fn(Python) -> PyObject) -> PyResult<()> { - let function = wrapper(self.py()); - let name = function - .getattr(self.py(), "__name__") - .expect("A function or module must have a __name__"); - self.add(name.extract(self.py()).unwrap(), function) + /// + /// **This function will be deprecated in the next release. Please use the specific + /// [add_function] and [add_submodule] functions instead.** + pub fn add_wrapped<'a, T>(&'a self, wrapper: &impl Fn(Python<'a>) -> T) -> PyResult<()> + where + T: IntoPyCallbackOutput, + { + let py = self.py(); + let function = wrapper(py).convert(py)?; + let name = function.getattr(py, "__name__")?; + let name = name.extract(py)?; + self.add(name, function) + } + + /// Add a submodule to a module. + /// + /// Use this together with `#[pymodule]` and [wrap_pymodule!]. + /// + /// ```rust + /// use pyo3::prelude::*; + /// + /// fn init_utils(module: &PyModule) -> PyResult<()> { + /// module.add("super_useful_constant", "important") + /// } + /// #[pymodule] + /// fn top_level(py: Python, module: &PyModule) -> PyResult<()> { + /// let utils = PyModule::new(py, "utils")?; + /// init_utils(utils)?; + /// module.add_submodule(utils) + /// } + /// ``` + pub fn add_submodule(&self, module: &PyModule) -> PyResult<()> { + let name = module.name()?; + self.add(name, module) + } + + /// Add a function to a module. + /// + /// Use this together with the`#[pyfunction]` and [wrap_pyfunction!]. + /// + /// ```rust + /// use pyo3::prelude::*; + /// #[pyfunction] + /// fn double(x: usize) -> usize { + /// x * 2 + /// } + /// #[pymodule] + /// fn double_mod(_py: Python, module: &PyModule) -> PyResult<()> { + /// module.add_function(pyo3::wrap_pyfunction!(double)) + /// } + /// ``` + /// + /// You can also add a function with a custom name using [add](PyModule::add): + /// + /// ```rust + /// use pyo3::prelude::*; + /// #[pyfunction] + /// fn double(x: usize) -> usize { + /// x * 2 + /// } + /// #[pymodule] + /// fn double_mod(_py: Python, module: &PyModule) -> PyResult<()> { + /// module.add("also_double", pyo3::wrap_pyfunction!(double)(module)?) + /// } + /// ``` + pub fn add_function<'a>( + &'a self, + wrapper: &impl Fn(&'a Self) -> PyResult, + ) -> PyResult<()> { + let py = self.py(); + let function = wrapper(self)?; + let name = function.getattr(py, "__name__")?; + let name = name.extract(py)?; + self.add(name, function) } } diff --git a/src/types/string.rs b/src/types/string.rs index 722055cb..8e313edc 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -78,6 +78,24 @@ impl PyString { } } +/// Convenience for calling Python APIs with a temporary string +/// object created from a given Rust string. +pub(crate) fn with_tmp_string(py: Python, s: &str, f: F) -> PyResult +where + F: FnOnce(*mut ffi::PyObject) -> PyResult, +{ + unsafe { + let s_obj = + ffi::PyUnicode_FromStringAndSize(s.as_ptr() as *const _, s.len() as ffi::Py_ssize_t); + if s_obj.is_null() { + return Err(PyErr::fetch(py)); + } + let ret = f(s_obj); + ffi::Py_DECREF(s_obj); + ret + } +} + /// Converts a Rust `str` to a Python object. /// See `PyString::new` for details on the conversion. impl ToPyObject for str { diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index a48c05fc..e458e35b 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -14,7 +14,7 @@ fn test_pybytes_bytes_conversion() { let gil = Python::acquire_gil(); let py = gil.python(); - let f = wrap_pyfunction!(bytes_pybytes_conversion)(py); + let f = wrap_pyfunction!(bytes_pybytes_conversion)(py).unwrap(); py_assert!(py, f, "f(b'Hello World') == b'Hello World'"); } @@ -28,7 +28,7 @@ fn test_pybytes_vec_conversion() { let gil = Python::acquire_gil(); let py = gil.python(); - let f = wrap_pyfunction!(bytes_vec_conversion)(py); + let f = wrap_pyfunction!(bytes_vec_conversion)(py).unwrap(); py_assert!(py, f, "f(b'Hello World') == b'Hello World'"); } @@ -37,6 +37,6 @@ fn test_bytearray_vec_conversion() { let gil = Python::acquire_gil(); let py = gil.python(); - let f = wrap_pyfunction!(bytes_vec_conversion)(py); + let f = wrap_pyfunction!(bytes_vec_conversion)(py).unwrap(); py_assert!(py, f, "f(bytearray(b'Hello World')) == b'Hello World'"); } diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index 653a80d5..5d02b8e8 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -4,6 +4,7 @@ fn test_compile_errors() { let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/invalid_frompy_derive.rs"); t.compile_fail("tests/ui/invalid_macro_args.rs"); + t.compile_fail("tests/ui/invalid_need_module_arg_position.rs"); t.compile_fail("tests/ui/invalid_property_args.rs"); t.compile_fail("tests/ui/invalid_pyclass_args.rs"); t.compile_fail("tests/ui/invalid_pymethod_names.rs"); diff --git a/tests/test_exceptions.rs b/tests/test_exceptions.rs index 3726dfb7..d232f29d 100644 --- a/tests/test_exceptions.rs +++ b/tests/test_exceptions.rs @@ -19,7 +19,7 @@ fn fail_to_open_file() -> PyResult<()> { fn test_filenotfounderror() { let gil = Python::acquire_gil(); let py = gil.python(); - let fail_to_open_file = wrap_pyfunction!(fail_to_open_file)(py); + let fail_to_open_file = wrap_pyfunction!(fail_to_open_file)(py).unwrap(); py_run!( py, @@ -64,7 +64,7 @@ fn call_fail_with_custom_error() -> PyResult<()> { fn test_custom_error() { let gil = Python::acquire_gil(); let py = gil.python(); - let call_fail_with_custom_error = wrap_pyfunction!(call_fail_with_custom_error)(py); + let call_fail_with_custom_error = wrap_pyfunction!(call_fail_with_custom_error)(py).unwrap(); py_run!( py, diff --git a/tests/test_module.rs b/tests/test_module.rs index 0746fb8f..7c278bdc 100644 --- a/tests/test_module.rs +++ b/tests/test_module.rs @@ -1,6 +1,6 @@ use pyo3::prelude::*; -use pyo3::types::{IntoPyDict, PyTuple}; +use pyo3::types::{IntoPyDict, PyDict, PyTuple}; mod common; @@ -35,7 +35,7 @@ fn double(x: usize) -> usize { /// This module is implemented in Rust. #[pymodule] -fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> { +fn module_with_functions(_py: Python, m: &PyModule) -> PyResult<()> { use pyo3::wrap_pyfunction; #[pyfn(m, "sum_as_string")] @@ -49,6 +49,11 @@ fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> { Ok(42) } + #[pyfn(m, "with_module", pass_module)] + fn with_module(module: &PyModule) -> PyResult<&str> { + module.name() + } + #[pyfn(m, "double_value")] fn double_value(v: &ValueClass) -> usize { v.value * 2 @@ -60,8 +65,8 @@ fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> { m.add("foo", "bar").unwrap(); - m.add_wrapped(wrap_pyfunction!(double)).unwrap(); - m.add("also_double", wrap_pyfunction!(double)(py)).unwrap(); + m.add_function(wrap_pyfunction!(double)).unwrap(); + m.add("also_double", wrap_pyfunction!(double)(m)?).unwrap(); Ok(()) } @@ -97,6 +102,7 @@ fn test_module_with_functions() { run("assert module_with_functions.also_double(3) == 6"); run("assert module_with_functions.also_double.__doc__ == 'Doubles the given value'"); run("assert module_with_functions.double_value(module_with_functions.ValueClass(1)) == 2"); + run("assert module_with_functions.with_module() == 'module_with_functions'"); } #[pymodule(other_name)] @@ -157,7 +163,7 @@ fn r#move() -> usize { fn raw_ident_module(_py: Python, module: &PyModule) -> PyResult<()> { use pyo3::wrap_pyfunction; - module.add_wrapped(wrap_pyfunction!(r#move)) + module.add_function(wrap_pyfunction!(r#move)) } #[test] @@ -182,7 +188,7 @@ fn custom_named_fn() -> usize { fn foobar_module(_py: Python, m: &PyModule) -> PyResult<()> { use pyo3::wrap_pyfunction; - m.add_wrapped(wrap_pyfunction!(custom_named_fn))?; + m.add_function(wrap_pyfunction!(custom_named_fn))?; m.dict().set_item("yay", "me")?; Ok(()) } @@ -212,11 +218,18 @@ fn subfunction() -> String { "Subfunction".to_string() } -#[pymodule] -fn submodule(_py: Python, module: &PyModule) -> PyResult<()> { +fn submodule(module: &PyModule) -> PyResult<()> { use pyo3::wrap_pyfunction; - module.add_wrapped(wrap_pyfunction!(subfunction))?; + module.add_function(wrap_pyfunction!(subfunction))?; + Ok(()) +} + +#[pymodule] +fn submodule_with_init_fn(_py: Python, module: &PyModule) -> PyResult<()> { + use pyo3::wrap_pyfunction; + + module.add_function(wrap_pyfunction!(subfunction))?; Ok(()) } @@ -226,11 +239,16 @@ fn superfunction() -> String { } #[pymodule] -fn supermodule(_py: Python, module: &PyModule) -> PyResult<()> { - use pyo3::{wrap_pyfunction, wrap_pymodule}; +fn supermodule(py: Python, module: &PyModule) -> PyResult<()> { + use pyo3::wrap_pyfunction; - module.add_wrapped(wrap_pyfunction!(superfunction))?; - module.add_wrapped(wrap_pymodule!(submodule))?; + module.add_function(wrap_pyfunction!(superfunction))?; + let module_to_add = PyModule::new(py, "submodule")?; + submodule(module_to_add)?; + module.add_submodule(module_to_add)?; + let module_to_add = PyModule::new(py, "submodule_with_init_fn")?; + submodule_with_init_fn(py, module_to_add)?; + module.add_submodule(module_to_add)?; Ok(()) } @@ -252,6 +270,11 @@ fn test_module_nesting() { supermodule, "supermodule.submodule.subfunction() == 'Subfunction'" ); + py_assert!( + py, + supermodule, + "supermodule.submodule_with_init_fn.subfunction() == 'Subfunction'" + ); } // Test that argument parsing specification works for pyfunctions @@ -268,7 +291,7 @@ fn vararg_module(_py: Python, m: &PyModule) -> PyResult<()> { ext_vararg_fn(py, a, vararg) } - m.add_wrapped(pyo3::wrap_pyfunction!(ext_vararg_fn)) + m.add_function(pyo3::wrap_pyfunction!(ext_vararg_fn)) .unwrap(); Ok(()) } @@ -305,3 +328,82 @@ fn test_module_with_constant() { py_assert!(py, m, "isinstance(m.ANON, m.AnonClass)"); }); } + +#[pyfunction(pass_module)] +fn pyfunction_with_module(module: &PyModule) -> PyResult<&str> { + module.name() +} + +#[pyfunction(pass_module)] +fn pyfunction_with_module_and_py<'a>( + module: &'a PyModule, + _python: Python<'a>, +) -> PyResult<&'a str> { + module.name() +} + +#[pyfunction(pass_module)] +fn pyfunction_with_module_and_arg(module: &PyModule, string: String) -> PyResult<(&str, String)> { + module.name().map(|s| (s, string)) +} + +#[pyfunction(pass_module, string = "\"foo\"")] +fn pyfunction_with_module_and_default_arg<'a>( + module: &'a PyModule, + string: &str, +) -> PyResult<(&'a str, String)> { + module.name().map(|s| (s, string.into())) +} + +#[pyfunction(pass_module, args = "*", kwargs = "**")] +fn pyfunction_with_module_and_args_kwargs<'a>( + module: &'a PyModule, + args: &PyTuple, + kwargs: Option<&PyDict>, +) -> PyResult<(&'a str, usize, Option)> { + module + .name() + .map(|s| (s, args.len(), kwargs.map(|d| d.len()))) +} + +#[pymodule] +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_and_py))?; + m.add_function(pyo3::wrap_pyfunction!(pyfunction_with_module_and_arg))?; + m.add_function(pyo3::wrap_pyfunction!( + pyfunction_with_module_and_default_arg + ))?; + m.add_function(pyo3::wrap_pyfunction!( + pyfunction_with_module_and_args_kwargs + )) +} + +#[test] +fn test_module_functions_with_module() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let m = pyo3::wrap_pymodule!(module_with_functions_with_module)(py); + py_assert!( + py, + m, + "m.pyfunction_with_module() == 'module_with_functions_with_module'" + ); + py_assert!( + py, + m, + "m.pyfunction_with_module_and_py() == 'module_with_functions_with_module'" + ); + py_assert!( + py, + m, + "m.pyfunction_with_module_and_default_arg() \ + == ('module_with_functions_with_module', 'foo')" + ); + py_assert!( + py, + m, + "m.pyfunction_with_module_and_args_kwargs(1, x=1, y=2) \ + == ('module_with_functions_with_module', 1, 2)" + ); +} diff --git a/tests/test_pyfunction.rs b/tests/test_pyfunction.rs index e8e95bdf..0d8500a3 100644 --- a/tests/test_pyfunction.rs +++ b/tests/test_pyfunction.rs @@ -14,7 +14,7 @@ fn test_optional_bool() { // Regression test for issue #932 let gil = Python::acquire_gil(); let py = gil.python(); - let f = wrap_pyfunction!(optional_bool)(py); + let f = wrap_pyfunction!(optional_bool)(py).unwrap(); py_assert!(py, f, "f() == 'Some(true)'"); py_assert!(py, f, "f(True) == 'Some(true)'"); @@ -36,7 +36,7 @@ fn buffer_inplace_add(py: Python, x: PyBuffer, y: PyBuffer) { fn test_buffer_add() { let gil = Python::acquire_gil(); let py = gil.python(); - let f = wrap_pyfunction!(buffer_inplace_add)(py); + let f = wrap_pyfunction!(buffer_inplace_add)(py).unwrap(); py_expect_exception!( py, diff --git a/tests/test_string.rs b/tests/test_string.rs index 6236484a..38d375b5 100644 --- a/tests/test_string.rs +++ b/tests/test_string.rs @@ -14,7 +14,7 @@ fn test_unicode_encode_error() { let gil = Python::acquire_gil(); let py = gil.python(); - let take_str = wrap_pyfunction!(take_str)(py); + let take_str = wrap_pyfunction!(take_str)(py).unwrap(); py_run!( py, take_str, diff --git a/tests/test_text_signature.rs b/tests/test_text_signature.rs index 617676c8..929bb2eb 100644 --- a/tests/test_text_signature.rs +++ b/tests/test_text_signature.rs @@ -110,7 +110,7 @@ fn test_function() { let gil = Python::acquire_gil(); let py = gil.python(); - let f = wrap_pyfunction!(my_function)(py); + let f = wrap_pyfunction!(my_function)(py).unwrap(); py_assert!(py, f, "f.__text_signature__ == '(a, b=None, *, c=42)'"); } diff --git a/tests/test_various.rs b/tests/test_various.rs index 87270c39..b2de718b 100644 --- a/tests/test_various.rs +++ b/tests/test_various.rs @@ -59,7 +59,7 @@ fn return_custom_class() { assert_eq!(get_zero().unwrap().value, 0); // Using from python - let get_zero = wrap_pyfunction!(get_zero)(py); + let get_zero = wrap_pyfunction!(get_zero)(py).unwrap(); py_assert!(py, get_zero, "get_zero().value == 0"); } @@ -206,5 +206,5 @@ fn result_conversion_function() -> Result<(), MyError> { fn test_result_conversion() { let gil = Python::acquire_gil(); let py = gil.python(); - wrap_pyfunction!(result_conversion_function)(py); + wrap_pyfunction!(result_conversion_function)(py).unwrap(); } diff --git a/tests/ui/invalid_need_module_arg_position.rs b/tests/ui/invalid_need_module_arg_position.rs new file mode 100644 index 00000000..607b2127 --- /dev/null +++ b/tests/ui/invalid_need_module_arg_position.rs @@ -0,0 +1,12 @@ +use pyo3::prelude::*; + +#[pymodule] +fn module(_py: Python, m: &PyModule) -> PyResult<()> { + #[pyfn(m, "with_module", pass_module)] + fn fail(string: &str, module: &PyModule) -> PyResult<&str> { + module.name() + } + Ok(()) +} + +fn main(){} \ No newline at end of file diff --git a/tests/ui/invalid_need_module_arg_position.stderr b/tests/ui/invalid_need_module_arg_position.stderr new file mode 100644 index 00000000..0fd00964 --- /dev/null +++ b/tests/ui/invalid_need_module_arg_position.stderr @@ -0,0 +1,5 @@ +error: Expected &PyModule as first argument with `pass_module`. + --> $DIR/invalid_need_module_arg_position.rs:6:13 + | +6 | fn fail(string: &str, module: &PyModule) -> PyResult<&str> { + | ^^^^^^^^^^^^