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