Skip to content

Commit 16fe583

Browse files
authored
Merge pull request #1143 from sebpuetz/pyfunction-modules
PyModule in #[pyfunction]
2 parents 05d86b7 + 64b06ea commit 16fe583

26 files changed

+435
-99
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1515
- 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/)
1616
- Allow other `Result` types when using `#[pyfunction]`. [#1106](https://github.com/PyO3/pyo3/issues/1106).
1717
- Add `#[derive(FromPyObject)]` macro for enums and structs. [#1065](https://github.com/PyO3/pyo3/pull/1065)
18+
- Add macro attribute to `#[pyfn]` and `#[pyfunction]` to pass the module of a Python function to the function
19+
body. [#1143](https://github.com/PyO3/pyo3/pull/1143)
20+
- Add `add_function()` and `add_submodule()` functions to `PyModule` [#1143](https://github.com/PyO3/pyo3/pull/1143)
1821

1922
### Changed
2023
- Exception types have been renamed from e.g. `RuntimeError` to `PyRuntimeError`, and are now only accessible by `&T` or `Py<T>` similar to other Python-native types. The old names continue to exist but are deprecated. [#1024](https://github.com/PyO3/pyo3/pull/1024)
@@ -30,6 +33,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
3033
- 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)
3134
- Change `#[pyproto]` to return NotImplemented for operators for which Python can try a reversed operation. #[1072](https://github.com/PyO3/pyo3/pull/1072)
3235
- `PyModule::add` now uses `IntoPy<PyObject>` instead of `ToPyObject`. #[1124](https://github.com/PyO3/pyo3/pull/1124)
36+
- Add nested modules as `&PyModule` instead of using the wrapper generated by `#[pymodule]`. [#1143](https://github.com/PyO3/pyo3/pull/1143)
3337

3438
### Removed
3539
- 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.
5054
- Link against libpython on android with `extension-module` set. [#1095](https://github.com/PyO3/pyo3/pull/1095)
5155
- Fix support for both `__add__` and `__radd__` in the `+` operator when both are defined in `PyNumberProtocol`
5256
(and similar for all other reversible operators). [#1107](https://github.com/PyO3/pyo3/pull/1107)
57+
- Associate Python functions with their module by passing the Module and Module name [#1143](https://github.com/PyO3/pyo3/pull/1143)
5358

5459
## [0.11.1] - 2020-06-30
5560
### Added

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
6767
/// A Python module implemented in Rust.
6868
#[pymodule]
6969
fn string_sum(py: Python, m: &PyModule) -> PyResult<()> {
70-
m.add_wrapped(wrap_pyfunction!(sum_as_string))?;
70+
m.add_function(wrap_pyfunction!(sum_as_string))?;
7171

7272
Ok(())
7373
}

examples/rustapi_module/src/datetime.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -215,29 +215,29 @@ impl TzClass {
215215

216216
#[pymodule]
217217
fn datetime(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
218-
m.add_wrapped(wrap_pyfunction!(make_date))?;
219-
m.add_wrapped(wrap_pyfunction!(get_date_tuple))?;
220-
m.add_wrapped(wrap_pyfunction!(date_from_timestamp))?;
221-
m.add_wrapped(wrap_pyfunction!(make_time))?;
222-
m.add_wrapped(wrap_pyfunction!(get_time_tuple))?;
223-
m.add_wrapped(wrap_pyfunction!(make_delta))?;
224-
m.add_wrapped(wrap_pyfunction!(get_delta_tuple))?;
225-
m.add_wrapped(wrap_pyfunction!(make_datetime))?;
226-
m.add_wrapped(wrap_pyfunction!(get_datetime_tuple))?;
227-
m.add_wrapped(wrap_pyfunction!(datetime_from_timestamp))?;
218+
m.add_function(wrap_pyfunction!(make_date))?;
219+
m.add_function(wrap_pyfunction!(get_date_tuple))?;
220+
m.add_function(wrap_pyfunction!(date_from_timestamp))?;
221+
m.add_function(wrap_pyfunction!(make_time))?;
222+
m.add_function(wrap_pyfunction!(get_time_tuple))?;
223+
m.add_function(wrap_pyfunction!(make_delta))?;
224+
m.add_function(wrap_pyfunction!(get_delta_tuple))?;
225+
m.add_function(wrap_pyfunction!(make_datetime))?;
226+
m.add_function(wrap_pyfunction!(get_datetime_tuple))?;
227+
m.add_function(wrap_pyfunction!(datetime_from_timestamp))?;
228228

229229
// Python 3.6+ functions
230230
#[cfg(Py_3_6)]
231231
{
232-
m.add_wrapped(wrap_pyfunction!(time_with_fold))?;
232+
m.add_function(wrap_pyfunction!(time_with_fold))?;
233233
#[cfg(not(PyPy))]
234234
{
235-
m.add_wrapped(wrap_pyfunction!(get_time_tuple_fold))?;
236-
m.add_wrapped(wrap_pyfunction!(get_datetime_tuple_fold))?;
235+
m.add_function(wrap_pyfunction!(get_time_tuple_fold))?;
236+
m.add_function(wrap_pyfunction!(get_datetime_tuple_fold))?;
237237
}
238238
}
239239

240-
m.add_wrapped(wrap_pyfunction!(issue_219))?;
240+
m.add_function(wrap_pyfunction!(issue_219))?;
241241
m.add_class::<TzClass>()?;
242242

243243
Ok(())

examples/rustapi_module/src/othermod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ fn double(x: i32) -> i32 {
3131

3232
#[pymodule]
3333
fn othermod(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
34-
m.add_wrapped(wrap_pyfunction!(double))?;
34+
m.add_function(wrap_pyfunction!(double))?;
3535

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

examples/word-count/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ fn count_line(line: &str, needle: &str) -> usize {
5656
#[pymodule]
5757
fn word_count(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
5858
m.add_wrapped(wrap_pyfunction!(search))?;
59-
m.add_wrapped(wrap_pyfunction!(search_sequential))?;
60-
m.add_wrapped(wrap_pyfunction!(search_sequential_allow_threads))?;
59+
m.add_function(wrap_pyfunction!(search_sequential))?;
60+
m.add_function(wrap_pyfunction!(search_sequential_allow_threads))?;
6161

6262
Ok(())
6363
}

guide/src/function.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fn double(x: usize) -> usize {
3636

3737
#[pymodule]
3838
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
39-
m.add_wrapped(wrap_pyfunction!(double)).unwrap();
39+
m.add_function(wrap_pyfunction!(double)).unwrap();
4040

4141
Ok(())
4242
}
@@ -65,7 +65,7 @@ fn num_kwds(kwds: Option<&PyDict>) -> usize {
6565

6666
#[pymodule]
6767
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
68-
m.add_wrapped(wrap_pyfunction!(num_kwds)).unwrap();
68+
m.add_function(wrap_pyfunction!(num_kwds)).unwrap();
6969
Ok(())
7070
}
7171

@@ -189,3 +189,47 @@ If you have a static function, you can expose it with `#[pyfunction]` and use [`
189189
[`PyAny::call1`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html#tymethod.call1
190190
[`PyObject`]: https://docs.rs/pyo3/latest/pyo3/type.PyObject.html
191191
[`wrap_pyfunction!`]: https://docs.rs/pyo3/latest/pyo3/macro.wrap_pyfunction.html
192+
193+
### Accessing the module of a function
194+
195+
It is possible to access the module of a `#[pyfunction]` and `#[pyfn]` in the
196+
function body by passing the `pass_module` argument to the attribute:
197+
198+
```rust
199+
use pyo3::wrap_pyfunction;
200+
use pyo3::prelude::*;
201+
202+
#[pyfunction(pass_module)]
203+
fn pyfunction_with_module(module: &PyModule) -> PyResult<&str> {
204+
module.name()
205+
}
206+
207+
#[pymodule]
208+
fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> {
209+
m.add_function(wrap_pyfunction!(pyfunction_with_module))
210+
}
211+
212+
# fn main() {}
213+
```
214+
215+
If `pass_module` is set, the first argument **must** be the `&PyModule`. It is then possible to use the module
216+
in the function body.
217+
218+
The same works for `#[pyfn]`:
219+
220+
```rust
221+
use pyo3::wrap_pyfunction;
222+
use pyo3::prelude::*;
223+
224+
#[pymodule]
225+
fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> {
226+
227+
#[pyfn(m, "module_name", pass_module)]
228+
fn module_name(module: &PyModule) -> PyResult<&str> {
229+
module.name()
230+
}
231+
Ok(())
232+
}
233+
234+
# fn main() {}
235+
```

guide/src/logging.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
3535
// A good place to install the Rust -> Python logger.
3636
pyo3_log::init();
3737

38-
m.add_wrapped(wrap_pyfunction!(log_something))?;
38+
m.add_function(wrap_pyfunction!(log_something))?;
3939
Ok(())
4040
}
4141
```

guide/src/module.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,22 @@ fn sum_as_string(a: i64, b: i64) -> String {
3232
# fn main() {}
3333
```
3434

35-
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.
35+
The `#[pymodule]` procedural macro attribute takes care of exporting the initialization function of your
36+
module to Python. It can take as an argument the name of your module, which must be the name of the `.so`
37+
or `.pyd` file; the default is the Rust function's name.
3638

37-
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:
39+
If the name of the module (the default being the function name) does not match the name of the `.so` or
40+
`.pyd` file, you will get an import error in Python with the following message:
3841
`ImportError: dynamic module does not define module export function (PyInit_name_of_your_module)`
3942

40-
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).
43+
To import the module, either copy the shared library as described in [the README](https://github.com/PyO3/pyo3)
44+
or use a tool, e.g. `maturin develop` with [maturin](https://github.com/PyO3/maturin) or
45+
`python setup.py develop` with [setuptools-rust](https://github.com/PyO3/setuptools-rust).
4146

4247
## Documentation
4348

44-
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.
49+
The [Rust doc comments](https://doc.rust-lang.org/stable/book/first-edition/comments.html) of the module
50+
initialization function will be applied automatically as the Python docstring of your module.
4551

4652
```python
4753
import rust2py
@@ -53,7 +59,8 @@ Which means that the above Python code will print `This module is implemented in
5359

5460
## Modules as objects
5561

56-
In Python, modules are first class objects. This means that you can store them as values or add them to dicts or other modules:
62+
In Python, modules are first class objects. This means that you can store them as values or add them to
63+
dicts or other modules:
5764

5865
```rust
5966
use pyo3::prelude::*;
@@ -65,15 +72,16 @@ fn subfunction() -> String {
6572
"Subfunction".to_string()
6673
}
6774

68-
#[pymodule]
69-
fn submodule(_py: Python, module: &PyModule) -> PyResult<()> {
70-
module.add_wrapped(wrap_pyfunction!(subfunction))?;
75+
fn init_submodule(module: &PyModule) -> PyResult<()> {
76+
module.add_function(wrap_pyfunction!(subfunction))?;
7177
Ok(())
7278
}
7379

7480
#[pymodule]
75-
fn supermodule(_py: Python, module: &PyModule) -> PyResult<()> {
76-
module.add_wrapped(wrap_pymodule!(submodule))?;
81+
fn supermodule(py: Python, module: &PyModule) -> PyResult<()> {
82+
let submod = PyModule::new(py, "submodule")?;
83+
init_submodule(submod)?;
84+
module.add_submodule(submod)?;
7785
Ok(())
7886
}
7987

@@ -86,3 +94,5 @@ fn supermodule(_py: Python, module: &PyModule) -> PyResult<()> {
8694
```
8795

8896
This way, you can create a module hierarchy within a single extension module.
97+
98+
It is not necessary to add `#[pymodule]` on nested modules, this is only required on the top-level module.

guide/src/trait_bounds.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ pub struct UserModel {
488488
#[pymodule]
489489
fn trait_exposure(_py: Python, m: &PyModule) -> PyResult<()> {
490490
m.add_class::<UserModel>()?;
491-
m.add_wrapped(wrap_pyfunction!(solve_wrapper))?;
491+
m.add_function(wrap_pyfunction!(solve_wrapper))?;
492492
Ok(())
493493
}
494494

0 commit comments

Comments
 (0)