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()
+}