Mercurial > public > mercurial-scm > hg-stable
changeset 52440:a642c0a3860f
rust-pyo3: conversion helpers for Revision objects
Although the PyO3 API is indeed nicer and has derive macros, we still need
the collection helpers. The `Result` issue is not very relevant any more,
however the checking of incoming revisions for Python definitely is.
author | Georges Racinet <georges.racinet@cloudcrane.io> |
---|---|
date | Sat, 30 Nov 2024 20:30:18 +0100 |
parents | 20c0472b2ab7 |
children | 15011324a80b |
files | rust/Cargo.lock rust/hg-pyo3/Cargo.toml rust/hg-pyo3/src/lib.rs rust/hg-pyo3/src/revision.rs |
diffstat | 4 files changed, 82 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/rust/Cargo.lock Sat Nov 30 20:27:11 2024 +0100 +++ b/rust/Cargo.lock Sat Nov 30 20:30:18 2024 +0100 @@ -712,6 +712,7 @@ name = "hg-pyo3" version = "0.1.0" dependencies = [ + "derive_more", "env_logger 0.9.3", "hg-core", "log",
--- a/rust/hg-pyo3/Cargo.toml Sat Nov 30 20:27:11 2024 +0100 +++ b/rust/hg-pyo3/Cargo.toml Sat Nov 30 20:30:18 2024 +0100 @@ -12,4 +12,5 @@ hg-core = { path = "../hg-core"} stable_deref_trait = "1.2.0" log = "0.4.17" -env_logger = "0.9.3" \ No newline at end of file +env_logger = "0.9.3" +derive_more = "0.99.17"
--- a/rust/hg-pyo3/src/lib.rs Sat Nov 30 20:27:11 2024 +0100 +++ b/rust/hg-pyo3/src/lib.rs Sat Nov 30 20:30:18 2024 +0100 @@ -2,6 +2,7 @@ mod dagops; mod exceptions; +mod revision; mod util; #[pymodule]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hg-pyo3/src/revision.rs Sat Nov 30 20:30:18 2024 +0100 @@ -0,0 +1,78 @@ +use pyo3::prelude::*; + +use hg::revlog::RevlogIndex; +use hg::{BaseRevision, Revision, UncheckedRevision}; + +use crate::exceptions::GraphError; + +/// Revision as exposed to/from the Python layer. +/// +/// We need this indirection because of the orphan rule, meaning we can't +/// implement a foreign trait (like [`cpython::ToPyObject`]) +/// for a foreign type (like [`hg::UncheckedRevision`]). +/// +/// This also acts as a deterrent against blindly trusting Python to send +/// us valid revision numbers. +#[derive( + Debug, + Copy, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + derive_more::From, + IntoPyObject, + FromPyObject, +)] +pub struct PyRevision(pub BaseRevision); + +impl From<Revision> for PyRevision { + fn from(r: Revision) -> Self { + PyRevision(r.0) + } +} + +/// Utility function to convert a Python iterable into various collections +/// +/// We need this in particular +/// - because of the checked upgrade from [`PyRevision`] to [`Revision`]. +/// - to feed to various methods of inner objects with `impl +/// IntoIterator<Item=Revision>` arguments, because a `PyErr` can arise at +/// each step of iteration, whereas these methods expect iterables over +/// `Revision`, not over some `Result<Revision, PyErr>` +pub fn rev_pyiter_collect<C, I>( + revs: &Bound<'_, PyAny>, + index: &I, +) -> PyResult<C> +where + C: FromIterator<Revision>, + I: RevlogIndex, +{ + rev_pyiter_collect_or_else(revs, index, |r| { + PyErr::new::<GraphError, _>(("InvalidRevision", r.0)) + }) +} + +/// Same as [`rev_pyiter_collect`], giving control on returned errors +pub fn rev_pyiter_collect_or_else<C, I>( + revs: &Bound<'_, PyAny>, + index: &I, + invalid_rev_error: impl FnOnce(PyRevision) -> PyErr + Copy, +) -> PyResult<C> +where + C: FromIterator<Revision>, + I: RevlogIndex, +{ + revs.try_iter()? + .map(|r| { + r.and_then(|o| match o.extract::<PyRevision>() { + Ok(r) => index + .check_revision(UncheckedRevision(r.0)) + .ok_or_else(|| invalid_rev_error(r)), + Err(e) => Err(e), + }) + }) + .collect() +}