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