Show More
@@ -0,0 +1,87 | |||
|
1 | // ancestors.rs | |
|
2 | // | |
|
3 | // Copyright 2024 Georges Racinet <georges.racinet@cloudcrane.io> | |
|
4 | // | |
|
5 | // This software may be used and distributed according to the terms of the | |
|
6 | // GNU General Public License version 2 or any later version. | |
|
7 | ||
|
8 | //! Bindings for the `hg::ancestors` module provided by the | |
|
9 | //! `hg-core` crate. From Python, this will be seen as `pyo3_rustext.ancestor` | |
|
10 | //! and can be used as replacement for the the pure `ancestor` Python module. | |
|
11 | use cpython::UnsafePyLeaked; | |
|
12 | use pyo3::prelude::*; | |
|
13 | ||
|
14 | use std::sync::RwLock; | |
|
15 | ||
|
16 | use vcsgraph::lazy_ancestors::AncestorsIterator as VCGAncestorsIterator; | |
|
17 | ||
|
18 | use crate::convert_cpython::{ | |
|
19 | proxy_index_extract, proxy_index_py_leak, py_leaked_borrow_mut, | |
|
20 | py_leaked_or_map_err, | |
|
21 | }; | |
|
22 | use crate::exceptions::{map_lock_error, GraphError}; | |
|
23 | use crate::revision::{rev_pyiter_collect, PyRevision}; | |
|
24 | use crate::util::new_submodule; | |
|
25 | use rusthg::revlog::PySharedIndex; | |
|
26 | ||
|
27 | #[pyclass] | |
|
28 | struct AncestorsIterator { | |
|
29 | inner: RwLock<UnsafePyLeaked<VCGAncestorsIterator<PySharedIndex>>>, | |
|
30 | } | |
|
31 | ||
|
32 | #[pymethods] | |
|
33 | impl AncestorsIterator { | |
|
34 | #[new] | |
|
35 | fn new( | |
|
36 | index_proxy: &Bound<'_, PyAny>, | |
|
37 | initrevs: &Bound<'_, PyAny>, | |
|
38 | stoprev: PyRevision, | |
|
39 | inclusive: bool, | |
|
40 | ) -> PyResult<Self> { | |
|
41 | // Safety: we don't leak the "faked" reference out of | |
|
42 | // `UnsafePyLeaked` | |
|
43 | let initvec: Vec<_> = { | |
|
44 | let borrowed_idx = unsafe { proxy_index_extract(index_proxy)? }; | |
|
45 | rev_pyiter_collect(initrevs, borrowed_idx)? | |
|
46 | }; | |
|
47 | let (py, leaked_idx) = proxy_index_py_leak(index_proxy)?; | |
|
48 | let res_ait = unsafe { | |
|
49 | leaked_idx.map(py, |idx| { | |
|
50 | VCGAncestorsIterator::new( | |
|
51 | idx, | |
|
52 | initvec.into_iter().map(|r| r.0), | |
|
53 | stoprev.0, | |
|
54 | inclusive, | |
|
55 | ) | |
|
56 | }) | |
|
57 | }; | |
|
58 | let ait = | |
|
59 | py_leaked_or_map_err(py, res_ait, GraphError::from_vcsgraph)?; | |
|
60 | let inner = ait.into(); | |
|
61 | Ok(Self { inner }) | |
|
62 | } | |
|
63 | ||
|
64 | fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { | |
|
65 | slf | |
|
66 | } | |
|
67 | ||
|
68 | fn __next__(slf: PyRefMut<'_, Self>) -> PyResult<Option<PyRevision>> { | |
|
69 | let mut leaked = slf.inner.write().map_err(map_lock_error)?; | |
|
70 | // Safety: we don't leak the inner 'static ref out of UnsafePyLeaked | |
|
71 | let mut inner = unsafe { py_leaked_borrow_mut(&slf, &mut leaked)? }; | |
|
72 | match inner.next() { | |
|
73 | Some(Err(e)) => Err(GraphError::from_vcsgraph(e)), | |
|
74 | None => Ok(None), | |
|
75 | Some(Ok(r)) => Ok(Some(PyRevision(r))), | |
|
76 | } | |
|
77 | } | |
|
78 | } | |
|
79 | ||
|
80 | pub fn init_module<'py>( | |
|
81 | py: Python<'py>, | |
|
82 | package: &str, | |
|
83 | ) -> PyResult<Bound<'py, PyModule>> { | |
|
84 | let m = new_submodule(py, package, "ancestor")?; | |
|
85 | m.add_class::<AncestorsIterator>()?; | |
|
86 | Ok(m) | |
|
87 | } |
@@ -1,286 +1,285 | |||
|
1 | 1 | //! This module takes care of all conversions involving `rusthg` (hg-cpython) |
|
2 | 2 | //! objects in the PyO3 call context. |
|
3 | 3 | //! |
|
4 | 4 | //! For source code clarity, we only import (`use`) [`cpython`] traits and not |
|
5 | 5 | //! any of its data objects. We are instead using full qualifiers, such as |
|
6 | 6 | //! `cpython::PyObject`, and believe that the added heaviness is an acceptatble |
|
7 | 7 | //! price to pay to avoid confusion. |
|
8 | 8 | //! |
|
9 | 9 | //! Also it, is customary in [`cpython`] to label the GIL lifetime as `'p`, |
|
10 | 10 | //! whereas it is `'py` in PyO3 context. We keep both these conventions in |
|
11 | 11 | //! the arguments side of function signatures when they are not simply elided. |
|
12 | 12 | use pyo3::exceptions::PyTypeError; |
|
13 | 13 | use pyo3::prelude::*; |
|
14 | 14 | use pyo3::{pyclass::boolean_struct::False, PyClass}; |
|
15 | 15 | |
|
16 | 16 | use cpython::ObjectProtocol; |
|
17 | 17 | use cpython::PythonObject; |
|
18 | 18 | use lazy_static::lazy_static; |
|
19 | 19 | |
|
20 | 20 | use hg::revlog::index::Index as CoreIndex; |
|
21 | 21 | use rusthg::revlog::{InnerRevlog, PySharedIndex}; |
|
22 | 22 | |
|
23 | 23 | /// Marker trait for PyO3 objects with a lifetime representing the acquired GIL |
|
24 | 24 | /// |
|
25 | 25 | /// # Safety |
|
26 | 26 | /// |
|
27 | 27 | /// This trait must not be implemented for objects with lifetimes that |
|
28 | 28 | /// do not imply in PyO3 that the GIL is acquired during the whole lifetime. |
|
29 | 29 | pub unsafe trait WithGIL<'py> {} |
|
30 | 30 | |
|
31 | 31 | // Safety: the lifetime on these PyO3 objects all represent the acquired GIL |
|
32 | 32 | unsafe impl<'py> WithGIL<'py> for Python<'py> {} |
|
33 | 33 | unsafe impl<'py, T> WithGIL<'py> for Bound<'py, T> {} |
|
34 | 34 | unsafe impl<'py, T: PyClass> WithGIL<'py> for PyRef<'py, T> {} |
|
35 | 35 | unsafe impl<'py, T: PyClass<Frozen = False>> WithGIL<'py> |
|
36 | 36 | for PyRefMut<'py, T> |
|
37 | 37 | { |
|
38 | 38 | } |
|
39 | 39 | |
|
40 | 40 | /// Force cpython's GIL handle with the appropriate lifetime |
|
41 | 41 | /// |
|
42 | 42 | /// In `pyo3`, the fact that we have the GIL is expressed by the lifetime of |
|
43 | 43 | /// the incoming [`Bound`] smart pointer. We therefore simply instantiate |
|
44 | 44 | /// the `cpython` handle and coerce its lifetime by the function signature. |
|
45 | 45 | /// |
|
46 | 46 | /// Reacquiring the GIL is also a possible alternative, as the CPython |
|
47 | 47 | /// documentation explicitely states that "recursive calls are allowed" |
|
48 | 48 | /// (we interpret that as saying that acquiring the GIL within a thread that |
|
49 | 49 | /// already has it works) *as long as it is properly released* |
|
50 | 50 | /// reference: |
|
51 | 51 | /// <https://docs.python.org/3.8/c-api/init.html#c.PyGILState_Ensure> |
|
52 | 52 | pub(crate) fn cpython_handle<'py, T: WithGIL<'py>>( |
|
53 | 53 | _with_gil: &T, |
|
54 | 54 | ) -> cpython::Python<'py> { |
|
55 | 55 | // safety: this is safe because the returned object has the same lifetime |
|
56 | 56 | // as the incoming object. |
|
57 | 57 | unsafe { cpython::Python::assume_gil_acquired() } |
|
58 | 58 | } |
|
59 | 59 | |
|
60 | 60 | /// Force PyO3 GIL handle from cpython's. |
|
61 | 61 | /// |
|
62 | 62 | /// Very similar to [`cpython_handle`] |
|
63 | 63 | pub fn pyo3_handle(_py: cpython::Python<'_>) -> Python<'_> { |
|
64 | 64 | // safety: this is safe because the returned object has the same lifetime |
|
65 | 65 | // as the incoming object. |
|
66 | 66 | unsafe { Python::assume_gil_acquired() } |
|
67 | 67 | } |
|
68 | 68 | |
|
69 | 69 | /// Convert a PyO3 [`PyObject`] into a [`cpython::PyObject`] |
|
70 | 70 | /// |
|
71 | 71 | /// During this process, the reference count is increased, then decreased. |
|
72 | 72 | /// This means that the GIL (symbolized by the lifetime on the `obj` |
|
73 | 73 | /// argument) is needed. |
|
74 | 74 | /// |
|
75 | 75 | /// We could make something perhaps more handy by simply stealing the |
|
76 | 76 | /// pointer, forgetting the incoming and then implement `From` with "newtype". |
|
77 | 77 | /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps |
|
78 | 78 | /// not for the current endeavour. |
|
79 | 79 | pub(crate) fn to_cpython_py_object<'py>( |
|
80 | 80 | obj: &Bound<'py, PyAny>, |
|
81 | 81 | ) -> (cpython::Python<'py>, cpython::PyObject) { |
|
82 | 82 | let py = cpython_handle(obj); |
|
83 | 83 | // public alias of the private cpython::fii::PyObject (!) |
|
84 | 84 | let raw = obj.as_ptr() as *mut python3_sys::PyObject; |
|
85 | 85 | // both pyo3 and rust-cpython will decrement the refcount on drop. |
|
86 | 86 | // If we use from_owned_ptr, that's a segfault. |
|
87 | 87 | (py, unsafe { cpython::PyObject::from_borrowed_ptr(py, raw) }) |
|
88 | 88 | } |
|
89 | 89 | |
|
90 | 90 | /// Convert a [`cpython::PyObject`] into a PyO3 [`PyObject`] |
|
91 | 91 | /// |
|
92 | 92 | /// During this process, the reference count is increased, then decreased. |
|
93 | 93 | /// This means that the GIL (symbolized by the PyO3 [`Python`] handle is |
|
94 | 94 | /// needed. |
|
95 | 95 | /// |
|
96 | 96 | /// We could make something perhaps more handy by simply stealing the |
|
97 | 97 | /// pointer, forgetting the incoming and then implement `From` with "newtype". |
|
98 | 98 | /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps |
|
99 | 99 | /// not for the current endeavour. |
|
100 | 100 | pub(crate) fn from_cpython_py_object( |
|
101 | 101 | py: Python<'_>, |
|
102 | 102 | obj: cpython::PyObject, |
|
103 | 103 | ) -> PyObject { |
|
104 | 104 | let raw = obj.as_ptr() as *mut pyo3::ffi::PyObject; |
|
105 | 105 | unsafe { Py::from_borrowed_ptr(py, raw) } |
|
106 | 106 | } |
|
107 | 107 | |
|
108 | 108 | /// Convert [`cpython::PyErr`] into [`pyo3::PyErr`] |
|
109 | 109 | /// |
|
110 | 110 | /// The exception class remains the same as the original exception, |
|
111 | 111 | /// hence if it is also defined in another dylib based on `cpython` crate, |
|
112 | 112 | /// it will need to be converted to be downcasted in this crate. |
|
113 | 113 | pub(crate) fn from_cpython_pyerr( |
|
114 | 114 | py: cpython::Python<'_>, |
|
115 | 115 | mut e: cpython::PyErr, |
|
116 | 116 | ) -> PyErr { |
|
117 | 117 | let pyo3_py = pyo3_handle(py); |
|
118 | 118 | let cpython_exc_obj = e.instance(py); |
|
119 | 119 | let pyo3_exc_obj = from_cpython_py_object(pyo3_py, cpython_exc_obj); |
|
120 | 120 | PyErr::from_value(pyo3_exc_obj.into_bound(pyo3_py)) |
|
121 | 121 | } |
|
122 | 122 | |
|
123 | 123 | /// Retrieve the PyType for objects from the `mercurial.rustext` crate. |
|
124 | 124 | fn retrieve_cpython_py_type( |
|
125 | 125 | submodule_name: &str, |
|
126 | 126 | type_name: &str, |
|
127 | 127 | ) -> cpython::PyResult<cpython::PyType> { |
|
128 | 128 | let guard = cpython::Python::acquire_gil(); |
|
129 | 129 | let py = guard.python(); |
|
130 | 130 | let module = py.import(&format!("mercurial.rustext.{submodule_name}"))?; |
|
131 | 131 | module.get(py, type_name)?.extract::<cpython::PyType>(py) |
|
132 | 132 | } |
|
133 | 133 | |
|
134 | 134 | lazy_static! { |
|
135 | 135 | static ref INNER_REVLOG_PY_TYPE: cpython::PyType = { |
|
136 | 136 | retrieve_cpython_py_type("revlog", "InnerRevlog") |
|
137 | 137 | .expect("Could not import InnerRevlog in Python") |
|
138 | 138 | }; |
|
139 | 139 | } |
|
140 | 140 | |
|
141 | 141 | /// Downcast [`InnerRevlog`], with the appropriate Python type checking. |
|
142 | 142 | /// |
|
143 | 143 | /// The PyType object representing the `InnerRevlog` Python class is not the |
|
144 | 144 | /// the same in this dylib as it is in the `mercurial.rustext` module. |
|
145 | 145 | /// This is because the code created with the [`cpython::py_class!`] |
|
146 | 146 | /// macro is itself duplicated in both dylibs. In the case of this crate, this |
|
147 | 147 | /// happens by linking to the [`rusthg`] crate and provides the `InnerRevlog` |
|
148 | 148 | /// that is visible from this crate. The `InnerRevlog::get_type` associated |
|
149 | 149 | /// function turns out to return a `static mut` (look for `TYPE_OBJECT` in |
|
150 | 150 | /// `py_class_impl3.rs`), which obviously is different in both dylibs. |
|
151 | 151 | /// |
|
152 | 152 | /// The consequence of that is that downcasting an `InnerRevlog` originally |
|
153 | 153 | /// from the `mecurial.rustext` module to our `InnerRevlog` cannot be done with |
|
154 | 154 | /// the usual `extract::<InnerRevlog>(py)`, as it would perform the type |
|
155 | 155 | /// checking with the `PyType` that is embedded in `mercurial.pyo3_rustext`. |
|
156 | 156 | /// We must check the `PyType` that is within `mercurial.rustext` instead. |
|
157 | 157 | /// This is what this function does. |
|
158 | 158 | fn extract_inner_revlog( |
|
159 | 159 | py: cpython::Python, |
|
160 | 160 | inner_revlog: cpython::PyObject, |
|
161 | 161 | ) -> PyResult<InnerRevlog> { |
|
162 | 162 | if !(*INNER_REVLOG_PY_TYPE).is_instance(py, &inner_revlog) { |
|
163 | 163 | return Err(PyTypeError::new_err("Not an InnerRevlog instance")); |
|
164 | 164 | } |
|
165 | 165 | // Safety: this is safe because we checked the PyType already, with the |
|
166 | 166 | // value embedded in `mercurial.rustext`. |
|
167 | 167 | Ok(unsafe { InnerRevlog::unchecked_downcast_from(inner_revlog) }) |
|
168 | 168 | } |
|
169 | 169 | |
|
170 | 170 | /// This is similar to [`rusthg.py_rust_index_to_graph`], with difference in |
|
171 | 171 | /// how we retrieve the [`InnerRevlog`]. |
|
172 | 172 | pub fn py_rust_index_to_graph( |
|
173 | 173 | py: cpython::Python, |
|
174 | 174 | index_proxy: cpython::PyObject, |
|
175 | 175 | ) -> PyResult<cpython::UnsafePyLeaked<PySharedIndex>> { |
|
176 | 176 | let inner_revlog = extract_inner_revlog( |
|
177 | 177 | py, |
|
178 | 178 | index_proxy |
|
179 | 179 | .getattr(py, "inner") |
|
180 | 180 | .map_err(|e| from_cpython_pyerr(py, e))?, |
|
181 | 181 | )?; |
|
182 | 182 | |
|
183 | 183 | let leaked = inner_revlog.pub_inner(py).leak_immutable(); |
|
184 | 184 | // Safety: we don't leak the "faked" reference out of the `UnsafePyLeaked` |
|
185 | 185 | Ok(unsafe { leaked.map(py, |idx| PySharedIndex { inner: &idx.index }) }) |
|
186 | 186 | } |
|
187 | 187 | |
|
188 | 188 | pub(crate) fn proxy_index_py_leak<'py>( |
|
189 | 189 | index_proxy: &Bound<'py, PyAny>, |
|
190 | 190 | ) -> PyResult<(cpython::Python<'py>, cpython::UnsafePyLeaked<PySharedIndex>)> { |
|
191 | 191 | let (py, idx_proxy) = to_cpython_py_object(index_proxy); |
|
192 | 192 | let py_leaked = py_rust_index_to_graph(py, idx_proxy)?; |
|
193 | 193 | Ok((py, py_leaked)) |
|
194 | 194 | } |
|
195 | 195 | |
|
196 | 196 | /// Full extraction of the proxy index object as received in PyO3 to a |
|
197 | 197 | /// [`CoreIndex`] reference. |
|
198 | 198 | /// |
|
199 | 199 | /// # Safety |
|
200 | 200 | /// |
|
201 | 201 | /// The invariants to maintain are those of the underlying |
|
202 | 202 | /// [`UnsafePyLeaked::try_borrow`]: the caller must not leak the inner |
|
203 | 203 | /// reference. |
|
204 | 204 | pub(crate) unsafe fn proxy_index_extract<'py>( |
|
205 | 205 | index_proxy: &Bound<'py, PyAny>, |
|
206 | 206 | ) -> PyResult<&'py CoreIndex> { |
|
207 | 207 | let (py, py_leaked) = proxy_index_py_leak(index_proxy)?; |
|
208 | 208 | let py_shared = &*unsafe { |
|
209 | 209 | py_leaked |
|
210 | 210 | .try_borrow(py) |
|
211 | 211 | .map_err(|e| from_cpython_pyerr(py, e))? |
|
212 | 212 | }; |
|
213 | 213 | Ok(py_shared.inner) |
|
214 | 214 | } |
|
215 | 215 | |
|
216 | 216 | /// Generic borrow of [`cpython::UnsafePyLeaked`], with proper mapping. |
|
217 | 217 | /// |
|
218 | 218 | /// # Safety |
|
219 | 219 | /// |
|
220 | 220 | /// The invariants to maintain are those of the underlying |
|
221 | 221 | /// [`UnsafePyLeaked::try_borrow`]: the caller must not leak the inner |
|
222 | 222 | /// static reference. It is possible, depending on `T` that such a leak cannot |
|
223 | 223 | /// occur in practice. We may later on define a marker trait for this, |
|
224 | 224 | /// which will allow us to make declare this function to be safe. |
|
225 | 225 | #[allow(dead_code)] |
|
226 | 226 | pub(crate) unsafe fn py_leaked_borrow<'a, 'py: 'a, T>( |
|
227 | 227 | py: &impl WithGIL<'py>, |
|
228 | 228 | leaked: &'a cpython::UnsafePyLeaked<T>, |
|
229 | 229 | ) -> PyResult<cpython::PyLeakedRef<'a, T>> { |
|
230 | 230 | let py = cpython_handle(py); |
|
231 | 231 | leaked.try_borrow(py).map_err(|e| from_cpython_pyerr(py, e)) |
|
232 | 232 | } |
|
233 | 233 | |
|
234 | 234 | /// Mutable variant of [`py_leaked_borrow`] |
|
235 | 235 | /// |
|
236 | 236 | /// # Safety |
|
237 | 237 | /// |
|
238 | 238 | /// See [`py_leaked_borrow`] |
|
239 | 239 | #[allow(dead_code)] |
|
240 | 240 | pub(crate) unsafe fn py_leaked_borrow_mut<'a, 'py: 'a, T>( |
|
241 | 241 | py: &impl WithGIL<'py>, |
|
242 | 242 | leaked: &'a mut cpython::UnsafePyLeaked<T>, |
|
243 | 243 | ) -> PyResult<cpython::PyLeakedRefMut<'a, T>> { |
|
244 | 244 | let py = cpython_handle(py); |
|
245 | 245 | leaked |
|
246 | 246 | .try_borrow_mut(py) |
|
247 | 247 | .map_err(|e| from_cpython_pyerr(py, e)) |
|
248 | 248 | } |
|
249 | 249 | |
|
250 | 250 | /// Error propagation for an [`UnsafePyLeaked`] wrapping a [`Result`] |
|
251 | 251 | /// |
|
252 | 252 | /// TODO (will consider when implementing UnsafePyLeaked in PyO3): |
|
253 | 253 | /// It would be nice for UnsafePyLeaked to provide this directly as a variant |
|
254 | 254 | /// of the `map` method with a signature such as: |
|
255 | 255 | /// |
|
256 | 256 | /// ``` |
|
257 | 257 | /// unsafe fn map_or_err(&self, |
|
258 | 258 | /// py: Python, |
|
259 | 259 | /// f: impl FnOnce(T) -> Result(U, E), |
|
260 | 260 | /// convert_err: impl FnOnce(E) -> PyErr) |
|
261 | 261 | /// ``` |
|
262 | 262 | /// |
|
263 | 263 | /// This would spare users of the `cpython` crate the additional `unsafe` deref |
|
264 | 264 | /// to inspect the error and return it outside `UnsafePyLeaked`, and the |
|
265 | 265 | /// subsequent unwrapping that this function performs. |
|
266 | #[allow(dead_code)] | |
|
267 | 266 | pub(crate) fn py_leaked_or_map_err<T, E: std::fmt::Debug + Copy>( |
|
268 | 267 | py: cpython::Python, |
|
269 | 268 | leaked: cpython::UnsafePyLeaked<Result<T, E>>, |
|
270 | 269 | convert_err: impl FnOnce(E) -> PyErr, |
|
271 | 270 | ) -> PyResult<cpython::UnsafePyLeaked<T>> { |
|
272 | 271 | // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` |
|
273 | 272 | if let Err(e) = *unsafe { |
|
274 | 273 | leaked |
|
275 | 274 | .try_borrow(py) |
|
276 | 275 | .map_err(|e| from_cpython_pyerr(py, e))? |
|
277 | 276 | } { |
|
278 | 277 | return Err(convert_err(e)); |
|
279 | 278 | } |
|
280 | 279 | // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` |
|
281 | 280 | Ok(unsafe { |
|
282 | 281 | leaked.map(py, |res| { |
|
283 | 282 | res.expect("Error case should have already be treated") |
|
284 | 283 | }) |
|
285 | 284 | }) |
|
286 | 285 | } |
@@ -1,34 +1,38 | |||
|
1 | use pyo3::exceptions::PyValueError; | |
|
1 | use pyo3::exceptions::{PyRuntimeError, PyValueError}; | |
|
2 | 2 | use pyo3::import_exception; |
|
3 | 3 | use pyo3::{create_exception, PyErr}; |
|
4 | 4 | |
|
5 | 5 | use crate::revision::PyRevision; |
|
6 | 6 | |
|
7 | 7 | create_exception!(pyo3_rustext, GraphError, PyValueError); |
|
8 | 8 | import_exception!(mercurial.error, WdirUnsupported); |
|
9 | 9 | |
|
10 | 10 | impl GraphError { |
|
11 | 11 | pub fn from_hg(inner: hg::GraphError) -> PyErr { |
|
12 | 12 | match inner { |
|
13 | 13 | hg::GraphError::ParentOutOfRange(r) => { |
|
14 | 14 | GraphError::new_err(("ParentOutOfRange", PyRevision(r.0))) |
|
15 | 15 | } |
|
16 | 16 | } |
|
17 | 17 | } |
|
18 | 18 | pub fn from_vcsgraph(inner: vcsgraph::graph::GraphReadError) -> PyErr { |
|
19 | 19 | match inner { |
|
20 | 20 | vcsgraph::graph::GraphReadError::InconsistentGraphData => { |
|
21 | 21 | GraphError::new_err("InconsistentGraphData") |
|
22 | 22 | } |
|
23 | 23 | vcsgraph::graph::GraphReadError::InvalidKey => { |
|
24 | 24 | GraphError::new_err("ParentOutOfRange") |
|
25 | 25 | } |
|
26 | 26 | vcsgraph::graph::GraphReadError::KeyedInvalidKey(r) => { |
|
27 | 27 | GraphError::new_err(("ParentOutOfRange", r)) |
|
28 | 28 | } |
|
29 | 29 | vcsgraph::graph::GraphReadError::WorkingDirectoryUnsupported => { |
|
30 | 30 | WdirUnsupported::new_err(()) |
|
31 | 31 | } |
|
32 | 32 | } |
|
33 | 33 | } |
|
34 | 34 | } |
|
35 | ||
|
36 | pub fn map_lock_error<T>(e: std::sync::PoisonError<T>) -> PyErr { | |
|
37 | PyRuntimeError::new_err(format!("In Rust PyO3 bindings: {e}")) | |
|
38 | } |
@@ -1,23 +1,25 | |||
|
1 | 1 | use pyo3::prelude::*; |
|
2 | 2 | |
|
3 | mod ancestors; | |
|
3 | 4 | mod convert_cpython; |
|
4 | 5 | mod dagops; |
|
5 | 6 | mod exceptions; |
|
6 | 7 | mod revision; |
|
7 | 8 | mod util; |
|
8 | 9 | |
|
9 | 10 | #[pymodule] |
|
10 | 11 | fn pyo3_rustext(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { |
|
11 | 12 | m.add( |
|
12 | 13 | "__doc__", |
|
13 | 14 | "Mercurial core concepts - Rust implementation exposed via PyO3", |
|
14 | 15 | )?; |
|
15 | 16 | // the module's __name__ is pyo3_rustext, not mercurial.pyo3_rustext |
|
16 | 17 | // (at least at this point). |
|
17 | 18 | let name: String = m.getattr("__name__")?.extract()?; |
|
18 | 19 | let dotted_name = format!("mercurial.{}", name); |
|
19 | 20 | |
|
21 | m.add_submodule(&ancestors::init_module(py, &dotted_name)?)?; | |
|
20 | 22 | m.add_submodule(&dagops::init_module(py, &dotted_name)?)?; |
|
21 | 23 | m.add("GraphError", py.get_type::<exceptions::GraphError>())?; |
|
22 | 24 | Ok(()) |
|
23 | 25 | } |
General Comments 0
You need to be logged in to leave comments.
Login now