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 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(|_| {
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 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 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}