Mercurial > public > mercurial-scm > hg
changeset 52407:c5128c541021
rust-pyo3: facility for submodule registration, using it for dagop
It turns out that PyO3 has the exact same problem that we encountered
long ago with rust-cpython. The only difference is that the value
of `__name__` is not within the `mercurial` package at this point of
registration.
We reimplement the solution (equivalent to the suggestions in PyO3
issue tracker anyway), this time in a generic module to limit the
amount of boilerplate in subsequent applications.
author | Georges Racinet <georges.racinet@cloudcrane.io> |
---|---|
date | Fri, 29 Nov 2024 23:47:37 +0100 |
parents | c28ba6fb3fae |
children | 20c0472b2ab7 |
files | rust/hg-pyo3/src/dagops.rs rust/hg-pyo3/src/lib.rs rust/hg-pyo3/src/util.rs |
diffstat | 3 files changed, 64 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hg-pyo3/src/dagops.rs Fri Nov 29 23:47:37 2024 +0100 @@ -0,0 +1,22 @@ +// dagops.rs +// +// Copyright 2024 Georges Racinet <georges.racinet@cloudcrane.io> +// +// This software may be used and distributed according to the terms of the +// GNU General Public License version 2 or any later version. + +//! Bindings for the `hg::dagops` module provided by the +//! `hg-core` package. +//! +//! From Python, this will be seen as `mercurial.pyo3-rustext.dagop` +use pyo3::prelude::*; + +use crate::util::new_submodule; + +pub fn init_module<'py>( + py: Python<'py>, + package: &str, +) -> PyResult<Bound<'py, PyModule>> { + let m = new_submodule(py, package, "dagop")?; + Ok(m) +}
--- a/rust/hg-pyo3/src/lib.rs Fri Nov 29 22:59:16 2024 +0100 +++ b/rust/hg-pyo3/src/lib.rs Fri Nov 29 23:47:37 2024 +0100 @@ -1,6 +1,19 @@ use pyo3::prelude::*; +mod dagops; +mod util; + #[pymodule] -fn pyo3_rustext(_py: Python<'_>, _m: &Bound<'_, PyModule>) -> PyResult<()> { +fn pyo3_rustext(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add( + "__doc__", + "Mercurial core concepts - Rust implementation exposed via PyO3", + )?; + // the module's __name__ is pyo3_rustext, not mercurial.pyo3_rustext + // (at least at this point). + let name: String = m.getattr("__name__")?.extract()?; + let dotted_name = format!("mercurial.{}", name); + + m.add_submodule(&dagops::init_module(py, &dotted_name)?)?; Ok(()) }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hg-pyo3/src/util.rs Fri Nov 29 23:47:37 2024 +0100 @@ -0,0 +1,28 @@ +use pyo3::prelude::*; +use pyo3::types::PyDict; +/// Create the module, with `__package__` given from parent +/// +/// According to PyO3 documentation, which links to +/// <https://github.com/PyO3/pyo3/issues/1517>, the same convoluted +/// write to sys.modules has to be made as with the `cpython` crate. +pub(crate) fn new_submodule<'py>( + py: Python<'py>, + package_name: &str, + name: &str, +) -> PyResult<Bound<'py, PyModule>> { + let dotted_name = &format!("{}.{}", package_name, name); + let m = PyModule::new(py, name)?; + m.add("__package__", package_name)?; + m.add("__doc__", "DAG operations - Rust implementation")?; + + let sys = PyModule::import(py, "sys")?; + // according to the doc, we could make a static PyString out of + // "modules" with the `intern!` macro, but this is used only at + // registration so it may not be worth the effort. + let sys_modules: Bound<'_, PyDict> = sys.getattr("modules")?.extract()?; + sys_modules.set_item(dotted_name, &m)?; + // Example C code (see pyexpat.c and import.c) will "give away the + // reference", but we won't because it will be consumed once the + // Rust PyObject is dropped. + Ok(m) +}