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.
--- /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)
+}