Skip to main content

graphrecords_python/graphrecord/
mod.rs

1#![allow(clippy::new_without_default, clippy::significant_drop_tightening)]
2
3pub mod attribute;
4mod borrowed;
5pub mod connector;
6pub mod datatype;
7pub mod errors;
8pub mod overview;
9pub mod plugins;
10pub mod querying;
11pub mod schema;
12pub mod traits;
13pub mod value;
14
15use crate::{
16    conversion_lut::ConversionLut,
17    graphrecord::{
18        overview::{PyGroupOverview, PyOverview},
19        plugins::PyPlugin,
20    },
21};
22use attribute::PyGraphRecordAttribute;
23use borrowed::BorrowedGraphRecord;
24use connector::PyConnector;
25use errors::PyGraphRecordError;
26use graphrecords_core::{
27    errors::GraphRecordError,
28    graphrecord::{
29        Attributes, EdgeDataFrameInput, EdgeIndex, GraphRecord, GraphRecordAttribute,
30        GraphRecordValue, Group, NodeDataFrameInput, connector::ConnectedGraphRecord,
31        plugins::Plugin,
32    },
33    prelude::NodeIndex,
34};
35use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
36use pyo3::{
37    exceptions::PyRuntimeError,
38    prelude::*,
39    types::{PyBytes, PyDict, PyFunction},
40};
41use pyo3_polars::PyDataFrame;
42use querying::{PyReturnOperand, edges::PyEdgeOperand, nodes::PyNodeOperand};
43use schema::PySchema;
44use std::{
45    collections::HashMap,
46    ops::{Deref, DerefMut},
47    ptr::NonNull,
48};
49use traits::DeepInto;
50use value::PyGraphRecordValue;
51
52pub type PyAttributes = HashMap<PyGraphRecordAttribute, PyGraphRecordValue>;
53pub type PyGroup = PyGraphRecordAttribute;
54pub type PyPluginName = PyGraphRecordAttribute;
55pub type PyNodeIndex = PyGraphRecordAttribute;
56pub type PyEdgeIndex = EdgeIndex;
57type Lut<T> = ConversionLut<usize, fn(&Bound<'_, PyAny>) -> PyResult<T>>;
58
59#[pyclass(frozen)]
60#[derive(Debug)]
61pub struct PyGraphRecord {
62    inner: PyGraphRecordInner,
63}
64
65#[derive(Debug)]
66#[allow(clippy::large_enum_variant)]
67enum PyGraphRecordInner {
68    Owned(RwLock<GraphRecord>),
69    Connected(RwLock<ConnectedGraphRecord<PyConnector>>),
70    Borrowed(BorrowedGraphRecord),
71}
72
73pub(crate) enum InnerRef<'a> {
74    Owned(RwLockReadGuard<'a, GraphRecord>),
75    Connected(RwLockReadGuard<'a, ConnectedGraphRecord<PyConnector>>),
76    Borrowed(RwLockReadGuard<'a, Option<NonNull<GraphRecord>>>),
77}
78
79impl Deref for InnerRef<'_> {
80    type Target = GraphRecord;
81
82    fn deref(&self) -> &GraphRecord {
83        match self {
84            InnerRef::Owned(guard) => guard,
85            InnerRef::Connected(guard) => guard,
86            // SAFETY: The guard is only constructed after checking `is_some()` in `inner()`.
87            // The pointer is valid for the duration of the `scope()`/`scope_mut()` call
88            // because the scope's Drop guard needs a write lock to clear it, and this read
89            // guard prevents that.
90            InnerRef::Borrowed(guard) => unsafe {
91                guard
92                    .expect("Borrowed pointer must be Some when InnerRef is alive")
93                    .as_ref()
94            },
95        }
96    }
97}
98
99pub(crate) enum InnerRefMut<'a> {
100    Owned(RwLockWriteGuard<'a, GraphRecord>),
101    Connected(RwLockWriteGuard<'a, ConnectedGraphRecord<PyConnector>>),
102    Borrowed(RwLockWriteGuard<'a, Option<NonNull<GraphRecord>>>),
103}
104
105impl Deref for InnerRefMut<'_> {
106    type Target = GraphRecord;
107
108    fn deref(&self) -> &GraphRecord {
109        match self {
110            InnerRefMut::Owned(guard) => guard,
111            InnerRefMut::Connected(guard) => guard,
112            // SAFETY: Same as `InnerRef::Borrowed`. Pointer was checked `is_some()` in
113            // `inner_mut()`, and the write guard keeps the scope's Drop from clearing it.
114            // Additionally, `inner_mut()` has already verified `is_mutable()` is true.
115            InnerRefMut::Borrowed(guard) => unsafe {
116                guard
117                    .expect("Borrowed pointer must be Some when InnerRefMut is alive")
118                    .as_ref()
119            },
120        }
121    }
122}
123
124impl DerefMut for InnerRefMut<'_> {
125    fn deref_mut(&mut self) -> &mut GraphRecord {
126        match self {
127            InnerRefMut::Owned(guard) => &mut *guard,
128            InnerRefMut::Connected(guard) => &mut *guard,
129            // SAFETY: Same as above, plus: the write guard ensures exclusive access to the
130            // pointer, so creating `&mut GraphRecord` is sound. The original `scope_mut()`
131            // call holds `&mut GraphRecord`, guaranteeing no other references to the pointee
132            // exist outside this lock. `inner_mut()` has verified `is_mutable()` is true,
133            // ensuring this path is only reachable for pointers originating from `&mut`.
134            InnerRefMut::Borrowed(guard) => unsafe {
135                guard
136                    .expect("Borrowed pointer must be Some when InnerRefMut is alive")
137                    .as_mut()
138            },
139        }
140    }
141}
142
143impl Clone for PyGraphRecord {
144    fn clone(&self) -> Self {
145        match &self.inner {
146            PyGraphRecordInner::Owned(lock) => Self {
147                inner: PyGraphRecordInner::Owned(RwLock::new(lock.read().clone())),
148            },
149            PyGraphRecordInner::Connected(lock) => Self {
150                inner: PyGraphRecordInner::Connected(RwLock::new(lock.read().clone())),
151            },
152            PyGraphRecordInner::Borrowed(_) => Self {
153                inner: PyGraphRecordInner::Borrowed(BorrowedGraphRecord::dead()),
154            },
155        }
156    }
157}
158
159impl PyGraphRecord {
160    pub(crate) fn inner(&self) -> PyResult<InnerRef<'_>> {
161        match &self.inner {
162            PyGraphRecordInner::Owned(lock) => Ok(InnerRef::Owned(lock.read())),
163            PyGraphRecordInner::Connected(lock) => Ok(InnerRef::Connected(lock.read())),
164            PyGraphRecordInner::Borrowed(borrowed) => {
165                let guard = borrowed.read();
166                if guard.is_some() {
167                    Ok(InnerRef::Borrowed(guard))
168                } else {
169                    Err(PyRuntimeError::new_err(
170                        "GraphRecord reference is no longer valid (used outside callback scope)",
171                    ))
172                }
173            }
174        }
175    }
176
177    pub(crate) fn connected(
178        &self,
179    ) -> PyResult<RwLockWriteGuard<'_, ConnectedGraphRecord<PyConnector>>> {
180        match &self.inner {
181            PyGraphRecordInner::Connected(lock) => Ok(lock.write()),
182            _ => Err(PyRuntimeError::new_err(
183                "GraphRecord has no connector attached",
184            )),
185        }
186    }
187
188    pub(crate) fn inner_mut(&self) -> PyResult<InnerRefMut<'_>> {
189        match &self.inner {
190            PyGraphRecordInner::Owned(lock) => Ok(InnerRefMut::Owned(lock.write())),
191            PyGraphRecordInner::Connected(lock) => Ok(InnerRefMut::Connected(lock.write())),
192            PyGraphRecordInner::Borrowed(borrowed) => {
193                if !borrowed.is_mutable() {
194                    return Err(PyRuntimeError::new_err("GraphRecord is read-only"));
195                }
196                let guard = borrowed.write();
197                if guard.is_some() {
198                    Ok(InnerRefMut::Borrowed(guard))
199                } else {
200                    Err(PyRuntimeError::new_err(
201                        "GraphRecord reference is no longer valid (used outside callback scope)",
202                    ))
203                }
204            }
205        }
206    }
207}
208
209impl From<GraphRecord> for PyGraphRecord {
210    fn from(value: GraphRecord) -> Self {
211        Self {
212            inner: PyGraphRecordInner::Owned(RwLock::new(value)),
213        }
214    }
215}
216
217impl From<ConnectedGraphRecord<PyConnector>> for PyGraphRecord {
218    fn from(value: ConnectedGraphRecord<PyConnector>) -> Self {
219        Self {
220            inner: PyGraphRecordInner::Connected(RwLock::new(value)),
221        }
222    }
223}
224
225impl TryFrom<PyGraphRecord> for GraphRecord {
226    type Error = PyErr;
227
228    fn try_from(value: PyGraphRecord) -> PyResult<Self> {
229        match value.inner {
230            PyGraphRecordInner::Owned(lock) => Ok(lock.into_inner()),
231            PyGraphRecordInner::Connected(lock) => Ok(lock.into_inner().into()),
232            PyGraphRecordInner::Borrowed(_) => Err(PyRuntimeError::new_err(
233                "Cannot convert a borrowed PyGraphRecord into an owned GraphRecord",
234            )),
235        }
236    }
237}
238
239#[pymethods]
240impl PyGraphRecord {
241    #[new]
242    pub fn new() -> Self {
243        GraphRecord::new().into()
244    }
245
246    pub fn _to_bytes<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
247        let bytes = bincode::serialize(&*self.inner()?)
248            .map_err(|_| {
249                GraphRecordError::ConversionError("Could not serialize GraphRecord".into())
250            })
251            .map_err(PyGraphRecordError::from)?;
252
253        Ok(PyBytes::new(py, &bytes))
254    }
255
256    #[staticmethod]
257    pub fn _from_bytes(data: &Bound<'_, PyBytes>) -> PyResult<Self> {
258        let graphrecord: GraphRecord = bincode::deserialize(data.as_bytes())
259            .map_err(|_| {
260                GraphRecordError::ConversionError("Could not deserialize GraphRecord".into())
261            })
262            .map_err(PyGraphRecordError::from)?;
263
264        Ok(graphrecord.into())
265    }
266
267    #[staticmethod]
268    pub fn with_schema(schema: PySchema) -> Self {
269        GraphRecord::with_schema(schema.into()).into()
270    }
271
272    #[staticmethod]
273    pub fn with_plugins(plugins: HashMap<PyPluginName, Py<PyAny>>) -> PyResult<Self> {
274        let plugins = plugins
275            .into_iter()
276            .map(|(name, plugin)| {
277                (
278                    name.into(),
279                    Box::new(PyPlugin::new(plugin)) as Box<dyn Plugin>,
280                )
281            })
282            .collect();
283
284        let graphrecord = GraphRecord::with_plugins(plugins).map_err(PyGraphRecordError::from)?;
285
286        Ok(graphrecord.into())
287    }
288
289    #[staticmethod]
290    #[pyo3(signature = (nodes, edges=None, schema=None))]
291    pub fn from_tuples(
292        nodes: Vec<(PyNodeIndex, PyAttributes)>,
293        edges: Option<Vec<(PyNodeIndex, PyNodeIndex, PyAttributes)>>,
294        schema: Option<PySchema>,
295    ) -> PyResult<Self> {
296        Ok(
297            GraphRecord::from_tuples(nodes.deep_into(), edges.deep_into(), schema.map(Into::into))
298                .map_err(PyGraphRecordError::from)?
299                .into(),
300        )
301    }
302
303    #[staticmethod]
304    #[pyo3(signature = (nodes_dataframes, edges_dataframes, schema=None))]
305    pub fn from_dataframes(
306        nodes_dataframes: Vec<(PyDataFrame, String)>,
307        edges_dataframes: Vec<(PyDataFrame, String, String)>,
308        schema: Option<PySchema>,
309    ) -> PyResult<Self> {
310        Ok(
311            GraphRecord::from_dataframes(
312                nodes_dataframes,
313                edges_dataframes,
314                schema.map(Into::into),
315            )
316            .map_err(PyGraphRecordError::from)?
317            .into(),
318        )
319    }
320
321    #[staticmethod]
322    #[pyo3(signature = (nodes_dataframes, schema=None))]
323    pub fn from_nodes_dataframes(
324        nodes_dataframes: Vec<(PyDataFrame, String)>,
325        schema: Option<PySchema>,
326    ) -> PyResult<Self> {
327        Ok(
328            GraphRecord::from_nodes_dataframes(nodes_dataframes, schema.map(Into::into))
329                .map_err(PyGraphRecordError::from)?
330                .into(),
331        )
332    }
333
334    #[staticmethod]
335    pub fn from_ron(path: &str) -> PyResult<Self> {
336        Ok(GraphRecord::from_ron(path)
337            .map_err(PyGraphRecordError::from)?
338            .into())
339    }
340
341    #[staticmethod]
342    pub fn with_connector(connector: Py<PyAny>) -> PyResult<Self> {
343        let connected = ConnectedGraphRecord::new(PyConnector::new(connector))
344            .map_err(PyGraphRecordError::from)?;
345
346        Ok(connected.into())
347    }
348
349    pub fn to_ron(&self, path: &str) -> PyResult<()> {
350        Ok(self
351            .inner()?
352            .to_ron(path)
353            .map_err(PyGraphRecordError::from)?)
354    }
355
356    #[allow(clippy::missing_panics_doc, reason = "infallible")]
357    pub fn to_dataframes(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
358        let export = self
359            .inner()?
360            .to_dataframes()
361            .map_err(PyGraphRecordError::from)?;
362
363        let outer_dict = PyDict::new(py);
364        let inner_dict = PyDict::new(py);
365
366        for (group, group_export) in export.groups {
367            let group_dict = PyDict::new(py);
368
369            let nodes_df = PyDataFrame(group_export.nodes);
370            group_dict
371                .set_item("nodes", nodes_df)
372                .expect("Setting item must succeed");
373
374            let edges_df = PyDataFrame(group_export.edges);
375            group_dict
376                .set_item("edges", edges_df)
377                .expect("Setting item must succeed");
378
379            inner_dict
380                .set_item(PyGraphRecordAttribute::from(group), group_dict)
381                .expect("Setting item must succeed");
382        }
383
384        outer_dict
385            .set_item("groups", inner_dict)
386            .expect("Setting item must succeed");
387
388        let ungrouped_dict = PyDict::new(py);
389
390        let nodes_df = PyDataFrame(export.ungrouped.nodes);
391        ungrouped_dict
392            .set_item("nodes", nodes_df)
393            .expect("Setting item must succeed");
394
395        let edges_df = PyDataFrame(export.ungrouped.edges);
396        ungrouped_dict
397            .set_item("edges", edges_df)
398            .expect("Setting item must succeed");
399
400        outer_dict
401            .set_item("ungrouped", ungrouped_dict)
402            .expect("Setting item must succeed");
403
404        Ok(outer_dict.into())
405    }
406
407    pub fn disconnect(&self) -> PyResult<Self> {
408        let graphrecord = self
409            .connected()?
410            .clone()
411            .disconnect()
412            .map_err(PyGraphRecordError::from)?;
413
414        Ok(graphrecord.into())
415    }
416
417    pub fn ingest(&self, data: Py<PyAny>) -> PyResult<()> {
418        self.connected()?
419            .ingest(data)
420            .map_err(PyGraphRecordError::from)?;
421
422        Ok(())
423    }
424
425    pub fn export(&self) -> PyResult<Py<PyAny>> {
426        let data = self
427            .connected()?
428            .export()
429            .map_err(PyGraphRecordError::from)?;
430
431        Ok(data)
432    }
433
434    pub fn add_plugin(&self, name: PyPluginName, plugin: Py<PyAny>) -> PyResult<()> {
435        let mut graphrecord = self.inner_mut()?;
436
437        graphrecord
438            .add_plugin(name.into(), Box::new(PyPlugin::new(plugin)))
439            .map_err(PyGraphRecordError::from)?;
440
441        Ok(())
442    }
443
444    pub fn remove_plugin(&self, name: PyPluginName) -> PyResult<()> {
445        let mut graphrecord = self.inner_mut()?;
446
447        graphrecord
448            .remove_plugin(&name.into())
449            .map_err(PyGraphRecordError::from)?;
450
451        Ok(())
452    }
453
454    #[getter]
455    pub fn plugins(&self) -> PyResult<Vec<PyPluginName>> {
456        Ok(self
457            .inner()?
458            .plugin_names()
459            .cloned()
460            .map(std::convert::Into::into)
461            .collect())
462    }
463
464    pub fn get_schema(&self) -> PyResult<PySchema> {
465        Ok(self.inner()?.get_schema().clone().into())
466    }
467
468    #[pyo3(signature = (schema, bypass_plugins=false))]
469    pub fn set_schema(&self, schema: PySchema, bypass_plugins: bool) -> PyResult<()> {
470        let mut graphrecord = self.inner_mut()?;
471
472        if bypass_plugins {
473            Ok(graphrecord
474                .set_schema_bypass_plugins(schema.into())
475                .map_err(PyGraphRecordError::from)?)
476        } else {
477            Ok(graphrecord
478                .set_schema(schema.into())
479                .map_err(PyGraphRecordError::from)?)
480        }
481    }
482
483    #[pyo3(signature = (bypass_plugins=false))]
484    pub fn freeze_schema(&self, bypass_plugins: bool) -> PyResult<()> {
485        let mut graphrecord = self.inner_mut()?;
486
487        if bypass_plugins {
488            Ok(graphrecord
489                .freeze_schema_bypass_plugins()
490                .map_err(PyGraphRecordError::from)?)
491        } else {
492            Ok(graphrecord
493                .freeze_schema()
494                .map_err(PyGraphRecordError::from)?)
495        }
496    }
497
498    #[pyo3(signature = (bypass_plugins=false))]
499    pub fn unfreeze_schema(&self, bypass_plugins: bool) -> PyResult<()> {
500        let mut graphrecord = self.inner_mut()?;
501
502        if bypass_plugins {
503            Ok(graphrecord
504                .unfreeze_schema_bypass_plugins()
505                .map_err(PyGraphRecordError::from)?)
506        } else {
507            Ok(graphrecord
508                .unfreeze_schema()
509                .map_err(PyGraphRecordError::from)?)
510        }
511    }
512
513    #[getter]
514    pub fn nodes(&self) -> PyResult<Vec<PyNodeIndex>> {
515        Ok(self
516            .inner()?
517            .node_indices()
518            .map(|node_index| node_index.clone().into())
519            .collect())
520    }
521
522    pub fn node(
523        &self,
524        node_index: Vec<PyNodeIndex>,
525    ) -> PyResult<HashMap<PyNodeIndex, PyAttributes>> {
526        let graphrecord = self.inner()?;
527
528        node_index
529            .into_iter()
530            .map(|node_index| {
531                let node_attributes = graphrecord
532                    .node_attributes(&node_index)
533                    .map_err(PyGraphRecordError::from)?;
534
535                Ok((node_index, node_attributes.deep_into()))
536            })
537            .collect()
538    }
539
540    #[getter]
541    pub fn edges(&self) -> PyResult<Vec<EdgeIndex>> {
542        Ok(self.inner()?.edge_indices().copied().collect())
543    }
544
545    pub fn edge(&self, edge_index: Vec<EdgeIndex>) -> PyResult<HashMap<EdgeIndex, PyAttributes>> {
546        let graphrecord = self.inner()?;
547
548        edge_index
549            .into_iter()
550            .map(|edge_index| {
551                let edge_attributes = graphrecord
552                    .edge_attributes(&edge_index)
553                    .map_err(PyGraphRecordError::from)?;
554
555                Ok((edge_index, edge_attributes.deep_into()))
556            })
557            .collect()
558    }
559
560    #[getter]
561    pub fn groups(&self) -> PyResult<Vec<PyGroup>> {
562        Ok(self
563            .inner()?
564            .groups()
565            .map(|group| group.clone().into())
566            .collect())
567    }
568
569    pub fn outgoing_edges(
570        &self,
571        node_index: Vec<PyNodeIndex>,
572    ) -> PyResult<HashMap<PyNodeIndex, Vec<EdgeIndex>>> {
573        let graphrecord = self.inner()?;
574
575        node_index
576            .into_iter()
577            .map(|node_index| {
578                let edges = graphrecord
579                    .outgoing_edges(&node_index)
580                    .map_err(PyGraphRecordError::from)?
581                    .copied()
582                    .collect();
583
584                Ok((node_index, edges))
585            })
586            .collect()
587    }
588
589    pub fn incoming_edges(
590        &self,
591        node_index: Vec<PyNodeIndex>,
592    ) -> PyResult<HashMap<PyNodeIndex, Vec<EdgeIndex>>> {
593        let graphrecord = self.inner()?;
594
595        node_index
596            .into_iter()
597            .map(|node_index| {
598                let edges = graphrecord
599                    .incoming_edges(&node_index)
600                    .map_err(PyGraphRecordError::from)?
601                    .copied()
602                    .collect();
603
604                Ok((node_index, edges))
605            })
606            .collect()
607    }
608
609    pub fn edge_endpoints(
610        &self,
611        edge_index: Vec<EdgeIndex>,
612    ) -> PyResult<HashMap<EdgeIndex, (PyNodeIndex, PyNodeIndex)>> {
613        let graphrecord = self.inner()?;
614
615        edge_index
616            .into_iter()
617            .map(|edge_index| {
618                let edge_endpoints = graphrecord
619                    .edge_endpoints(&edge_index)
620                    .map_err(PyGraphRecordError::from)?;
621
622                Ok((
623                    edge_index,
624                    (
625                        edge_endpoints.0.clone().into(),
626                        edge_endpoints.1.clone().into(),
627                    ),
628                ))
629            })
630            .collect()
631    }
632
633    pub fn edges_connecting(
634        &self,
635        source_node_indices: Vec<PyNodeIndex>,
636        target_node_indices: Vec<PyNodeIndex>,
637    ) -> PyResult<Vec<EdgeIndex>> {
638        let source_node_indices: Vec<GraphRecordAttribute> = source_node_indices.deep_into();
639        let target_node_indices: Vec<GraphRecordAttribute> = target_node_indices.deep_into();
640
641        Ok(self
642            .inner()?
643            .edges_connecting(
644                source_node_indices.iter().collect(),
645                target_node_indices.iter().collect(),
646            )
647            .copied()
648            .collect())
649    }
650
651    pub fn edges_connecting_undirected(
652        &self,
653        first_node_indices: Vec<PyNodeIndex>,
654        second_node_indices: Vec<PyNodeIndex>,
655    ) -> PyResult<Vec<EdgeIndex>> {
656        let first_node_indices: Vec<GraphRecordAttribute> = first_node_indices.deep_into();
657        let second_node_indices: Vec<GraphRecordAttribute> = second_node_indices.deep_into();
658
659        Ok(self
660            .inner()?
661            .edges_connecting_undirected(
662                first_node_indices.iter().collect(),
663                second_node_indices.iter().collect(),
664            )
665            .copied()
666            .collect())
667    }
668
669    #[pyo3(signature = (node_indices, bypass_plugins=false))]
670    pub fn remove_nodes(
671        &self,
672        node_indices: Vec<PyNodeIndex>,
673        bypass_plugins: bool,
674    ) -> PyResult<HashMap<PyNodeIndex, PyAttributes>> {
675        let mut graphrecord = self.inner_mut()?;
676
677        if bypass_plugins {
678            node_indices
679                .into_iter()
680                .map(|node_index| {
681                    let attributes = graphrecord
682                        .remove_node_bypass_plugins(&node_index)
683                        .map_err(PyGraphRecordError::from)?;
684                    Ok((node_index, attributes.deep_into()))
685                })
686                .collect()
687        } else {
688            node_indices
689                .into_iter()
690                .map(|node_index| {
691                    let attributes = graphrecord
692                        .remove_node(&node_index)
693                        .map_err(PyGraphRecordError::from)?;
694                    Ok((node_index, attributes.deep_into()))
695                })
696                .collect()
697        }
698    }
699
700    pub fn replace_node_attributes(
701        &self,
702        node_indices: Vec<PyNodeIndex>,
703        attributes: PyAttributes,
704    ) -> PyResult<()> {
705        let mut graphrecord = self.inner_mut()?;
706
707        let attributes: Attributes = attributes.deep_into();
708
709        for node_index in node_indices {
710            let mut current_attributes = graphrecord
711                .node_attributes_mut(&node_index)
712                .map_err(PyGraphRecordError::from)?;
713
714            current_attributes
715                .replace_attributes(attributes.clone())
716                .map_err(PyGraphRecordError::from)?;
717        }
718
719        Ok(())
720    }
721
722    pub fn update_node_attribute(
723        &self,
724        node_indices: Vec<PyNodeIndex>,
725        attribute: PyGraphRecordAttribute,
726        value: PyGraphRecordValue,
727    ) -> PyResult<()> {
728        let mut graphrecord = self.inner_mut()?;
729
730        let attribute: GraphRecordAttribute = attribute.into();
731        let value: GraphRecordValue = value.into();
732
733        for node_index in node_indices {
734            let mut node_attributes = graphrecord
735                .node_attributes_mut(&node_index)
736                .map_err(PyGraphRecordError::from)?;
737
738            node_attributes
739                .update_attribute(&attribute, value.clone())
740                .map_err(PyGraphRecordError::from)?;
741        }
742
743        Ok(())
744    }
745
746    pub fn remove_node_attribute(
747        &self,
748        node_indices: Vec<PyNodeIndex>,
749        attribute: PyGraphRecordAttribute,
750    ) -> PyResult<()> {
751        let mut graphrecord = self.inner_mut()?;
752
753        let attribute: GraphRecordAttribute = attribute.into();
754
755        for node_index in node_indices {
756            let mut node_attributes = graphrecord
757                .node_attributes_mut(&node_index)
758                .map_err(PyGraphRecordError::from)?;
759
760            node_attributes
761                .remove_attribute(&attribute)
762                .map_err(PyGraphRecordError::from)?;
763        }
764
765        Ok(())
766    }
767
768    #[pyo3(signature = (nodes, bypass_plugins=false))]
769    pub fn add_nodes(
770        &self,
771        nodes: Vec<(PyNodeIndex, PyAttributes)>,
772        bypass_plugins: bool,
773    ) -> PyResult<()> {
774        let mut graphrecord = self.inner_mut()?;
775
776        if bypass_plugins {
777            Ok(graphrecord
778                .add_nodes_bypass_plugins(nodes.deep_into())
779                .map_err(PyGraphRecordError::from)?)
780        } else {
781            Ok(graphrecord
782                .add_nodes(nodes.deep_into())
783                .map_err(PyGraphRecordError::from)?)
784        }
785    }
786
787    #[pyo3(signature = (nodes, group, bypass_plugins=false))]
788    pub fn add_nodes_with_group(
789        &self,
790        nodes: Vec<(PyNodeIndex, PyAttributes)>,
791        group: PyGroup,
792        bypass_plugins: bool,
793    ) -> PyResult<()> {
794        let mut graphrecord = self.inner_mut()?;
795
796        if bypass_plugins {
797            Ok(graphrecord
798                .add_nodes_with_group_bypass_plugins(nodes.deep_into(), group.into())
799                .map_err(PyGraphRecordError::from)?)
800        } else {
801            Ok(graphrecord
802                .add_nodes_with_group(nodes.deep_into(), group.into())
803                .map_err(PyGraphRecordError::from)?)
804        }
805    }
806
807    #[pyo3(signature = (nodes, groups, bypass_plugins=false))]
808    pub fn add_nodes_with_groups(
809        &self,
810        nodes: Vec<(PyNodeIndex, PyAttributes)>,
811        groups: Vec<PyGroup>,
812        bypass_plugins: bool,
813    ) -> PyResult<()> {
814        let mut graphrecord = self.inner_mut()?;
815        let groups: Vec<graphrecords_core::graphrecord::Group> = groups.deep_into();
816
817        if bypass_plugins {
818            Ok(graphrecord
819                .add_nodes_with_groups_bypass_plugins(nodes.deep_into(), &groups)
820                .map_err(PyGraphRecordError::from)?)
821        } else {
822            Ok(graphrecord
823                .add_nodes_with_groups(nodes.deep_into(), &groups)
824                .map_err(PyGraphRecordError::from)?)
825        }
826    }
827
828    #[pyo3(signature = (node_index, attributes, groups, bypass_plugins=false))]
829    pub fn add_node_with_groups(
830        &self,
831        node_index: PyNodeIndex,
832        attributes: PyAttributes,
833        groups: Vec<PyGroup>,
834        bypass_plugins: bool,
835    ) -> PyResult<()> {
836        let mut graphrecord = self.inner_mut()?;
837        let groups: Vec<graphrecords_core::graphrecord::Group> = groups.deep_into();
838
839        if bypass_plugins {
840            Ok(graphrecord
841                .add_node_with_groups_bypass_plugins(
842                    node_index.into(),
843                    attributes.deep_into(),
844                    &groups,
845                )
846                .map_err(PyGraphRecordError::from)?)
847        } else {
848            Ok(graphrecord
849                .add_node_with_groups(node_index.into(), attributes.deep_into(), &groups)
850                .map_err(PyGraphRecordError::from)?)
851        }
852    }
853
854    #[pyo3(signature = (nodes_dataframes, bypass_plugins=false))]
855    pub fn add_nodes_dataframes(
856        &self,
857        nodes_dataframes: Vec<(PyDataFrame, String)>,
858        bypass_plugins: bool,
859    ) -> PyResult<()> {
860        let mut graphrecord = self.inner_mut()?;
861
862        if bypass_plugins {
863            Ok(graphrecord
864                .add_nodes_dataframes_bypass_plugins(nodes_dataframes)
865                .map_err(PyGraphRecordError::from)?)
866        } else {
867            Ok(graphrecord
868                .add_nodes_dataframes(nodes_dataframes)
869                .map_err(PyGraphRecordError::from)?)
870        }
871    }
872
873    #[pyo3(signature = (nodes_dataframes, group, bypass_plugins=false))]
874    pub fn add_nodes_dataframes_with_group(
875        &self,
876        nodes_dataframes: Vec<(PyDataFrame, String)>,
877        group: PyGroup,
878        bypass_plugins: bool,
879    ) -> PyResult<()> {
880        let mut graphrecord = self.inner_mut()?;
881
882        if bypass_plugins {
883            Ok(graphrecord
884                .add_nodes_dataframes_with_group_bypass_plugins(nodes_dataframes, group.into())
885                .map_err(PyGraphRecordError::from)?)
886        } else {
887            Ok(graphrecord
888                .add_nodes_dataframes_with_group(nodes_dataframes, group.into())
889                .map_err(PyGraphRecordError::from)?)
890        }
891    }
892
893    #[pyo3(signature = (nodes_dataframes, groups, bypass_plugins=false))]
894    pub fn add_nodes_dataframes_with_groups(
895        &self,
896        nodes_dataframes: Vec<(PyDataFrame, String)>,
897        groups: Vec<PyGroup>,
898        bypass_plugins: bool,
899    ) -> PyResult<()> {
900        let mut graphrecord = self.inner_mut()?;
901        let groups: Vec<Group> = groups.deep_into();
902        let nodes_dataframes: Vec<NodeDataFrameInput> =
903            nodes_dataframes.into_iter().map(Into::into).collect();
904
905        if bypass_plugins {
906            Ok(graphrecord
907                .add_nodes_dataframes_with_groups_bypass_plugins(nodes_dataframes, &groups)
908                .map_err(PyGraphRecordError::from)?)
909        } else {
910            Ok(graphrecord
911                .add_nodes_dataframes_with_groups(nodes_dataframes, &groups)
912                .map_err(PyGraphRecordError::from)?)
913        }
914    }
915
916    #[pyo3(signature = (edge_indices, bypass_plugins=false))]
917    pub fn remove_edges(
918        &self,
919        edge_indices: Vec<EdgeIndex>,
920        bypass_plugins: bool,
921    ) -> PyResult<HashMap<EdgeIndex, PyAttributes>> {
922        let mut graphrecord = self.inner_mut()?;
923
924        if bypass_plugins {
925            edge_indices
926                .into_iter()
927                .map(|edge_index| {
928                    let attributes = graphrecord
929                        .remove_edge_bypass_plugins(&edge_index)
930                        .map_err(PyGraphRecordError::from)?;
931                    Ok((edge_index, attributes.deep_into()))
932                })
933                .collect()
934        } else {
935            edge_indices
936                .into_iter()
937                .map(|edge_index| {
938                    let attributes = graphrecord
939                        .remove_edge(&edge_index)
940                        .map_err(PyGraphRecordError::from)?;
941                    Ok((edge_index, attributes.deep_into()))
942                })
943                .collect()
944        }
945    }
946
947    pub fn replace_edge_attributes(
948        &self,
949        edge_indices: Vec<EdgeIndex>,
950        attributes: PyAttributes,
951    ) -> PyResult<()> {
952        let mut graphrecord = self.inner_mut()?;
953
954        let attributes: Attributes = attributes.deep_into();
955
956        for edge_index in edge_indices {
957            let mut current_attributes = graphrecord
958                .edge_attributes_mut(&edge_index)
959                .map_err(PyGraphRecordError::from)?;
960
961            current_attributes
962                .replace_attributes(attributes.clone())
963                .map_err(PyGraphRecordError::from)?;
964        }
965
966        Ok(())
967    }
968
969    pub fn update_edge_attribute(
970        &self,
971        edge_indices: Vec<EdgeIndex>,
972        attribute: PyGraphRecordAttribute,
973        value: PyGraphRecordValue,
974    ) -> PyResult<()> {
975        let mut graphrecord = self.inner_mut()?;
976
977        let attribute: GraphRecordAttribute = attribute.into();
978        let value: GraphRecordValue = value.into();
979
980        for edge_index in edge_indices {
981            let mut edge_attributes = graphrecord
982                .edge_attributes_mut(&edge_index)
983                .map_err(PyGraphRecordError::from)?;
984
985            edge_attributes
986                .update_attribute(&attribute, value.clone())
987                .map_err(PyGraphRecordError::from)?;
988        }
989
990        Ok(())
991    }
992
993    pub fn remove_edge_attribute(
994        &self,
995        edge_indices: Vec<EdgeIndex>,
996        attribute: PyGraphRecordAttribute,
997    ) -> PyResult<()> {
998        let mut graphrecord = self.inner_mut()?;
999
1000        let attribute: GraphRecordAttribute = attribute.into();
1001
1002        for edge_index in edge_indices {
1003            let mut edge_attributes = graphrecord
1004                .edge_attributes_mut(&edge_index)
1005                .map_err(PyGraphRecordError::from)?;
1006
1007            edge_attributes
1008                .remove_attribute(&attribute)
1009                .map_err(PyGraphRecordError::from)?;
1010        }
1011
1012        Ok(())
1013    }
1014
1015    #[pyo3(signature = (relations, bypass_plugins=false))]
1016    pub fn add_edges(
1017        &self,
1018        relations: Vec<(PyNodeIndex, PyNodeIndex, PyAttributes)>,
1019        bypass_plugins: bool,
1020    ) -> PyResult<Vec<EdgeIndex>> {
1021        let mut graphrecord = self.inner_mut()?;
1022
1023        if bypass_plugins {
1024            Ok(graphrecord
1025                .add_edges_bypass_plugins(relations.deep_into())
1026                .map_err(PyGraphRecordError::from)?)
1027        } else {
1028            Ok(graphrecord
1029                .add_edges(relations.deep_into())
1030                .map_err(PyGraphRecordError::from)?)
1031        }
1032    }
1033
1034    #[pyo3(signature = (relations, group, bypass_plugins=false))]
1035    pub fn add_edges_with_group(
1036        &self,
1037        relations: Vec<(PyNodeIndex, PyNodeIndex, PyAttributes)>,
1038        group: PyGroup,
1039        bypass_plugins: bool,
1040    ) -> PyResult<Vec<EdgeIndex>> {
1041        let mut graphrecord = self.inner_mut()?;
1042
1043        if bypass_plugins {
1044            Ok(graphrecord
1045                .add_edges_with_group_bypass_plugins(relations.deep_into(), &group)
1046                .map_err(PyGraphRecordError::from)?)
1047        } else {
1048            Ok(graphrecord
1049                .add_edges_with_group(relations.deep_into(), &group)
1050                .map_err(PyGraphRecordError::from)?)
1051        }
1052    }
1053
1054    #[pyo3(signature = (relations, groups, bypass_plugins=false))]
1055    pub fn add_edges_with_groups(
1056        &self,
1057        relations: Vec<(PyNodeIndex, PyNodeIndex, PyAttributes)>,
1058        groups: Vec<PyGroup>,
1059        bypass_plugins: bool,
1060    ) -> PyResult<Vec<EdgeIndex>> {
1061        let mut graphrecord = self.inner_mut()?;
1062        let groups: Vec<Group> = groups.deep_into();
1063
1064        if bypass_plugins {
1065            Ok(graphrecord
1066                .add_edges_with_groups_bypass_plugins(relations.deep_into(), &groups)
1067                .map_err(PyGraphRecordError::from)?)
1068        } else {
1069            Ok(graphrecord
1070                .add_edges_with_groups(relations.deep_into(), &groups)
1071                .map_err(PyGraphRecordError::from)?)
1072        }
1073    }
1074
1075    #[pyo3(signature = (source_node_index, target_node_index, attributes, groups, bypass_plugins=false))]
1076    pub fn add_edge_with_groups(
1077        &self,
1078        source_node_index: PyNodeIndex,
1079        target_node_index: PyNodeIndex,
1080        attributes: PyAttributes,
1081        groups: Vec<PyGroup>,
1082        bypass_plugins: bool,
1083    ) -> PyResult<EdgeIndex> {
1084        let mut graphrecord = self.inner_mut()?;
1085        let groups: Vec<graphrecords_core::graphrecord::Group> = groups.deep_into();
1086
1087        if bypass_plugins {
1088            Ok(graphrecord
1089                .add_edge_with_groups_bypass_plugins(
1090                    source_node_index.into(),
1091                    target_node_index.into(),
1092                    attributes.deep_into(),
1093                    &groups,
1094                )
1095                .map_err(PyGraphRecordError::from)?)
1096        } else {
1097            Ok(graphrecord
1098                .add_edge_with_groups(
1099                    source_node_index.into(),
1100                    target_node_index.into(),
1101                    attributes.deep_into(),
1102                    &groups,
1103                )
1104                .map_err(PyGraphRecordError::from)?)
1105        }
1106    }
1107
1108    #[pyo3(signature = (edges_dataframes, bypass_plugins=false))]
1109    pub fn add_edges_dataframes(
1110        &self,
1111        edges_dataframes: Vec<(PyDataFrame, String, String)>,
1112        bypass_plugins: bool,
1113    ) -> PyResult<Vec<EdgeIndex>> {
1114        let mut graphrecord = self.inner_mut()?;
1115
1116        if bypass_plugins {
1117            Ok(graphrecord
1118                .add_edges_dataframes_bypass_plugins(edges_dataframes)
1119                .map_err(PyGraphRecordError::from)?)
1120        } else {
1121            Ok(graphrecord
1122                .add_edges_dataframes(edges_dataframes)
1123                .map_err(PyGraphRecordError::from)?)
1124        }
1125    }
1126
1127    #[pyo3(signature = (edges_dataframes, group, bypass_plugins=false))]
1128    pub fn add_edges_dataframes_with_group(
1129        &self,
1130        edges_dataframes: Vec<(PyDataFrame, String, String)>,
1131        group: PyGroup,
1132        bypass_plugins: bool,
1133    ) -> PyResult<Vec<EdgeIndex>> {
1134        let mut graphrecord = self.inner_mut()?;
1135
1136        if bypass_plugins {
1137            Ok(graphrecord
1138                .add_edges_dataframes_with_group_bypass_plugins(edges_dataframes, &group)
1139                .map_err(PyGraphRecordError::from)?)
1140        } else {
1141            Ok(graphrecord
1142                .add_edges_dataframes_with_group(edges_dataframes, &group)
1143                .map_err(PyGraphRecordError::from)?)
1144        }
1145    }
1146
1147    #[pyo3(signature = (edges_dataframes, groups, bypass_plugins=false))]
1148    pub fn add_edges_dataframes_with_groups(
1149        &self,
1150        edges_dataframes: Vec<(PyDataFrame, String, String)>,
1151        groups: Vec<PyGroup>,
1152        bypass_plugins: bool,
1153    ) -> PyResult<Vec<EdgeIndex>> {
1154        let mut graphrecord = self.inner_mut()?;
1155        let groups: Vec<Group> = groups.deep_into();
1156        let edges_dataframes: Vec<EdgeDataFrameInput> =
1157            edges_dataframes.into_iter().map(Into::into).collect();
1158
1159        if bypass_plugins {
1160            Ok(graphrecord
1161                .add_edges_dataframes_with_groups_bypass_plugins(edges_dataframes, &groups)
1162                .map_err(PyGraphRecordError::from)?)
1163        } else {
1164            Ok(graphrecord
1165                .add_edges_dataframes_with_groups(edges_dataframes, &groups)
1166                .map_err(PyGraphRecordError::from)?)
1167        }
1168    }
1169
1170    #[pyo3(signature = (group, node_indices_to_add=None, edge_indices_to_add=None, bypass_plugins=false))]
1171    pub fn add_group(
1172        &self,
1173        group: PyGroup,
1174        node_indices_to_add: Option<Vec<PyNodeIndex>>,
1175        edge_indices_to_add: Option<Vec<EdgeIndex>>,
1176        bypass_plugins: bool,
1177    ) -> PyResult<()> {
1178        let mut graphrecord = self.inner_mut()?;
1179
1180        if bypass_plugins {
1181            Ok(graphrecord
1182                .add_group_bypass_plugins(
1183                    group.into(),
1184                    node_indices_to_add.deep_into(),
1185                    edge_indices_to_add,
1186                )
1187                .map_err(PyGraphRecordError::from)?)
1188        } else {
1189            Ok(graphrecord
1190                .add_group(
1191                    group.into(),
1192                    node_indices_to_add.deep_into(),
1193                    edge_indices_to_add,
1194                )
1195                .map_err(PyGraphRecordError::from)?)
1196        }
1197    }
1198
1199    #[pyo3(signature = (group, bypass_plugins=false))]
1200    pub fn remove_groups(&self, group: Vec<PyGroup>, bypass_plugins: bool) -> PyResult<()> {
1201        let mut graphrecord = self.inner_mut()?;
1202
1203        if bypass_plugins {
1204            group.into_iter().try_for_each(|group| {
1205                graphrecord
1206                    .remove_group_bypass_plugins(&group)
1207                    .map_err(PyGraphRecordError::from)?;
1208                Ok(())
1209            })
1210        } else {
1211            group.into_iter().try_for_each(|group| {
1212                graphrecord
1213                    .remove_group(&group)
1214                    .map_err(PyGraphRecordError::from)?;
1215                Ok(())
1216            })
1217        }
1218    }
1219
1220    #[pyo3(signature = (group, node_index, bypass_plugins=false))]
1221    pub fn add_nodes_to_group(
1222        &self,
1223        group: PyGroup,
1224        node_index: Vec<PyNodeIndex>,
1225        bypass_plugins: bool,
1226    ) -> PyResult<()> {
1227        let mut graphrecord = self.inner_mut()?;
1228
1229        if bypass_plugins {
1230            node_index.into_iter().try_for_each(|node_index| {
1231                Ok(graphrecord
1232                    .add_node_to_group_bypass_plugins(group.clone().into(), node_index.into())
1233                    .map_err(PyGraphRecordError::from)?)
1234            })
1235        } else {
1236            node_index.into_iter().try_for_each(|node_index| {
1237                Ok(graphrecord
1238                    .add_node_to_group(group.clone().into(), node_index.into())
1239                    .map_err(PyGraphRecordError::from)?)
1240            })
1241        }
1242    }
1243
1244    #[pyo3(signature = (node_index, groups, bypass_plugins=false))]
1245    pub fn add_node_to_groups(
1246        &self,
1247        node_index: PyNodeIndex,
1248        groups: Vec<PyGroup>,
1249        bypass_plugins: bool,
1250    ) -> PyResult<()> {
1251        let mut graphrecord = self.inner_mut()?;
1252        let groups: Vec<Group> = groups.deep_into();
1253
1254        if bypass_plugins {
1255            graphrecord
1256                .add_node_to_groups_bypass_plugins(&groups, node_index.into())
1257                .map_err(PyGraphRecordError::from)?;
1258        } else {
1259            graphrecord
1260                .add_node_to_groups(&groups, node_index.into())
1261                .map_err(PyGraphRecordError::from)?;
1262        }
1263
1264        Ok(())
1265    }
1266
1267    #[pyo3(signature = (node_indices, groups, bypass_plugins=false))]
1268    pub fn add_nodes_to_groups(
1269        &self,
1270        node_indices: Vec<PyNodeIndex>,
1271        groups: Vec<PyGroup>,
1272        bypass_plugins: bool,
1273    ) -> PyResult<()> {
1274        let mut graphrecord = self.inner_mut()?;
1275        let groups: Vec<Group> = groups.deep_into();
1276
1277        if bypass_plugins {
1278            graphrecord
1279                .add_nodes_to_groups_bypass_plugins(&groups, node_indices.deep_into())
1280                .map_err(PyGraphRecordError::from)?;
1281        } else {
1282            graphrecord
1283                .add_nodes_to_groups(&groups, node_indices.deep_into())
1284                .map_err(PyGraphRecordError::from)?;
1285        }
1286
1287        Ok(())
1288    }
1289
1290    #[pyo3(signature = (group, edge_index, bypass_plugins=false))]
1291    pub fn add_edges_to_group(
1292        &self,
1293        group: PyGroup,
1294        edge_index: Vec<EdgeIndex>,
1295        bypass_plugins: bool,
1296    ) -> PyResult<()> {
1297        let mut graphrecord = self.inner_mut()?;
1298
1299        if bypass_plugins {
1300            edge_index.into_iter().try_for_each(|edge_index| {
1301                Ok(graphrecord
1302                    .add_edge_to_group_bypass_plugins(group.clone().into(), edge_index)
1303                    .map_err(PyGraphRecordError::from)?)
1304            })
1305        } else {
1306            edge_index.into_iter().try_for_each(|edge_index| {
1307                Ok(graphrecord
1308                    .add_edge_to_group(group.clone().into(), edge_index)
1309                    .map_err(PyGraphRecordError::from)?)
1310            })
1311        }
1312    }
1313
1314    #[pyo3(signature = (edge_index, groups, bypass_plugins=false))]
1315    pub fn add_edge_to_groups(
1316        &self,
1317        edge_index: EdgeIndex,
1318        groups: Vec<PyGroup>,
1319        bypass_plugins: bool,
1320    ) -> PyResult<()> {
1321        let mut graphrecord = self.inner_mut()?;
1322        let groups: Vec<Group> = groups.deep_into();
1323
1324        if bypass_plugins {
1325            graphrecord
1326                .add_edge_to_groups_bypass_plugins(&groups, edge_index)
1327                .map_err(PyGraphRecordError::from)?;
1328        } else {
1329            graphrecord
1330                .add_edge_to_groups(&groups, edge_index)
1331                .map_err(PyGraphRecordError::from)?;
1332        }
1333
1334        Ok(())
1335    }
1336
1337    #[pyo3(signature = (edge_indices, groups, bypass_plugins=false))]
1338    pub fn add_edges_to_groups(
1339        &self,
1340        edge_indices: Vec<EdgeIndex>,
1341        groups: Vec<PyGroup>,
1342        bypass_plugins: bool,
1343    ) -> PyResult<()> {
1344        let mut graphrecord = self.inner_mut()?;
1345        let groups: Vec<Group> = groups.deep_into();
1346
1347        if bypass_plugins {
1348            graphrecord
1349                .add_edges_to_groups_bypass_plugins(&groups, edge_indices)
1350                .map_err(PyGraphRecordError::from)?;
1351        } else {
1352            graphrecord
1353                .add_edges_to_groups(&groups, edge_indices)
1354                .map_err(PyGraphRecordError::from)?;
1355        }
1356
1357        Ok(())
1358    }
1359
1360    #[pyo3(signature = (group, node_index, bypass_plugins=false))]
1361    pub fn remove_nodes_from_group(
1362        &self,
1363        group: PyGroup,
1364        node_index: Vec<PyNodeIndex>,
1365        bypass_plugins: bool,
1366    ) -> PyResult<()> {
1367        let mut graphrecord = self.inner_mut()?;
1368
1369        if bypass_plugins {
1370            node_index.into_iter().try_for_each(|node_index| {
1371                Ok(graphrecord
1372                    .remove_node_from_group_bypass_plugins(&group, &node_index)
1373                    .map_err(PyGraphRecordError::from)?)
1374            })
1375        } else {
1376            node_index.into_iter().try_for_each(|node_index| {
1377                Ok(graphrecord
1378                    .remove_node_from_group(&group, &node_index)
1379                    .map_err(PyGraphRecordError::from)?)
1380            })
1381        }
1382    }
1383
1384    #[pyo3(signature = (node_index, groups, bypass_plugins=false))]
1385    pub fn remove_node_from_groups(
1386        &self,
1387        node_index: PyNodeIndex,
1388        groups: Vec<PyGroup>,
1389        bypass_plugins: bool,
1390    ) -> PyResult<()> {
1391        let mut graphrecord = self.inner_mut()?;
1392        let groups: Vec<Group> = groups.deep_into();
1393        let node_index: NodeIndex = node_index.into();
1394
1395        if bypass_plugins {
1396            graphrecord
1397                .remove_node_from_groups_bypass_plugins(&groups, &node_index)
1398                .map_err(PyGraphRecordError::from)?;
1399        } else {
1400            graphrecord
1401                .remove_node_from_groups(&groups, &node_index)
1402                .map_err(PyGraphRecordError::from)?;
1403        }
1404
1405        Ok(())
1406    }
1407
1408    #[pyo3(signature = (node_indices, groups, bypass_plugins=false))]
1409    pub fn remove_nodes_from_groups(
1410        &self,
1411        node_indices: Vec<PyNodeIndex>,
1412        groups: Vec<PyGroup>,
1413        bypass_plugins: bool,
1414    ) -> PyResult<()> {
1415        let mut graphrecord = self.inner_mut()?;
1416        let groups: Vec<Group> = groups.deep_into();
1417        let node_indices: Vec<NodeIndex> = node_indices.deep_into();
1418
1419        if bypass_plugins {
1420            graphrecord
1421                .remove_nodes_from_groups_bypass_plugins(&groups, &node_indices)
1422                .map_err(PyGraphRecordError::from)?;
1423        } else {
1424            graphrecord
1425                .remove_nodes_from_groups(&groups, &node_indices)
1426                .map_err(PyGraphRecordError::from)?;
1427        }
1428
1429        Ok(())
1430    }
1431
1432    #[pyo3(signature = (group, edge_index, bypass_plugins=false))]
1433    pub fn remove_edges_from_group(
1434        &self,
1435        group: PyGroup,
1436        edge_index: Vec<EdgeIndex>,
1437        bypass_plugins: bool,
1438    ) -> PyResult<()> {
1439        let mut graphrecord = self.inner_mut()?;
1440
1441        if bypass_plugins {
1442            edge_index.into_iter().try_for_each(|edge_index| {
1443                Ok(graphrecord
1444                    .remove_edge_from_group_bypass_plugins(&group, &edge_index)
1445                    .map_err(PyGraphRecordError::from)?)
1446            })
1447        } else {
1448            edge_index.into_iter().try_for_each(|edge_index| {
1449                Ok(graphrecord
1450                    .remove_edge_from_group(&group, &edge_index)
1451                    .map_err(PyGraphRecordError::from)?)
1452            })
1453        }
1454    }
1455
1456    #[pyo3(signature = (edge_index, groups, bypass_plugins=false))]
1457    pub fn remove_edge_from_groups(
1458        &self,
1459        edge_index: EdgeIndex,
1460        groups: Vec<PyGroup>,
1461        bypass_plugins: bool,
1462    ) -> PyResult<()> {
1463        let mut graphrecord = self.inner_mut()?;
1464        let groups: Vec<Group> = groups.deep_into();
1465
1466        if bypass_plugins {
1467            graphrecord
1468                .remove_edge_from_groups_bypass_plugins(&groups, &edge_index)
1469                .map_err(PyGraphRecordError::from)?;
1470        } else {
1471            graphrecord
1472                .remove_edge_from_groups(&groups, &edge_index)
1473                .map_err(PyGraphRecordError::from)?;
1474        }
1475
1476        Ok(())
1477    }
1478
1479    #[pyo3(signature = (edge_indices, groups, bypass_plugins=false))]
1480    pub fn remove_edges_from_groups(
1481        &self,
1482        edge_indices: Vec<EdgeIndex>,
1483        groups: Vec<PyGroup>,
1484        bypass_plugins: bool,
1485    ) -> PyResult<()> {
1486        let mut graphrecord = self.inner_mut()?;
1487        let groups: Vec<Group> = groups.deep_into();
1488
1489        if bypass_plugins {
1490            graphrecord
1491                .remove_edges_from_groups_bypass_plugins(&groups, &edge_indices)
1492                .map_err(PyGraphRecordError::from)?;
1493        } else {
1494            graphrecord
1495                .remove_edges_from_groups(&groups, &edge_indices)
1496                .map_err(PyGraphRecordError::from)?;
1497        }
1498
1499        Ok(())
1500    }
1501
1502    pub fn nodes_in_group(
1503        &self,
1504        group: Vec<PyGroup>,
1505    ) -> PyResult<HashMap<PyGroup, Vec<PyNodeIndex>>> {
1506        let graphrecord = self.inner()?;
1507
1508        group
1509            .into_iter()
1510            .map(|group| {
1511                let nodes_attributes = graphrecord
1512                    .nodes_in_group(&group)
1513                    .map_err(PyGraphRecordError::from)?
1514                    .map(|node_index| node_index.clone().into())
1515                    .collect();
1516
1517                Ok((group, nodes_attributes))
1518            })
1519            .collect()
1520    }
1521
1522    pub fn ungrouped_nodes(&self) -> PyResult<Vec<PyNodeIndex>> {
1523        Ok(self
1524            .inner()?
1525            .ungrouped_nodes()
1526            .map(|node_index| node_index.clone().into())
1527            .collect())
1528    }
1529
1530    pub fn edges_in_group(
1531        &self,
1532        group: Vec<PyGroup>,
1533    ) -> PyResult<HashMap<PyGroup, Vec<EdgeIndex>>> {
1534        let graphrecord = self.inner()?;
1535
1536        group
1537            .into_iter()
1538            .map(|group| {
1539                let edges = graphrecord
1540                    .edges_in_group(&group)
1541                    .map_err(PyGraphRecordError::from)?
1542                    .copied()
1543                    .collect();
1544
1545                Ok((group, edges))
1546            })
1547            .collect()
1548    }
1549
1550    pub fn ungrouped_edges(&self) -> PyResult<Vec<EdgeIndex>> {
1551        Ok(self.inner()?.ungrouped_edges().copied().collect())
1552    }
1553
1554    pub fn groups_of_node(
1555        &self,
1556        node_index: Vec<PyNodeIndex>,
1557    ) -> PyResult<HashMap<PyNodeIndex, Vec<PyGroup>>> {
1558        let graphrecord = self.inner()?;
1559
1560        node_index
1561            .into_iter()
1562            .map(|node_index| {
1563                let groups = graphrecord
1564                    .groups_of_node(&node_index)
1565                    .map_err(PyGraphRecordError::from)?
1566                    .map(|node_index| node_index.clone().into())
1567                    .collect();
1568
1569                Ok((node_index, groups))
1570            })
1571            .collect()
1572    }
1573
1574    pub fn groups_of_edge(
1575        &self,
1576        edge_index: Vec<EdgeIndex>,
1577    ) -> PyResult<HashMap<EdgeIndex, Vec<PyGroup>>> {
1578        let graphrecord = self.inner()?;
1579
1580        edge_index
1581            .into_iter()
1582            .map(|edge_index| {
1583                let groups = graphrecord
1584                    .groups_of_edge(&edge_index)
1585                    .map_err(PyGraphRecordError::from)?
1586                    .map(|group| group.clone().into())
1587                    .collect();
1588
1589                Ok((edge_index, groups))
1590            })
1591            .collect()
1592    }
1593
1594    pub fn node_count(&self) -> PyResult<usize> {
1595        Ok(self.inner()?.node_count())
1596    }
1597
1598    pub fn edge_count(&self) -> PyResult<usize> {
1599        Ok(self.inner()?.edge_count())
1600    }
1601
1602    pub fn group_count(&self) -> PyResult<usize> {
1603        Ok(self.inner()?.group_count())
1604    }
1605
1606    pub fn contains_node(&self, node_index: PyNodeIndex) -> PyResult<bool> {
1607        Ok(self.inner()?.contains_node(&node_index.into()))
1608    }
1609
1610    pub fn contains_edge(&self, edge_index: EdgeIndex) -> PyResult<bool> {
1611        Ok(self.inner()?.contains_edge(&edge_index))
1612    }
1613
1614    pub fn contains_group(&self, group: PyGroup) -> PyResult<bool> {
1615        Ok(self.inner()?.contains_group(&group.into()))
1616    }
1617
1618    pub fn neighbors_outgoing(
1619        &self,
1620        node_indices: Vec<PyNodeIndex>,
1621    ) -> PyResult<HashMap<PyNodeIndex, Vec<PyNodeIndex>>> {
1622        let graphrecord = self.inner()?;
1623
1624        node_indices
1625            .into_iter()
1626            .map(|node_index| {
1627                let neighbors = graphrecord
1628                    .neighbors_outgoing(&node_index)
1629                    .map_err(PyGraphRecordError::from)?
1630                    .map(|neighbor| neighbor.clone().into())
1631                    .collect();
1632
1633                Ok((node_index, neighbors))
1634            })
1635            .collect()
1636    }
1637
1638    pub fn neighbors_incoming(
1639        &self,
1640        node_indices: Vec<PyNodeIndex>,
1641    ) -> PyResult<HashMap<PyNodeIndex, Vec<PyNodeIndex>>> {
1642        let graphrecord = self.inner()?;
1643
1644        node_indices
1645            .into_iter()
1646            .map(|node_index| {
1647                let neighbors = graphrecord
1648                    .neighbors_incoming(&node_index)
1649                    .map_err(PyGraphRecordError::from)?
1650                    .map(|neighbor| neighbor.clone().into())
1651                    .collect();
1652
1653                Ok((node_index, neighbors))
1654            })
1655            .collect()
1656    }
1657
1658    pub fn neighbors_undirected(
1659        &self,
1660        node_indices: Vec<PyNodeIndex>,
1661    ) -> PyResult<HashMap<PyNodeIndex, Vec<PyNodeIndex>>> {
1662        let graphrecord = self.inner()?;
1663
1664        node_indices
1665            .into_iter()
1666            .map(|node_index| {
1667                let neighbors = graphrecord
1668                    .neighbors_undirected(&node_index)
1669                    .map_err(PyGraphRecordError::from)?
1670                    .map(|neighbor| neighbor.clone().into())
1671                    .collect();
1672
1673                Ok((node_index, neighbors))
1674            })
1675            .collect()
1676    }
1677
1678    #[pyo3(signature = (bypass_plugins=false))]
1679    pub fn clear(&self, bypass_plugins: bool) -> PyResult<()> {
1680        let mut graphrecord = self.inner_mut()?;
1681
1682        if bypass_plugins {
1683            Ok(graphrecord
1684                .clear_bypass_plugins()
1685                .map_err(PyGraphRecordError::from)?)
1686        } else {
1687            Ok(graphrecord.clear().map_err(PyGraphRecordError::from)?)
1688        }
1689    }
1690
1691    /// # Panics
1692    ///
1693    /// Panics if the python typing was not followed.
1694    pub fn query_nodes(
1695        &self,
1696        py: Python<'_>,
1697        query: &Bound<'_, PyFunction>,
1698    ) -> PyResult<Py<PyAny>> {
1699        let graphrecord = self.inner()?;
1700
1701        let result = graphrecord
1702            .query_nodes(|nodes| {
1703                let result = query
1704                    .call1((PyNodeOperand::from(nodes.clone()),))
1705                    .expect("Call should succeed");
1706
1707                result
1708                    .extract::<PyReturnOperand>()
1709                    .expect("Extraction must succeed")
1710            })
1711            .evaluate()
1712            .map_err(PyGraphRecordError::from)?;
1713
1714        Ok(result.into_pyobject(py)?.unbind())
1715    }
1716
1717    /// # Panics
1718    ///
1719    /// Panics if the python typing was not followed.
1720    pub fn query_edges(
1721        &self,
1722        py: Python<'_>,
1723        query: &Bound<'_, PyFunction>,
1724    ) -> PyResult<Py<PyAny>> {
1725        let graphrecord = self.inner()?;
1726
1727        let result = graphrecord
1728            .query_edges(|edges| {
1729                let result = query
1730                    .call1((PyEdgeOperand::from(edges.clone()),))
1731                    .expect("Call should succeed");
1732
1733                result
1734                    .extract::<PyReturnOperand>()
1735                    .expect("Extraction must succeed")
1736            })
1737            .evaluate()
1738            .map_err(PyGraphRecordError::from)?;
1739
1740        Ok(result.into_pyobject(py)?.unbind())
1741    }
1742
1743    #[allow(clippy::should_implement_trait)]
1744    pub fn clone(&self) -> Self {
1745        Clone::clone(self)
1746    }
1747
1748    pub fn overview(&self, truncate_details: Option<usize>) -> PyResult<PyOverview> {
1749        Ok(self
1750            .inner()?
1751            .overview(truncate_details)
1752            .map_err(PyGraphRecordError::from)?
1753            .into())
1754    }
1755
1756    pub fn group_overview(
1757        &self,
1758        group: PyGroup,
1759        truncate_details: Option<usize>,
1760    ) -> PyResult<PyGroupOverview> {
1761        Ok(self
1762            .inner()?
1763            .group_overview(&group.into(), truncate_details)
1764            .map_err(PyGraphRecordError::from)?
1765            .into())
1766    }
1767}