##// END OF EJS Templates
rust-pyo3: dagop submodule implementation...
rust-pyo3: dagop submodule implementation This is the first demonstration that the passing of objects through the Python interpreter from `rustext` to `pyo3_rustext` actually works. In the Python tests, we conflate the presence of the `pyo3_rustext` package with that of `rustext`, as we do not plan to support building one and not the other (we hope to convert fully to PyO3 soon). The skipping is actually done by the base test class. The implementation of `rank` is as trivial as it was in `hg-cpython`, yet it required to be able to convert exceptions from `vcsgraph`.

File last commit:

r53308:c2480ac4 default
r53310:20fe0bf9 default
Show More
convert_cpython.rs
173 lines | 7.2 KiB | application/rls-services+xml | RustLexer
//! This module takes care of all conversions involving `rusthg` (hg-cpython)
//! objects in the PyO3 call context.
//!
//! For source code clarity, we only import (`use`) [`cpython`] traits and not
//! any of its data objects. We are instead using full qualifiers, such as
//! `cpython::PyObject`, and believe that the added heaviness is an acceptatble
//! price to pay to avoid confusion.
//!
//! Also it, is customary in [`cpython`] to label the GIL lifetime as `'p`,
//! whereas it is `'py` in PyO3 context. We keep both these conventions in
//! the arguments side of function signatures when they are not simply elided.
use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use cpython::ObjectProtocol;
use cpython::PythonObject;
use lazy_static::lazy_static;
use rusthg::revlog::{InnerRevlog, PySharedIndex};
/// Force cpython's GIL handle with the appropriate lifetime
///
/// In `pyo3`, the fact that we have the GIL is expressed by the lifetime of
/// the incoming [`Bound`] smart pointer. We therefore simply instantiate
/// the `cpython` handle and coerce its lifetime by the function signature.
///
/// Reacquiring the GIL is also a possible alternative, as the CPython
/// documentation explicitely states that "recursive calls are allowed"
/// (we interpret that as saying that acquiring the GIL within a thread that
/// already has it works) *as long as it is properly released*
/// reference:
/// <https://docs.python.org/3.8/c-api/init.html#c.PyGILState_Ensure>
pub(crate) fn cpython_handle<'py, T>(
_bound: &Bound<'py, T>,
) -> cpython::Python<'py> {
// safety: this is safe because the returned object has the 'py lifetime
unsafe { cpython::Python::assume_gil_acquired() }
}
/// Force PyO3 GIL handle from cpython's.
///
/// Very similar to [`cpython_handle`]
pub fn pyo3_handle(_py: cpython::Python<'_>) -> Python<'_> {
// safety: this is safe because the returned object has the same lifetime
// as the incoming object.
unsafe { Python::assume_gil_acquired() }
}
/// Convert a PyO3 [`PyObject`] into a [`cpython::PyObject`]
///
/// During this process, the reference count is increased, then decreased.
/// This means that the GIL (symbolized by the lifetime on the `obj`
/// argument) is needed.
///
/// We could make something perhaps more handy by simply stealing the
/// pointer, forgetting the incoming and then implement `From` with "newtype".
/// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps
/// not for the current endeavour.
pub(crate) fn to_cpython_py_object<'py>(
obj: &Bound<'py, PyAny>,
) -> (cpython::Python<'py>, cpython::PyObject) {
let py = cpython_handle(obj);
// public alias of the private cpython::fii::PyObject (!)
let raw = obj.as_ptr() as *mut python3_sys::PyObject;
// both pyo3 and rust-cpython will decrement the refcount on drop.
// If we use from_owned_ptr, that's a segfault.
(py, unsafe { cpython::PyObject::from_borrowed_ptr(py, raw) })
}
/// Convert a [`cpython::PyObject`] into a PyO3 [`PyObject`]
///
/// During this process, the reference count is increased, then decreased.
/// This means that the GIL (symbolized by the PyO3 [`Python`] handle is
/// needed.
///
/// We could make something perhaps more handy by simply stealing the
/// pointer, forgetting the incoming and then implement `From` with "newtype".
/// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps
/// not for the current endeavour.
pub(crate) fn from_cpython_py_object(
py: Python<'_>,
obj: cpython::PyObject,
) -> PyObject {
let raw = obj.as_ptr() as *mut pyo3::ffi::PyObject;
unsafe { Py::from_borrowed_ptr(py, raw) }
}
/// Convert [`cpython::PyErr`] into [`pyo3::PyErr`]
///
/// The exception class remains the same as the original exception,
/// hence if it is also defined in another dylib based on `cpython` crate,
/// it will need to be converted to be downcasted in this crate.
pub(crate) fn from_cpython_pyerr(
py: cpython::Python<'_>,
mut e: cpython::PyErr,
) -> PyErr {
let pyo3_py = pyo3_handle(py);
let cpython_exc_obj = e.instance(py);
let pyo3_exc_obj = from_cpython_py_object(pyo3_py, cpython_exc_obj);
PyErr::from_value(pyo3_exc_obj.into_bound(pyo3_py))
}
/// Retrieve the PyType for objects from the `mercurial.rustext` crate.
fn retrieve_cpython_py_type(
submodule_name: &str,
type_name: &str,
) -> cpython::PyResult<cpython::PyType> {
let guard = cpython::Python::acquire_gil();
let py = guard.python();
let module = py.import(&format!("mercurial.rustext.{submodule_name}"))?;
module.get(py, type_name)?.extract::<cpython::PyType>(py)
}
lazy_static! {
static ref INNER_REVLOG_PY_TYPE: cpython::PyType = {
retrieve_cpython_py_type("revlog", "InnerRevlog")
.expect("Could not import InnerRevlog in Python")
};
}
/// Downcast [`InnerRevlog`], with the appropriate Python type checking.
///
/// The PyType object representing the `InnerRevlog` Python class is not the
/// the same in this dylib as it is in the `mercurial.rustext` module.
/// This is because the code created with the [`cpython::py_class!`]
/// macro is itself duplicated in both dylibs. In the case of this crate, this
/// happens by linking to the [`rusthg`] crate and provides the `InnerRevlog`
/// that is visible from this crate. The `InnerRevlog::get_type` associated
/// function turns out to return a `static mut` (look for `TYPE_OBJECT` in
/// `py_class_impl3.rs`), which obviously is different in both dylibs.
///
/// The consequence of that is that downcasting an `InnerRevlog` originally
/// from the `mecurial.rustext` module to our `InnerRevlog` cannot be done with
/// the usual `extract::<InnerRevlog>(py)`, as it would perform the type
/// checking with the `PyType` that is embedded in `mercurial.pyo3_rustext`.
/// We must check the `PyType` that is within `mercurial.rustext` instead.
/// This is what this function does.
fn extract_inner_revlog(
py: cpython::Python,
inner_revlog: cpython::PyObject,
) -> PyResult<InnerRevlog> {
if !(*INNER_REVLOG_PY_TYPE).is_instance(py, &inner_revlog) {
return Err(PyTypeError::new_err("Not an InnerRevlog instance"));
}
// Safety: this is safe because we checked the PyType already, with the
// value embedded in `mercurial.rustext`.
Ok(unsafe { InnerRevlog::unchecked_downcast_from(inner_revlog) })
}
/// This is similar to [`rusthg.py_rust_index_to_graph`], with difference in
/// how we retrieve the [`InnerRevlog`].
pub fn py_rust_index_to_graph(
py: cpython::Python,
index_proxy: cpython::PyObject,
) -> PyResult<cpython::UnsafePyLeaked<PySharedIndex>> {
let inner_revlog = extract_inner_revlog(
py,
index_proxy
.getattr(py, "inner")
.map_err(|e| from_cpython_pyerr(py, e))?,
)?;
let leaked = inner_revlog.pub_inner(py).leak_immutable();
// Safety: we don't leak the "faked" reference out of the `UnsafePyLeaked`
Ok(unsafe { leaked.map(py, |idx| PySharedIndex { inner: &idx.index }) })
}
pub(crate) fn proxy_index_extract<'py>(
index_proxy: &Bound<'py, PyAny>,
) -> PyResult<(cpython::Python<'py>, cpython::UnsafePyLeaked<PySharedIndex>)> {
let (py, idx_proxy) = to_cpython_py_object(index_proxy);
Ok((py, py_rust_index_to_graph(py, idx_proxy)?))
}