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 datatype;
6pub mod errors;
7pub mod overview;
8pub mod plugins;
9pub mod querying;
10pub mod schema;
11pub mod traits;
12pub mod value;
13
14use crate::{
15    conversion_lut::ConversionLut,
16    graphrecord::{
17        overview::{PyGroupOverview, PyOverview},
18        plugins::PyPlugin,
19    },
20};
21use attribute::PyGraphRecordAttribute;
22use borrowed::BorrowedGraphRecord;
23use errors::PyGraphRecordError;
24use graphrecords_core::{
25    errors::GraphRecordError,
26    graphrecord::{
27        Attributes, EdgeIndex, GraphRecord, GraphRecordAttribute, GraphRecordValue,
28        PluginGraphRecord, plugins::Plugin,
29    },
30};
31use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
32use pyo3::{
33    exceptions::PyRuntimeError,
34    prelude::*,
35    types::{PyBytes, PyDict, PyFunction},
36};
37use pyo3_polars::PyDataFrame;
38use querying::{PyReturnOperand, edges::PyEdgeOperand, nodes::PyNodeOperand};
39use schema::PySchema;
40use std::{
41    collections::HashMap,
42    ops::{Deref, DerefMut},
43    ptr::NonNull,
44};
45use traits::DeepInto;
46use value::PyGraphRecordValue;
47
48pub type PyAttributes = HashMap<PyGraphRecordAttribute, PyGraphRecordValue>;
49pub type PyGroup = PyGraphRecordAttribute;
50pub type PyNodeIndex = PyGraphRecordAttribute;
51pub type PyEdgeIndex = EdgeIndex;
52type Lut<T> = ConversionLut<usize, fn(&Bound<'_, PyAny>) -> PyResult<T>>;
53
54#[pyclass(frozen)]
55#[derive(Debug)]
56pub struct PyGraphRecord {
57    inner: PyGraphRecordInner,
58}
59
60#[derive(Debug)]
61#[allow(clippy::large_enum_variant)]
62enum PyGraphRecordInner {
63    Owned(RwLock<PluginGraphRecord>),
64    Borrowed(BorrowedGraphRecord),
65}
66
67pub(crate) enum InnerRef<'a> {
68    Owned(RwLockReadGuard<'a, PluginGraphRecord>),
69    Borrowed(RwLockReadGuard<'a, Option<NonNull<PluginGraphRecord>>>),
70}
71
72impl Deref for InnerRef<'_> {
73    type Target = PluginGraphRecord;
74
75    fn deref(&self) -> &PluginGraphRecord {
76        match self {
77            InnerRef::Owned(guard) => guard,
78            // SAFETY: The guard is only constructed after checking `is_some()` in `inner()`.
79            // The pointer is valid for the duration of the `scope()` call because the scope's
80            // Drop guard needs a write lock to clear it, and this read guard prevents that.
81            InnerRef::Borrowed(guard) => unsafe {
82                guard
83                    .expect("Borrowed pointer must be Some when InnerRef is alive")
84                    .as_ref()
85            },
86        }
87    }
88}
89
90pub(crate) enum InnerRefMut<'a> {
91    Owned(RwLockWriteGuard<'a, PluginGraphRecord>),
92    Borrowed(RwLockWriteGuard<'a, Option<NonNull<PluginGraphRecord>>>),
93}
94
95impl Deref for InnerRefMut<'_> {
96    type Target = PluginGraphRecord;
97
98    fn deref(&self) -> &PluginGraphRecord {
99        match self {
100            InnerRefMut::Owned(guard) => guard,
101            // SAFETY: Same as `InnerRef::Borrowed`. Pointer was checked `is_some()` in
102            // `inner_mut()`, and the write guard keeps the scope's Drop from clearing it.
103            InnerRefMut::Borrowed(guard) => unsafe {
104                guard
105                    .expect("Borrowed pointer must be Some when InnerRefMut is alive")
106                    .as_ref()
107            },
108        }
109    }
110}
111
112impl DerefMut for InnerRefMut<'_> {
113    fn deref_mut(&mut self) -> &mut PluginGraphRecord {
114        match self {
115            InnerRefMut::Owned(guard) => &mut *guard,
116            // SAFETY: Same as above, plus: the write guard ensures exclusive access to the
117            // pointer, so creating `&mut PluginGraphRecord` is sound. The original `scope()` call
118            // holds `&mut PluginGraphRecord`, guaranteeing no other references to the pointee exist
119            // outside this lock.
120            InnerRefMut::Borrowed(guard) => unsafe {
121                guard
122                    .expect("Borrowed pointer must be Some when InnerRefMut is alive")
123                    .as_mut()
124            },
125        }
126    }
127}
128
129impl Clone for PyGraphRecord {
130    fn clone(&self) -> Self {
131        match &self.inner {
132            PyGraphRecordInner::Owned(lock) => Self {
133                inner: PyGraphRecordInner::Owned(RwLock::new(lock.read().clone())),
134            },
135            PyGraphRecordInner::Borrowed(_) => Self {
136                inner: PyGraphRecordInner::Borrowed(BorrowedGraphRecord::dead()),
137            },
138        }
139    }
140}
141
142impl PyGraphRecord {
143    pub(crate) fn inner(&self) -> PyResult<InnerRef<'_>> {
144        match &self.inner {
145            PyGraphRecordInner::Owned(lock) => Ok(InnerRef::Owned(lock.read())),
146            PyGraphRecordInner::Borrowed(borrowed) => {
147                let guard = borrowed.read();
148                if guard.is_some() {
149                    Ok(InnerRef::Borrowed(guard))
150                } else {
151                    Err(PyRuntimeError::new_err(
152                        "GraphRecord reference is no longer valid (used outside plugin callback scope)",
153                    ))
154                }
155            }
156        }
157    }
158
159    pub(crate) fn inner_mut(&self) -> PyResult<InnerRefMut<'_>> {
160        match &self.inner {
161            PyGraphRecordInner::Owned(lock) => Ok(InnerRefMut::Owned(lock.write())),
162            PyGraphRecordInner::Borrowed(borrowed) => {
163                let guard = borrowed.write();
164                if guard.is_some() {
165                    Ok(InnerRefMut::Borrowed(guard))
166                } else {
167                    Err(PyRuntimeError::new_err(
168                        "GraphRecord reference is no longer valid (used outside plugin callback scope)",
169                    ))
170                }
171            }
172        }
173    }
174}
175
176impl From<GraphRecord> for PyGraphRecord {
177    fn from(value: GraphRecord) -> Self {
178        Self {
179            inner: PyGraphRecordInner::Owned(RwLock::new(PluginGraphRecord::from(value))),
180        }
181    }
182}
183
184impl From<PluginGraphRecord> for PyGraphRecord {
185    fn from(value: PluginGraphRecord) -> Self {
186        Self {
187            inner: PyGraphRecordInner::Owned(RwLock::new(value)),
188        }
189    }
190}
191
192impl TryFrom<PyGraphRecord> for GraphRecord {
193    type Error = PyErr;
194
195    fn try_from(value: PyGraphRecord) -> PyResult<Self> {
196        match value.inner {
197            PyGraphRecordInner::Owned(lock) => Ok(lock.into_inner().into()),
198            PyGraphRecordInner::Borrowed(_) => Err(PyRuntimeError::new_err(
199                "Cannot convert a borrowed PyGraphRecord into an owned GraphRecord",
200            )),
201        }
202    }
203}
204
205#[pymethods]
206impl PyGraphRecord {
207    #[new]
208    pub fn new() -> Self {
209        GraphRecord::new().into()
210    }
211
212    pub fn _to_bytes<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
213        let bytes = bincode::serialize(&*self.inner()?)
214            .map_err(|_| {
215                GraphRecordError::ConversionError("Could not serialize GraphRecord".into())
216            })
217            .map_err(PyGraphRecordError::from)?;
218
219        Ok(PyBytes::new(py, &bytes))
220    }
221
222    #[staticmethod]
223    pub fn _from_bytes(data: &Bound<'_, PyBytes>) -> PyResult<Self> {
224        let graphrecord: GraphRecord = bincode::deserialize(data.as_bytes())
225            .map_err(|_| {
226                GraphRecordError::ConversionError("Could not deserialize GraphRecord".into())
227            })
228            .map_err(PyGraphRecordError::from)?;
229
230        Ok(graphrecord.into())
231    }
232
233    #[staticmethod]
234    pub fn with_schema(schema: PySchema) -> Self {
235        GraphRecord::with_schema(schema.into()).into()
236    }
237
238    #[staticmethod]
239    pub fn with_plugins(plugins: Vec<Py<PyAny>>) -> PyResult<Self> {
240        let plugins = plugins
241            .into_iter()
242            .map(|plugin| Box::new(PyPlugin::new(plugin)) as Box<dyn Plugin>)
243            .collect();
244
245        let graphrecord = GraphRecord::with_plugins(plugins).map_err(PyGraphRecordError::from)?;
246
247        Ok(graphrecord.into())
248    }
249
250    #[staticmethod]
251    #[pyo3(signature = (nodes, edges=None))]
252    pub fn from_tuples(
253        nodes: Vec<(PyNodeIndex, PyAttributes)>,
254        edges: Option<Vec<(PyNodeIndex, PyNodeIndex, PyAttributes)>>,
255    ) -> PyResult<Self> {
256        Ok(
257            GraphRecord::from_tuples(nodes.deep_into(), edges.deep_into(), None)
258                .map_err(PyGraphRecordError::from)?
259                .into(),
260        )
261    }
262
263    #[staticmethod]
264    pub fn from_dataframes(
265        nodes_dataframes: Vec<(PyDataFrame, String)>,
266        edges_dataframes: Vec<(PyDataFrame, String, String)>,
267    ) -> PyResult<Self> {
268        Ok(
269            GraphRecord::from_dataframes(nodes_dataframes, edges_dataframes, None)
270                .map_err(PyGraphRecordError::from)?
271                .into(),
272        )
273    }
274
275    #[staticmethod]
276    pub fn from_nodes_dataframes(nodes_dataframes: Vec<(PyDataFrame, String)>) -> PyResult<Self> {
277        Ok(GraphRecord::from_nodes_dataframes(nodes_dataframes, None)
278            .map_err(PyGraphRecordError::from)?
279            .into())
280    }
281
282    #[staticmethod]
283    pub fn from_ron(path: &str) -> PyResult<Self> {
284        Ok(GraphRecord::from_ron(path)
285            .map_err(PyGraphRecordError::from)?
286            .into())
287    }
288
289    pub fn to_ron(&self, path: &str) -> PyResult<()> {
290        Ok(self
291            .inner()?
292            .to_ron(path)
293            .map_err(PyGraphRecordError::from)?)
294    }
295
296    #[allow(clippy::missing_panics_doc, reason = "infallible")]
297    pub fn to_dataframes(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
298        let export = self
299            .inner()?
300            .to_dataframes()
301            .map_err(PyGraphRecordError::from)?;
302
303        let outer_dict = PyDict::new(py);
304        let inner_dict = PyDict::new(py);
305
306        for (group, group_export) in export.groups {
307            let group_dict = PyDict::new(py);
308
309            let nodes_df = PyDataFrame(group_export.nodes);
310            group_dict
311                .set_item("nodes", nodes_df)
312                .expect("Setting item must succeed");
313
314            let edges_df = PyDataFrame(group_export.edges);
315            group_dict
316                .set_item("edges", edges_df)
317                .expect("Setting item must succeed");
318
319            inner_dict
320                .set_item(PyGraphRecordAttribute::from(group), group_dict)
321                .expect("Setting item must succeed");
322        }
323
324        outer_dict
325            .set_item("groups", inner_dict)
326            .expect("Setting item must succeed");
327
328        let ungrouped_dict = PyDict::new(py);
329
330        let nodes_df = PyDataFrame(export.ungrouped.nodes);
331        ungrouped_dict
332            .set_item("nodes", nodes_df)
333            .expect("Setting item must succeed");
334
335        let edges_df = PyDataFrame(export.ungrouped.edges);
336        ungrouped_dict
337            .set_item("edges", edges_df)
338            .expect("Setting item must succeed");
339
340        outer_dict
341            .set_item("ungrouped", ungrouped_dict)
342            .expect("Setting item must succeed");
343
344        Ok(outer_dict.into())
345    }
346
347    pub fn get_schema(&self) -> PyResult<PySchema> {
348        Ok(self.inner()?.get_schema().clone().into())
349    }
350
351    #[pyo3(signature = (schema, bypass_plugins=false))]
352    pub fn set_schema(&self, schema: PySchema, bypass_plugins: bool) -> PyResult<()> {
353        let mut graphrecord = self.inner_mut()?;
354
355        if bypass_plugins {
356            Ok((**graphrecord)
357                .set_schema(schema.into())
358                .map_err(PyGraphRecordError::from)?)
359        } else {
360            Ok(graphrecord
361                .set_schema(schema.into())
362                .map_err(PyGraphRecordError::from)?)
363        }
364    }
365
366    #[pyo3(signature = (bypass_plugins=false))]
367    pub fn freeze_schema(&self, bypass_plugins: bool) -> PyResult<()> {
368        let mut graphrecord = self.inner_mut()?;
369
370        if bypass_plugins {
371            (**graphrecord).freeze_schema();
372            Ok(())
373        } else {
374            Ok(graphrecord
375                .freeze_schema()
376                .map_err(PyGraphRecordError::from)?)
377        }
378    }
379
380    #[pyo3(signature = (bypass_plugins=false))]
381    pub fn unfreeze_schema(&self, bypass_plugins: bool) -> PyResult<()> {
382        let mut graphrecord = self.inner_mut()?;
383
384        if bypass_plugins {
385            (**graphrecord).unfreeze_schema();
386            Ok(())
387        } else {
388            Ok(graphrecord
389                .unfreeze_schema()
390                .map_err(PyGraphRecordError::from)?)
391        }
392    }
393
394    #[getter]
395    pub fn nodes(&self) -> PyResult<Vec<PyNodeIndex>> {
396        Ok(self
397            .inner()?
398            .node_indices()
399            .map(|node_index| node_index.clone().into())
400            .collect())
401    }
402
403    pub fn node(
404        &self,
405        node_index: Vec<PyNodeIndex>,
406    ) -> PyResult<HashMap<PyNodeIndex, PyAttributes>> {
407        let graphrecord = self.inner()?;
408
409        node_index
410            .into_iter()
411            .map(|node_index| {
412                let node_attributes = graphrecord
413                    .node_attributes(&node_index)
414                    .map_err(PyGraphRecordError::from)?;
415
416                Ok((node_index, node_attributes.deep_into()))
417            })
418            .collect()
419    }
420
421    #[getter]
422    pub fn edges(&self) -> PyResult<Vec<EdgeIndex>> {
423        Ok(self.inner()?.edge_indices().copied().collect())
424    }
425
426    pub fn edge(&self, edge_index: Vec<EdgeIndex>) -> PyResult<HashMap<EdgeIndex, PyAttributes>> {
427        let graphrecord = self.inner()?;
428
429        edge_index
430            .into_iter()
431            .map(|edge_index| {
432                let edge_attributes = graphrecord
433                    .edge_attributes(&edge_index)
434                    .map_err(PyGraphRecordError::from)?;
435
436                Ok((edge_index, edge_attributes.deep_into()))
437            })
438            .collect()
439    }
440
441    #[getter]
442    pub fn groups(&self) -> PyResult<Vec<PyGroup>> {
443        Ok(self
444            .inner()?
445            .groups()
446            .map(|group| group.clone().into())
447            .collect())
448    }
449
450    pub fn outgoing_edges(
451        &self,
452        node_index: Vec<PyNodeIndex>,
453    ) -> PyResult<HashMap<PyNodeIndex, Vec<EdgeIndex>>> {
454        let graphrecord = self.inner()?;
455
456        node_index
457            .into_iter()
458            .map(|node_index| {
459                let edges = graphrecord
460                    .outgoing_edges(&node_index)
461                    .map_err(PyGraphRecordError::from)?
462                    .copied()
463                    .collect();
464
465                Ok((node_index, edges))
466            })
467            .collect()
468    }
469
470    pub fn incoming_edges(
471        &self,
472        node_index: Vec<PyNodeIndex>,
473    ) -> PyResult<HashMap<PyNodeIndex, Vec<EdgeIndex>>> {
474        let graphrecord = self.inner()?;
475
476        node_index
477            .into_iter()
478            .map(|node_index| {
479                let edges = graphrecord
480                    .incoming_edges(&node_index)
481                    .map_err(PyGraphRecordError::from)?
482                    .copied()
483                    .collect();
484
485                Ok((node_index, edges))
486            })
487            .collect()
488    }
489
490    pub fn edge_endpoints(
491        &self,
492        edge_index: Vec<EdgeIndex>,
493    ) -> PyResult<HashMap<EdgeIndex, (PyNodeIndex, PyNodeIndex)>> {
494        let graphrecord = self.inner()?;
495
496        edge_index
497            .into_iter()
498            .map(|edge_index| {
499                let edge_endpoints = graphrecord
500                    .edge_endpoints(&edge_index)
501                    .map_err(PyGraphRecordError::from)?;
502
503                Ok((
504                    edge_index,
505                    (
506                        edge_endpoints.0.clone().into(),
507                        edge_endpoints.1.clone().into(),
508                    ),
509                ))
510            })
511            .collect()
512    }
513
514    pub fn edges_connecting(
515        &self,
516        source_node_indices: Vec<PyNodeIndex>,
517        target_node_indices: Vec<PyNodeIndex>,
518    ) -> PyResult<Vec<EdgeIndex>> {
519        let source_node_indices: Vec<GraphRecordAttribute> = source_node_indices.deep_into();
520        let target_node_indices: Vec<GraphRecordAttribute> = target_node_indices.deep_into();
521
522        Ok(self
523            .inner()?
524            .edges_connecting(
525                source_node_indices.iter().collect(),
526                target_node_indices.iter().collect(),
527            )
528            .copied()
529            .collect())
530    }
531
532    pub fn edges_connecting_undirected(
533        &self,
534        first_node_indices: Vec<PyNodeIndex>,
535        second_node_indices: Vec<PyNodeIndex>,
536    ) -> PyResult<Vec<EdgeIndex>> {
537        let first_node_indices: Vec<GraphRecordAttribute> = first_node_indices.deep_into();
538        let second_node_indices: Vec<GraphRecordAttribute> = second_node_indices.deep_into();
539
540        Ok(self
541            .inner()?
542            .edges_connecting_undirected(
543                first_node_indices.iter().collect(),
544                second_node_indices.iter().collect(),
545            )
546            .copied()
547            .collect())
548    }
549
550    #[pyo3(signature = (node_indices, bypass_plugins=false))]
551    pub fn remove_nodes(
552        &self,
553        node_indices: Vec<PyNodeIndex>,
554        bypass_plugins: bool,
555    ) -> PyResult<HashMap<PyNodeIndex, PyAttributes>> {
556        let mut graphrecord = self.inner_mut()?;
557
558        if bypass_plugins {
559            node_indices
560                .into_iter()
561                .map(|node_index| {
562                    let attributes = (**graphrecord)
563                        .remove_node(&node_index)
564                        .map_err(PyGraphRecordError::from)?;
565                    Ok((node_index, attributes.deep_into()))
566                })
567                .collect()
568        } else {
569            node_indices
570                .into_iter()
571                .map(|node_index| {
572                    let attributes = graphrecord
573                        .remove_node(&node_index)
574                        .map_err(PyGraphRecordError::from)?;
575                    Ok((node_index, attributes.deep_into()))
576                })
577                .collect()
578        }
579    }
580
581    pub fn replace_node_attributes(
582        &self,
583        node_indices: Vec<PyNodeIndex>,
584        attributes: PyAttributes,
585    ) -> PyResult<()> {
586        let mut graphrecord = self.inner_mut()?;
587
588        let attributes: Attributes = attributes.deep_into();
589
590        for node_index in node_indices {
591            let mut current_attributes = graphrecord
592                .node_attributes_mut(&node_index)
593                .map_err(PyGraphRecordError::from)?;
594
595            current_attributes
596                .replace_attributes(attributes.clone())
597                .map_err(PyGraphRecordError::from)?;
598        }
599
600        Ok(())
601    }
602
603    pub fn update_node_attribute(
604        &self,
605        node_indices: Vec<PyNodeIndex>,
606        attribute: PyGraphRecordAttribute,
607        value: PyGraphRecordValue,
608    ) -> PyResult<()> {
609        let mut graphrecord = self.inner_mut()?;
610
611        let attribute: GraphRecordAttribute = attribute.into();
612        let value: GraphRecordValue = value.into();
613
614        for node_index in node_indices {
615            let mut node_attributes = graphrecord
616                .node_attributes_mut(&node_index)
617                .map_err(PyGraphRecordError::from)?;
618
619            node_attributes
620                .update_attribute(&attribute, value.clone())
621                .map_err(PyGraphRecordError::from)?;
622        }
623
624        Ok(())
625    }
626
627    pub fn remove_node_attribute(
628        &self,
629        node_indices: Vec<PyNodeIndex>,
630        attribute: PyGraphRecordAttribute,
631    ) -> PyResult<()> {
632        let mut graphrecord = self.inner_mut()?;
633
634        let attribute: GraphRecordAttribute = attribute.into();
635
636        for node_index in node_indices {
637            let mut node_attributes = graphrecord
638                .node_attributes_mut(&node_index)
639                .map_err(PyGraphRecordError::from)?;
640
641            node_attributes
642                .remove_attribute(&attribute)
643                .map_err(PyGraphRecordError::from)?;
644        }
645
646        Ok(())
647    }
648
649    #[pyo3(signature = (nodes, bypass_plugins=false))]
650    pub fn add_nodes(
651        &self,
652        nodes: Vec<(PyNodeIndex, PyAttributes)>,
653        bypass_plugins: bool,
654    ) -> PyResult<()> {
655        let mut graphrecord = self.inner_mut()?;
656
657        if bypass_plugins {
658            Ok((**graphrecord)
659                .add_nodes(nodes.deep_into())
660                .map_err(PyGraphRecordError::from)?)
661        } else {
662            Ok(graphrecord
663                .add_nodes(nodes.deep_into())
664                .map_err(PyGraphRecordError::from)?)
665        }
666    }
667
668    #[pyo3(signature = (nodes, group, bypass_plugins=false))]
669    pub fn add_nodes_with_group(
670        &self,
671        nodes: Vec<(PyNodeIndex, PyAttributes)>,
672        group: PyGroup,
673        bypass_plugins: bool,
674    ) -> PyResult<()> {
675        let mut graphrecord = self.inner_mut()?;
676
677        if bypass_plugins {
678            Ok((**graphrecord)
679                .add_nodes_with_group(nodes.deep_into(), group.into())
680                .map_err(PyGraphRecordError::from)?)
681        } else {
682            Ok(graphrecord
683                .add_nodes_with_group(nodes.deep_into(), group.into())
684                .map_err(PyGraphRecordError::from)?)
685        }
686    }
687
688    #[pyo3(signature = (nodes_dataframes, bypass_plugins=false))]
689    pub fn add_nodes_dataframes(
690        &self,
691        nodes_dataframes: Vec<(PyDataFrame, String)>,
692        bypass_plugins: bool,
693    ) -> PyResult<()> {
694        let mut graphrecord = self.inner_mut()?;
695
696        if bypass_plugins {
697            Ok((**graphrecord)
698                .add_nodes_dataframes(nodes_dataframes)
699                .map_err(PyGraphRecordError::from)?)
700        } else {
701            Ok(graphrecord
702                .add_nodes_dataframes(nodes_dataframes)
703                .map_err(PyGraphRecordError::from)?)
704        }
705    }
706
707    #[pyo3(signature = (nodes_dataframes, group, bypass_plugins=false))]
708    pub fn add_nodes_dataframes_with_group(
709        &self,
710        nodes_dataframes: Vec<(PyDataFrame, String)>,
711        group: PyGroup,
712        bypass_plugins: bool,
713    ) -> PyResult<()> {
714        let mut graphrecord = self.inner_mut()?;
715
716        if bypass_plugins {
717            Ok((**graphrecord)
718                .add_nodes_dataframes_with_group(nodes_dataframes, group.into())
719                .map_err(PyGraphRecordError::from)?)
720        } else {
721            Ok(graphrecord
722                .add_nodes_dataframes_with_group(nodes_dataframes, group.into())
723                .map_err(PyGraphRecordError::from)?)
724        }
725    }
726
727    #[pyo3(signature = (edge_indices, bypass_plugins=false))]
728    pub fn remove_edges(
729        &self,
730        edge_indices: Vec<EdgeIndex>,
731        bypass_plugins: bool,
732    ) -> PyResult<HashMap<EdgeIndex, PyAttributes>> {
733        let mut graphrecord = self.inner_mut()?;
734
735        if bypass_plugins {
736            edge_indices
737                .into_iter()
738                .map(|edge_index| {
739                    let attributes = (**graphrecord)
740                        .remove_edge(&edge_index)
741                        .map_err(PyGraphRecordError::from)?;
742                    Ok((edge_index, attributes.deep_into()))
743                })
744                .collect()
745        } else {
746            edge_indices
747                .into_iter()
748                .map(|edge_index| {
749                    let attributes = graphrecord
750                        .remove_edge(&edge_index)
751                        .map_err(PyGraphRecordError::from)?;
752                    Ok((edge_index, attributes.deep_into()))
753                })
754                .collect()
755        }
756    }
757
758    pub fn replace_edge_attributes(
759        &self,
760        edge_indices: Vec<EdgeIndex>,
761        attributes: PyAttributes,
762    ) -> PyResult<()> {
763        let mut graphrecord = self.inner_mut()?;
764
765        let attributes: Attributes = attributes.deep_into();
766
767        for edge_index in edge_indices {
768            let mut current_attributes = graphrecord
769                .edge_attributes_mut(&edge_index)
770                .map_err(PyGraphRecordError::from)?;
771
772            current_attributes
773                .replace_attributes(attributes.clone())
774                .map_err(PyGraphRecordError::from)?;
775        }
776
777        Ok(())
778    }
779
780    pub fn update_edge_attribute(
781        &self,
782        edge_indices: Vec<EdgeIndex>,
783        attribute: PyGraphRecordAttribute,
784        value: PyGraphRecordValue,
785    ) -> PyResult<()> {
786        let mut graphrecord = self.inner_mut()?;
787
788        let attribute: GraphRecordAttribute = attribute.into();
789        let value: GraphRecordValue = value.into();
790
791        for edge_index in edge_indices {
792            let mut edge_attributes = graphrecord
793                .edge_attributes_mut(&edge_index)
794                .map_err(PyGraphRecordError::from)?;
795
796            edge_attributes
797                .update_attribute(&attribute, value.clone())
798                .map_err(PyGraphRecordError::from)?;
799        }
800
801        Ok(())
802    }
803
804    pub fn remove_edge_attribute(
805        &self,
806        edge_indices: Vec<EdgeIndex>,
807        attribute: PyGraphRecordAttribute,
808    ) -> PyResult<()> {
809        let mut graphrecord = self.inner_mut()?;
810
811        let attribute: GraphRecordAttribute = attribute.into();
812
813        for edge_index in edge_indices {
814            let mut edge_attributes = graphrecord
815                .edge_attributes_mut(&edge_index)
816                .map_err(PyGraphRecordError::from)?;
817
818            edge_attributes
819                .remove_attribute(&attribute)
820                .map_err(PyGraphRecordError::from)?;
821        }
822
823        Ok(())
824    }
825
826    #[pyo3(signature = (relations, bypass_plugins=false))]
827    pub fn add_edges(
828        &self,
829        relations: Vec<(PyNodeIndex, PyNodeIndex, PyAttributes)>,
830        bypass_plugins: bool,
831    ) -> PyResult<Vec<EdgeIndex>> {
832        let mut graphrecord = self.inner_mut()?;
833
834        if bypass_plugins {
835            Ok((**graphrecord)
836                .add_edges(relations.deep_into())
837                .map_err(PyGraphRecordError::from)?)
838        } else {
839            Ok(graphrecord
840                .add_edges(relations.deep_into())
841                .map_err(PyGraphRecordError::from)?)
842        }
843    }
844
845    #[pyo3(signature = (relations, group, bypass_plugins=false))]
846    pub fn add_edges_with_group(
847        &self,
848        relations: Vec<(PyNodeIndex, PyNodeIndex, PyAttributes)>,
849        group: PyGroup,
850        bypass_plugins: bool,
851    ) -> PyResult<Vec<EdgeIndex>> {
852        let mut graphrecord = self.inner_mut()?;
853
854        if bypass_plugins {
855            Ok((**graphrecord)
856                .add_edges_with_group(relations.deep_into(), &group)
857                .map_err(PyGraphRecordError::from)?)
858        } else {
859            Ok(graphrecord
860                .add_edges_with_group(relations.deep_into(), &group)
861                .map_err(PyGraphRecordError::from)?)
862        }
863    }
864
865    #[pyo3(signature = (edges_dataframes, bypass_plugins=false))]
866    pub fn add_edges_dataframes(
867        &self,
868        edges_dataframes: Vec<(PyDataFrame, String, String)>,
869        bypass_plugins: bool,
870    ) -> PyResult<Vec<EdgeIndex>> {
871        let mut graphrecord = self.inner_mut()?;
872
873        if bypass_plugins {
874            Ok((**graphrecord)
875                .add_edges_dataframes(edges_dataframes)
876                .map_err(PyGraphRecordError::from)?)
877        } else {
878            Ok(graphrecord
879                .add_edges_dataframes(edges_dataframes)
880                .map_err(PyGraphRecordError::from)?)
881        }
882    }
883
884    #[pyo3(signature = (edges_dataframes, group, bypass_plugins=false))]
885    pub fn add_edges_dataframes_with_group(
886        &self,
887        edges_dataframes: Vec<(PyDataFrame, String, String)>,
888        group: PyGroup,
889        bypass_plugins: bool,
890    ) -> PyResult<Vec<EdgeIndex>> {
891        let mut graphrecord = self.inner_mut()?;
892
893        if bypass_plugins {
894            Ok((**graphrecord)
895                .add_edges_dataframes_with_group(edges_dataframes, &group)
896                .map_err(PyGraphRecordError::from)?)
897        } else {
898            Ok(graphrecord
899                .add_edges_dataframes_with_group(edges_dataframes, &group)
900                .map_err(PyGraphRecordError::from)?)
901        }
902    }
903
904    #[pyo3(signature = (group, node_indices_to_add=None, edge_indices_to_add=None, bypass_plugins=false))]
905    pub fn add_group(
906        &self,
907        group: PyGroup,
908        node_indices_to_add: Option<Vec<PyNodeIndex>>,
909        edge_indices_to_add: Option<Vec<EdgeIndex>>,
910        bypass_plugins: bool,
911    ) -> PyResult<()> {
912        let mut graphrecord = self.inner_mut()?;
913
914        if bypass_plugins {
915            Ok((**graphrecord)
916                .add_group(
917                    group.into(),
918                    node_indices_to_add.deep_into(),
919                    edge_indices_to_add,
920                )
921                .map_err(PyGraphRecordError::from)?)
922        } else {
923            Ok(graphrecord
924                .add_group(
925                    group.into(),
926                    node_indices_to_add.deep_into(),
927                    edge_indices_to_add,
928                )
929                .map_err(PyGraphRecordError::from)?)
930        }
931    }
932
933    #[pyo3(signature = (group, bypass_plugins=false))]
934    pub fn remove_groups(&self, group: Vec<PyGroup>, bypass_plugins: bool) -> PyResult<()> {
935        let mut graphrecord = self.inner_mut()?;
936
937        if bypass_plugins {
938            group.into_iter().try_for_each(|group| {
939                (**graphrecord)
940                    .remove_group(&group)
941                    .map_err(PyGraphRecordError::from)?;
942                Ok(())
943            })
944        } else {
945            group.into_iter().try_for_each(|group| {
946                graphrecord
947                    .remove_group(&group)
948                    .map_err(PyGraphRecordError::from)?;
949                Ok(())
950            })
951        }
952    }
953
954    #[pyo3(signature = (group, node_index, bypass_plugins=false))]
955    pub fn add_nodes_to_group(
956        &self,
957        group: PyGroup,
958        node_index: Vec<PyNodeIndex>,
959        bypass_plugins: bool,
960    ) -> PyResult<()> {
961        let mut graphrecord = self.inner_mut()?;
962
963        if bypass_plugins {
964            node_index.into_iter().try_for_each(|node_index| {
965                Ok((**graphrecord)
966                    .add_node_to_group(group.clone().into(), node_index.into())
967                    .map_err(PyGraphRecordError::from)?)
968            })
969        } else {
970            node_index.into_iter().try_for_each(|node_index| {
971                Ok(graphrecord
972                    .add_node_to_group(group.clone().into(), node_index.into())
973                    .map_err(PyGraphRecordError::from)?)
974            })
975        }
976    }
977
978    #[pyo3(signature = (group, edge_index, bypass_plugins=false))]
979    pub fn add_edges_to_group(
980        &self,
981        group: PyGroup,
982        edge_index: Vec<EdgeIndex>,
983        bypass_plugins: bool,
984    ) -> PyResult<()> {
985        let mut graphrecord = self.inner_mut()?;
986
987        if bypass_plugins {
988            edge_index.into_iter().try_for_each(|edge_index| {
989                Ok((**graphrecord)
990                    .add_edge_to_group(group.clone().into(), edge_index)
991                    .map_err(PyGraphRecordError::from)?)
992            })
993        } else {
994            edge_index.into_iter().try_for_each(|edge_index| {
995                Ok(graphrecord
996                    .add_edge_to_group(group.clone().into(), edge_index)
997                    .map_err(PyGraphRecordError::from)?)
998            })
999        }
1000    }
1001
1002    #[pyo3(signature = (group, node_index, bypass_plugins=false))]
1003    pub fn remove_nodes_from_group(
1004        &self,
1005        group: PyGroup,
1006        node_index: Vec<PyNodeIndex>,
1007        bypass_plugins: bool,
1008    ) -> PyResult<()> {
1009        let mut graphrecord = self.inner_mut()?;
1010
1011        if bypass_plugins {
1012            node_index.into_iter().try_for_each(|node_index| {
1013                Ok((**graphrecord)
1014                    .remove_node_from_group(&group, &node_index)
1015                    .map_err(PyGraphRecordError::from)?)
1016            })
1017        } else {
1018            node_index.into_iter().try_for_each(|node_index| {
1019                Ok(graphrecord
1020                    .remove_node_from_group(&group, &node_index)
1021                    .map_err(PyGraphRecordError::from)?)
1022            })
1023        }
1024    }
1025
1026    #[pyo3(signature = (group, edge_index, bypass_plugins=false))]
1027    pub fn remove_edges_from_group(
1028        &self,
1029        group: PyGroup,
1030        edge_index: Vec<EdgeIndex>,
1031        bypass_plugins: bool,
1032    ) -> PyResult<()> {
1033        let mut graphrecord = self.inner_mut()?;
1034
1035        if bypass_plugins {
1036            edge_index.into_iter().try_for_each(|edge_index| {
1037                Ok((**graphrecord)
1038                    .remove_edge_from_group(&group, &edge_index)
1039                    .map_err(PyGraphRecordError::from)?)
1040            })
1041        } else {
1042            edge_index.into_iter().try_for_each(|edge_index| {
1043                Ok(graphrecord
1044                    .remove_edge_from_group(&group, &edge_index)
1045                    .map_err(PyGraphRecordError::from)?)
1046            })
1047        }
1048    }
1049
1050    pub fn nodes_in_group(
1051        &self,
1052        group: Vec<PyGroup>,
1053    ) -> PyResult<HashMap<PyGroup, Vec<PyNodeIndex>>> {
1054        let graphrecord = self.inner()?;
1055
1056        group
1057            .into_iter()
1058            .map(|group| {
1059                let nodes_attributes = graphrecord
1060                    .nodes_in_group(&group)
1061                    .map_err(PyGraphRecordError::from)?
1062                    .map(|node_index| node_index.clone().into())
1063                    .collect();
1064
1065                Ok((group, nodes_attributes))
1066            })
1067            .collect()
1068    }
1069
1070    pub fn ungrouped_nodes(&self) -> PyResult<Vec<PyNodeIndex>> {
1071        Ok(self
1072            .inner()?
1073            .ungrouped_nodes()
1074            .map(|node_index| node_index.clone().into())
1075            .collect())
1076    }
1077
1078    pub fn edges_in_group(
1079        &self,
1080        group: Vec<PyGroup>,
1081    ) -> PyResult<HashMap<PyGroup, Vec<EdgeIndex>>> {
1082        let graphrecord = self.inner()?;
1083
1084        group
1085            .into_iter()
1086            .map(|group| {
1087                let edges = graphrecord
1088                    .edges_in_group(&group)
1089                    .map_err(PyGraphRecordError::from)?
1090                    .copied()
1091                    .collect();
1092
1093                Ok((group, edges))
1094            })
1095            .collect()
1096    }
1097
1098    pub fn ungrouped_edges(&self) -> PyResult<Vec<EdgeIndex>> {
1099        Ok(self.inner()?.ungrouped_edges().copied().collect())
1100    }
1101
1102    pub fn groups_of_node(
1103        &self,
1104        node_index: Vec<PyNodeIndex>,
1105    ) -> PyResult<HashMap<PyNodeIndex, Vec<PyGroup>>> {
1106        let graphrecord = self.inner()?;
1107
1108        node_index
1109            .into_iter()
1110            .map(|node_index| {
1111                let groups = graphrecord
1112                    .groups_of_node(&node_index)
1113                    .map_err(PyGraphRecordError::from)?
1114                    .map(|node_index| node_index.clone().into())
1115                    .collect();
1116
1117                Ok((node_index, groups))
1118            })
1119            .collect()
1120    }
1121
1122    pub fn groups_of_edge(
1123        &self,
1124        edge_index: Vec<EdgeIndex>,
1125    ) -> PyResult<HashMap<EdgeIndex, Vec<PyGroup>>> {
1126        let graphrecord = self.inner()?;
1127
1128        edge_index
1129            .into_iter()
1130            .map(|edge_index| {
1131                let groups = graphrecord
1132                    .groups_of_edge(&edge_index)
1133                    .map_err(PyGraphRecordError::from)?
1134                    .map(|group| group.clone().into())
1135                    .collect();
1136
1137                Ok((edge_index, groups))
1138            })
1139            .collect()
1140    }
1141
1142    pub fn node_count(&self) -> PyResult<usize> {
1143        Ok(self.inner()?.node_count())
1144    }
1145
1146    pub fn edge_count(&self) -> PyResult<usize> {
1147        Ok(self.inner()?.edge_count())
1148    }
1149
1150    pub fn group_count(&self) -> PyResult<usize> {
1151        Ok(self.inner()?.group_count())
1152    }
1153
1154    pub fn contains_node(&self, node_index: PyNodeIndex) -> PyResult<bool> {
1155        Ok(self.inner()?.contains_node(&node_index.into()))
1156    }
1157
1158    pub fn contains_edge(&self, edge_index: EdgeIndex) -> PyResult<bool> {
1159        Ok(self.inner()?.contains_edge(&edge_index))
1160    }
1161
1162    pub fn contains_group(&self, group: PyGroup) -> PyResult<bool> {
1163        Ok(self.inner()?.contains_group(&group.into()))
1164    }
1165
1166    pub fn neighbors(
1167        &self,
1168        node_indices: Vec<PyNodeIndex>,
1169    ) -> PyResult<HashMap<PyNodeIndex, Vec<PyNodeIndex>>> {
1170        let graphrecord = self.inner()?;
1171
1172        node_indices
1173            .into_iter()
1174            .map(|node_index| {
1175                let neighbors = graphrecord
1176                    .neighbors_outgoing(&node_index)
1177                    .map_err(PyGraphRecordError::from)?
1178                    .map(|neighbor| neighbor.clone().into())
1179                    .collect();
1180
1181                Ok((node_index, neighbors))
1182            })
1183            .collect()
1184    }
1185
1186    pub fn neighbors_undirected(
1187        &self,
1188        node_indices: Vec<PyNodeIndex>,
1189    ) -> PyResult<HashMap<PyNodeIndex, Vec<PyNodeIndex>>> {
1190        let graphrecord = self.inner()?;
1191
1192        node_indices
1193            .into_iter()
1194            .map(|node_index| {
1195                let neighbors = graphrecord
1196                    .neighbors_undirected(&node_index)
1197                    .map_err(PyGraphRecordError::from)?
1198                    .map(|neighbor| neighbor.clone().into())
1199                    .collect();
1200
1201                Ok((node_index, neighbors))
1202            })
1203            .collect()
1204    }
1205
1206    #[pyo3(signature = (bypass_plugins=false))]
1207    pub fn clear(&self, bypass_plugins: bool) -> PyResult<()> {
1208        let mut graphrecord = self.inner_mut()?;
1209
1210        if bypass_plugins {
1211            (**graphrecord).clear();
1212            Ok(())
1213        } else {
1214            Ok(graphrecord.clear().map_err(PyGraphRecordError::from)?)
1215        }
1216    }
1217
1218    /// # Panics
1219    ///
1220    /// Panics if the python typing was not followed.
1221    pub fn query_nodes(
1222        &self,
1223        py: Python<'_>,
1224        query: &Bound<'_, PyFunction>,
1225    ) -> PyResult<Py<PyAny>> {
1226        let graphrecord = self.inner()?;
1227
1228        let result = graphrecord
1229            .query_nodes(|nodes| {
1230                let result = query
1231                    .call1((PyNodeOperand::from(nodes.clone()),))
1232                    .expect("Call should succeed");
1233
1234                result
1235                    .extract::<PyReturnOperand>()
1236                    .expect("Extraction must succeed")
1237            })
1238            .evaluate()
1239            .map_err(PyGraphRecordError::from)?;
1240
1241        Ok(result.into_pyobject(py)?.unbind())
1242    }
1243
1244    /// # Panics
1245    ///
1246    /// Panics if the python typing was not followed.
1247    pub fn query_edges(
1248        &self,
1249        py: Python<'_>,
1250        query: &Bound<'_, PyFunction>,
1251    ) -> PyResult<Py<PyAny>> {
1252        let graphrecord = self.inner()?;
1253
1254        let result = graphrecord
1255            .query_edges(|edges| {
1256                let result = query
1257                    .call1((PyEdgeOperand::from(edges.clone()),))
1258                    .expect("Call should succeed");
1259
1260                result
1261                    .extract::<PyReturnOperand>()
1262                    .expect("Extraction must succeed")
1263            })
1264            .evaluate()
1265            .map_err(PyGraphRecordError::from)?;
1266
1267        Ok(result.into_pyobject(py)?.unbind())
1268    }
1269
1270    #[allow(clippy::should_implement_trait)]
1271    pub fn clone(&self) -> Self {
1272        Clone::clone(self)
1273    }
1274
1275    pub fn overview(&self, truncate_details: Option<usize>) -> PyResult<PyOverview> {
1276        Ok(self
1277            .inner()?
1278            .overview(truncate_details)
1279            .map_err(PyGraphRecordError::from)?
1280            .into())
1281    }
1282
1283    pub fn group_overview(
1284        &self,
1285        group: PyGroup,
1286        truncate_details: Option<usize>,
1287    ) -> PyResult<PyGroupOverview> {
1288        Ok(self
1289            .inner()?
1290            .group_overview(&group.into(), truncate_details)
1291            .map_err(PyGraphRecordError::from)?
1292            .into())
1293    }
1294}