Skip to main content

graphrecords_python/graphrecord/querying/
attributes.rs

1use std::ops::Deref;
2
3use crate::graphrecord::{
4    attribute::PyGraphRecordAttribute,
5    errors::PyGraphRecordError,
6    querying::values::{
7        PyEdgeMultipleValuesWithIndexGroupOperand, PyEdgeMultipleValuesWithIndexOperand,
8        PyNodeMultipleValuesWithIndexGroupOperand, PyNodeMultipleValuesWithIndexOperand,
9    },
10};
11use graphrecords_core::{
12    errors::GraphRecordError,
13    graphrecord::{
14        GraphRecordAttribute,
15        querying::{
16            DeepClone,
17            attributes::{
18                AttributesTreeOperand, MultipleAttributesComparisonOperand,
19                MultipleAttributesWithIndexOperand, MultipleAttributesWithoutIndexOperand,
20                SingleAttributeComparisonOperand, SingleAttributeWithIndexOperand,
21                SingleAttributeWithoutIndexOperand,
22            },
23            edges::EdgeOperand,
24            group_by::GroupOperand,
25            nodes::NodeOperand,
26            wrapper::Wrapper,
27        },
28    },
29};
30use pyo3::{
31    Borrowed, Bound, FromPyObject, PyAny, PyErr, PyResult, pyclass, pymethods,
32    types::{PyAnyMethods, PyFunction},
33};
34
35#[repr(transparent)]
36pub struct PySingleAttributeComparisonOperand(SingleAttributeComparisonOperand);
37
38impl From<SingleAttributeComparisonOperand> for PySingleAttributeComparisonOperand {
39    fn from(operand: SingleAttributeComparisonOperand) -> Self {
40        Self(operand)
41    }
42}
43
44impl From<PySingleAttributeComparisonOperand> for SingleAttributeComparisonOperand {
45    fn from(operand: PySingleAttributeComparisonOperand) -> Self {
46        operand.0
47    }
48}
49
50impl Deref for PySingleAttributeComparisonOperand {
51    type Target = SingleAttributeComparisonOperand;
52
53    fn deref(&self) -> &Self::Target {
54        &self.0
55    }
56}
57
58impl FromPyObject<'_, '_> for PySingleAttributeComparisonOperand {
59    type Error = PyErr;
60
61    fn extract(ob: Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
62        match ob.extract::<PyGraphRecordAttribute>() {
63            Ok(attribute) => {
64                Ok(SingleAttributeComparisonOperand::Attribute(attribute.into()).into())
65            }
66            _ => match ob.extract::<PyNodeSingleAttributeWithIndexOperand>() {
67                Ok(operand) => Ok(Self(operand.0.into())),
68                _ => match ob.extract::<PyNodeSingleAttributeWithoutIndexOperand>() {
69                    Ok(operand) => Ok(Self(operand.0.into())),
70                    _ => match ob.extract::<PyEdgeSingleAttributeWithIndexOperand>() {
71                        Ok(operand) => Ok(Self(operand.0.into())),
72                        _ => match ob.extract::<PyEdgeSingleAttributeWithoutIndexOperand>() {
73                            Ok(operand) => Ok(Self(operand.0.into())),
74                            _ => Err(PyGraphRecordError::from(GraphRecordError::ConversionError(
75                                format!(
76                    "Failed to convert {} into GraphRecordValue or SingleValueOperand",
77                    ob.to_owned()
78                ),
79                            ))
80                            .into()),
81                        },
82                    },
83                },
84            },
85        }
86    }
87}
88
89#[repr(transparent)]
90pub struct PyMultipleAttributesComparisonOperand(MultipleAttributesComparisonOperand);
91
92impl From<MultipleAttributesComparisonOperand> for PyMultipleAttributesComparisonOperand {
93    fn from(operand: MultipleAttributesComparisonOperand) -> Self {
94        Self(operand)
95    }
96}
97
98impl From<PyMultipleAttributesComparisonOperand> for MultipleAttributesComparisonOperand {
99    fn from(operand: PyMultipleAttributesComparisonOperand) -> Self {
100        operand.0
101    }
102}
103
104impl Deref for PyMultipleAttributesComparisonOperand {
105    type Target = MultipleAttributesComparisonOperand;
106
107    fn deref(&self) -> &Self::Target {
108        &self.0
109    }
110}
111
112impl FromPyObject<'_, '_> for PyMultipleAttributesComparisonOperand {
113    type Error = PyErr;
114
115    fn extract(ob: Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
116        match ob.extract::<Vec<PyGraphRecordAttribute>>() {
117            Ok(values) => Ok(MultipleAttributesComparisonOperand::Attributes(
118                values.into_iter().map(GraphRecordAttribute::from).collect(),
119            )
120            .into()),
121            _ => {
122                match ob.extract::<PyNodeMultipleAttributesWithIndexOperand>() {
123                    Ok(operand) => Ok(Self(operand.0.into())),
124                    _ => {
125                        match ob.extract::<PyNodeMultipleAttributesWithoutIndexOperand>() {
126                            Ok(operand) => Ok(Self(operand.0.into())),
127                            _ => match ob.extract::<PyEdgeMultipleAttributesWithIndexOperand>() {
128                                Ok(operand) => Ok(Self(operand.0.into())),
129                                _ => {
130                                    match ob.extract::<PyEdgeMultipleAttributesWithoutIndexOperand>() { Ok(operand) => {
131            Ok(Self(operand.0.into()))
132        } _ => {
133            Err(
134                PyGraphRecordError::from(GraphRecordError::ConversionError(format!(
135                    "Failed to convert {} into List[GraphRecordAttribute] or MultipleAttributesOperand",
136                    ob.to_owned()
137                )))
138                .into(),
139            )
140        }}
141                                }
142                            },
143                        }
144                    }
145                }
146            }
147        }
148    }
149}
150
151macro_rules! implement_attributes_tree_operand {
152    ($name:ident, $generic:ty, $multiple_attributes_operand:ty) => {
153        #[pyclass(frozen)]
154        #[repr(transparent)]
155        #[derive(Clone)]
156        pub struct $name(Wrapper<AttributesTreeOperand<$generic>>);
157
158        impl From<Wrapper<AttributesTreeOperand<$generic>>> for $name {
159            fn from(operand: Wrapper<AttributesTreeOperand<$generic>>) -> Self {
160                Self(operand)
161            }
162        }
163
164        impl From<$name> for Wrapper<AttributesTreeOperand<$generic>> {
165            fn from(operand: $name) -> Self {
166                operand.0
167            }
168        }
169
170        impl Deref for $name {
171            type Target = Wrapper<AttributesTreeOperand<$generic>>;
172
173            fn deref(&self) -> &Self::Target {
174                &self.0
175            }
176        }
177
178        #[pymethods]
179        impl $name {
180            pub fn max(&self) -> $multiple_attributes_operand {
181                self.0.max().into()
182            }
183
184            pub fn min(&self) -> $multiple_attributes_operand {
185                self.0.min().into()
186            }
187
188            pub fn count(&self) -> $multiple_attributes_operand {
189                self.0.count().into()
190            }
191
192            pub fn sum(&self) -> $multiple_attributes_operand {
193                self.0.sum().into()
194            }
195
196            pub fn random(&self) -> $multiple_attributes_operand {
197                self.0.random().into()
198            }
199
200            pub fn greater_than(&self, attribute: PySingleAttributeComparisonOperand) {
201                self.0.greater_than(attribute);
202            }
203
204            pub fn greater_than_or_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
205                self.0.greater_than_or_equal_to(attribute);
206            }
207
208            pub fn less_than(&self, attribute: PySingleAttributeComparisonOperand) {
209                self.0.less_than(attribute);
210            }
211
212            pub fn less_than_or_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
213                self.0.less_than_or_equal_to(attribute);
214            }
215
216            pub fn equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
217                self.0.equal_to(attribute);
218            }
219
220            pub fn not_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
221                self.0.not_equal_to(attribute);
222            }
223
224            pub fn starts_with(&self, attribute: PySingleAttributeComparisonOperand) {
225                self.0.starts_with(attribute);
226            }
227
228            pub fn ends_with(&self, attribute: PySingleAttributeComparisonOperand) {
229                self.0.ends_with(attribute);
230            }
231
232            pub fn contains(&self, attribute: PySingleAttributeComparisonOperand) {
233                self.0.contains(attribute);
234            }
235
236            pub fn is_in(&self, attributes: PyMultipleAttributesComparisonOperand) {
237                self.0.is_in(attributes);
238            }
239
240            pub fn is_not_in(&self, attributes: PyMultipleAttributesComparisonOperand) {
241                self.0.is_not_in(attributes);
242            }
243
244            pub fn add(&self, attribute: PySingleAttributeComparisonOperand) {
245                self.0.add(attribute);
246            }
247
248            pub fn sub(&self, attribute: PySingleAttributeComparisonOperand) {
249                self.0.sub(attribute);
250            }
251
252            pub fn mul(&self, attribute: PySingleAttributeComparisonOperand) {
253                self.0.mul(attribute);
254            }
255
256            pub fn pow(&self, attribute: PySingleAttributeComparisonOperand) {
257                self.0.pow(attribute);
258            }
259
260            pub fn r#mod(&self, attribute: PySingleAttributeComparisonOperand) {
261                self.0.r#mod(attribute);
262            }
263
264            pub fn abs(&self) {
265                self.0.abs();
266            }
267
268            pub fn trim(&self) {
269                self.0.trim();
270            }
271
272            pub fn trim_start(&self) {
273                self.0.trim_start();
274            }
275
276            pub fn trim_end(&self) {
277                self.0.trim_end();
278            }
279
280            pub fn lowercase(&self) {
281                self.0.lowercase();
282            }
283
284            pub fn uppercase(&self) {
285                self.0.uppercase();
286            }
287
288            pub fn slice(&self, start: usize, end: usize) {
289                self.0.slice(start, end);
290            }
291
292            pub fn is_string(&self) {
293                self.0.is_string();
294            }
295
296            pub fn is_int(&self) {
297                self.0.is_int();
298            }
299
300            pub fn is_max(&self) {
301                self.0.is_max();
302            }
303
304            pub fn is_min(&self) {
305                self.0.is_min();
306            }
307
308            pub fn either_or(&self, either: &Bound<'_, PyFunction>, or: &Bound<'_, PyFunction>) {
309                self.0.either_or(
310                    |operand| {
311                        either
312                            .call1(($name::from(operand.clone()),))
313                            .expect("Call must succeed");
314                    },
315                    |operand| {
316                        or.call1(($name::from(operand.clone()),))
317                            .expect("Call must succeed");
318                    },
319                );
320            }
321
322            pub fn exclude(&self, query: &Bound<'_, PyFunction>) {
323                self.0.exclude(|operand| {
324                    query
325                        .call1(($name::from(operand.clone()),))
326                        .expect("Call must succeed");
327                });
328            }
329
330            pub fn deep_clone(&self) -> $name {
331                self.0.deep_clone().into()
332            }
333        }
334    };
335}
336
337implement_attributes_tree_operand!(
338    PyNodeAttributesTreeOperand,
339    NodeOperand,
340    PyNodeMultipleAttributesWithIndexOperand
341);
342implement_attributes_tree_operand!(
343    PyEdgeAttributesTreeOperand,
344    EdgeOperand,
345    PyEdgeMultipleAttributesWithIndexOperand
346);
347
348macro_rules! implement_attributes_tree_group_operand {
349    ($name:ident, $ungrouped_name:ident, $generic:ty, $multiple_attributes_operand:ty) => {
350        #[pyclass(frozen)]
351        #[repr(transparent)]
352        #[derive(Clone)]
353        pub struct $name(Wrapper<GroupOperand<AttributesTreeOperand<$generic>>>);
354
355        impl From<Wrapper<GroupOperand<AttributesTreeOperand<$generic>>>> for $name {
356            fn from(operand: Wrapper<GroupOperand<AttributesTreeOperand<$generic>>>) -> Self {
357                Self(operand)
358            }
359        }
360
361        impl From<$name> for Wrapper<GroupOperand<AttributesTreeOperand<$generic>>> {
362            fn from(operand: $name) -> Self {
363                operand.0
364            }
365        }
366
367        impl Deref for $name {
368            type Target = Wrapper<GroupOperand<AttributesTreeOperand<$generic>>>;
369
370            fn deref(&self) -> &Self::Target {
371                &self.0
372            }
373        }
374
375        #[pymethods]
376        impl $name {
377            pub fn max(&self) -> $multiple_attributes_operand {
378                self.0.max().into()
379            }
380
381            pub fn min(&self) -> $multiple_attributes_operand {
382                self.0.min().into()
383            }
384
385            pub fn count(&self) -> $multiple_attributes_operand {
386                self.0.count().into()
387            }
388
389            pub fn sum(&self) -> $multiple_attributes_operand {
390                self.0.sum().into()
391            }
392
393            pub fn random(&self) -> $multiple_attributes_operand {
394                self.0.random().into()
395            }
396
397            pub fn greater_than(&self, attribute: PySingleAttributeComparisonOperand) {
398                self.0.greater_than(attribute);
399            }
400
401            pub fn greater_than_or_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
402                self.0.greater_than_or_equal_to(attribute);
403            }
404
405            pub fn less_than(&self, attribute: PySingleAttributeComparisonOperand) {
406                self.0.less_than(attribute);
407            }
408
409            pub fn less_than_or_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
410                self.0.less_than_or_equal_to(attribute);
411            }
412
413            pub fn equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
414                self.0.equal_to(attribute);
415            }
416
417            pub fn not_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
418                self.0.not_equal_to(attribute);
419            }
420
421            pub fn starts_with(&self, attribute: PySingleAttributeComparisonOperand) {
422                self.0.starts_with(attribute);
423            }
424
425            pub fn ends_with(&self, attribute: PySingleAttributeComparisonOperand) {
426                self.0.ends_with(attribute);
427            }
428
429            pub fn contains(&self, attribute: PySingleAttributeComparisonOperand) {
430                self.0.contains(attribute);
431            }
432
433            pub fn is_in(&self, attributes: PyMultipleAttributesComparisonOperand) {
434                self.0.is_in(attributes);
435            }
436
437            pub fn is_not_in(&self, attributes: PyMultipleAttributesComparisonOperand) {
438                self.0.is_not_in(attributes);
439            }
440
441            pub fn add(&self, attribute: PySingleAttributeComparisonOperand) {
442                self.0.add(attribute);
443            }
444
445            pub fn sub(&self, attribute: PySingleAttributeComparisonOperand) {
446                self.0.sub(attribute);
447            }
448
449            pub fn mul(&self, attribute: PySingleAttributeComparisonOperand) {
450                self.0.mul(attribute);
451            }
452
453            pub fn pow(&self, attribute: PySingleAttributeComparisonOperand) {
454                self.0.pow(attribute);
455            }
456
457            pub fn r#mod(&self, attribute: PySingleAttributeComparisonOperand) {
458                self.0.r#mod(attribute);
459            }
460
461            pub fn abs(&self) {
462                self.0.abs();
463            }
464
465            pub fn trim(&self) {
466                self.0.trim();
467            }
468
469            pub fn trim_start(&self) {
470                self.0.trim_start();
471            }
472
473            pub fn trim_end(&self) {
474                self.0.trim_end();
475            }
476
477            pub fn lowercase(&self) {
478                self.0.lowercase();
479            }
480
481            pub fn uppercase(&self) {
482                self.0.uppercase();
483            }
484
485            pub fn slice(&self, start: usize, end: usize) {
486                self.0.slice(start, end);
487            }
488
489            pub fn is_string(&self) {
490                self.0.is_string();
491            }
492
493            pub fn is_int(&self) {
494                self.0.is_int();
495            }
496
497            pub fn is_max(&self) {
498                self.0.is_max();
499            }
500
501            pub fn is_min(&self) {
502                self.0.is_min();
503            }
504
505            pub fn either_or(&self, either: &Bound<'_, PyFunction>, or: &Bound<'_, PyFunction>) {
506                self.0.either_or(
507                    |operand| {
508                        either
509                            .call1(($ungrouped_name::from(operand.clone()),))
510                            .expect("Call must succeed");
511                    },
512                    |operand| {
513                        or.call1(($ungrouped_name::from(operand.clone()),))
514                            .expect("Call must succeed");
515                    },
516                );
517            }
518
519            pub fn exclude(&self, query: &Bound<'_, PyFunction>) {
520                self.0.exclude(|operand| {
521                    query
522                        .call1(($ungrouped_name::from(operand.clone()),))
523                        .expect("Call must succeed");
524                });
525            }
526
527            pub fn ungroup(&self) -> $ungrouped_name {
528                self.0.ungroup().into()
529            }
530
531            pub fn deep_clone(&self) -> $name {
532                self.0.deep_clone().into()
533            }
534        }
535    };
536}
537
538implement_attributes_tree_group_operand!(
539    PyNodeAttributesTreeGroupOperand,
540    PyNodeAttributesTreeOperand,
541    NodeOperand,
542    PyNodeMultipleAttributesWithIndexGroupOperand
543);
544implement_attributes_tree_group_operand!(
545    PyEdgeAttributesTreeGroupOperand,
546    PyEdgeAttributesTreeOperand,
547    EdgeOperand,
548    PyEdgeMultipleAttributesWithIndexGroupOperand
549);
550
551macro_rules! implement_multiple_attributes_operand {
552    ($name:ident, $kind:ident, $generic:ty, $py_single_attribute_with_index_operand:ty, $py_single_attribute_without_index_operand:ty) => {
553        #[pyclass(frozen)]
554        #[repr(transparent)]
555        #[derive(Clone)]
556        pub struct $name(Wrapper<$kind<$generic>>);
557
558        impl From<Wrapper<$kind<$generic>>> for $name {
559            fn from(operand: Wrapper<$kind<$generic>>) -> Self {
560                Self(operand)
561            }
562        }
563
564        impl From<$name> for Wrapper<$kind<$generic>> {
565            fn from(operand: $name) -> Self {
566                operand.0
567            }
568        }
569
570        impl Deref for $name {
571            type Target = Wrapper<$kind<$generic>>;
572
573            fn deref(&self) -> &Self::Target {
574                &self.0
575            }
576        }
577
578        #[pymethods]
579        impl $name {
580            pub fn max(&self) -> $py_single_attribute_with_index_operand {
581                self.0.max().into()
582            }
583
584            pub fn min(&self) -> $py_single_attribute_with_index_operand {
585                self.0.min().into()
586            }
587
588            pub fn count(&self) -> $py_single_attribute_without_index_operand {
589                self.0.count().into()
590            }
591
592            pub fn sum(&self) -> $py_single_attribute_without_index_operand {
593                self.0.sum().into()
594            }
595
596            pub fn random(&self) -> $py_single_attribute_with_index_operand {
597                self.0.random().into()
598            }
599
600            pub fn greater_than(&self, attribute: PySingleAttributeComparisonOperand) {
601                self.0.greater_than(attribute);
602            }
603
604            pub fn greater_than_or_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
605                self.0.greater_than_or_equal_to(attribute);
606            }
607
608            pub fn less_than(&self, attribute: PySingleAttributeComparisonOperand) {
609                self.0.less_than(attribute);
610            }
611
612            pub fn less_than_or_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
613                self.0.less_than_or_equal_to(attribute);
614            }
615
616            pub fn equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
617                self.0.equal_to(attribute);
618            }
619
620            pub fn not_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
621                self.0.not_equal_to(attribute);
622            }
623
624            pub fn starts_with(&self, attribute: PySingleAttributeComparisonOperand) {
625                self.0.starts_with(attribute);
626            }
627
628            pub fn ends_with(&self, attribute: PySingleAttributeComparisonOperand) {
629                self.0.ends_with(attribute);
630            }
631
632            pub fn contains(&self, attribute: PySingleAttributeComparisonOperand) {
633                self.0.contains(attribute);
634            }
635
636            pub fn is_in(&self, attributes: PyMultipleAttributesComparisonOperand) {
637                self.0.is_in(attributes);
638            }
639
640            pub fn is_not_in(&self, attributes: PyMultipleAttributesComparisonOperand) {
641                self.0.is_not_in(attributes);
642            }
643
644            pub fn add(&self, attribute: PySingleAttributeComparisonOperand) {
645                self.0.add(attribute);
646            }
647
648            pub fn sub(&self, attribute: PySingleAttributeComparisonOperand) {
649                self.0.sub(attribute);
650            }
651
652            pub fn mul(&self, attribute: PySingleAttributeComparisonOperand) {
653                self.0.mul(attribute);
654            }
655
656            pub fn pow(&self, attribute: PySingleAttributeComparisonOperand) {
657                self.0.pow(attribute);
658            }
659
660            pub fn r#mod(&self, attribute: PySingleAttributeComparisonOperand) {
661                self.0.r#mod(attribute);
662            }
663
664            pub fn abs(&self) {
665                self.0.abs();
666            }
667
668            pub fn trim(&self) {
669                self.0.trim();
670            }
671
672            pub fn trim_start(&self) {
673                self.0.trim_start();
674            }
675
676            pub fn trim_end(&self) {
677                self.0.trim_end();
678            }
679
680            pub fn lowercase(&self) {
681                self.0.lowercase();
682            }
683
684            pub fn uppercase(&self) {
685                self.0.uppercase();
686            }
687
688            pub fn slice(&self, start: usize, end: usize) {
689                self.0.slice(start, end);
690            }
691
692            pub fn is_string(&self) {
693                self.0.is_string();
694            }
695
696            pub fn is_int(&self) {
697                self.0.is_int();
698            }
699
700            pub fn is_max(&self) {
701                self.0.is_max();
702            }
703
704            pub fn is_min(&self) {
705                self.0.is_min();
706            }
707
708            pub fn either_or(&self, either: &Bound<'_, PyFunction>, or: &Bound<'_, PyFunction>) {
709                self.0.either_or(
710                    |operand| {
711                        either
712                            .call1(($name::from(operand.clone()),))
713                            .expect("Call must succeed");
714                    },
715                    |operand| {
716                        or.call1(($name::from(operand.clone()),))
717                            .expect("Call must succeed");
718                    },
719                );
720            }
721
722            pub fn exclude(&self, query: &Bound<'_, PyFunction>) {
723                self.0.exclude(|operand| {
724                    query
725                        .call1(($name::from(operand.clone()),))
726                        .expect("Call must succeed");
727                });
728            }
729
730            pub fn deep_clone(&self) -> $name {
731                self.0.deep_clone().into()
732            }
733        }
734    };
735    ($name:ident, $kind:ident, $generic:ty, $py_single_attribute_with_index_operand:ty, $py_single_attribute_without_index_operand:ty, $py_multiple_values_operand:ty) => {
736        #[pyclass(frozen)]
737        #[repr(transparent)]
738        #[derive(Clone)]
739        pub struct $name(Wrapper<$kind<$generic>>);
740
741        impl From<Wrapper<$kind<$generic>>> for $name {
742            fn from(operand: Wrapper<$kind<$generic>>) -> Self {
743                Self(operand)
744            }
745        }
746
747        impl From<$name> for Wrapper<$kind<$generic>> {
748            fn from(operand: $name) -> Self {
749                operand.0
750            }
751        }
752
753        impl Deref for $name {
754            type Target = Wrapper<$kind<$generic>>;
755
756            fn deref(&self) -> &Self::Target {
757                &self.0
758            }
759        }
760
761        #[pymethods]
762        impl $name {
763            pub fn max(&self) -> $py_single_attribute_with_index_operand {
764                self.0.max().into()
765            }
766
767            pub fn min(&self) -> $py_single_attribute_with_index_operand {
768                self.0.min().into()
769            }
770
771            pub fn count(&self) -> $py_single_attribute_without_index_operand {
772                self.0.count().into()
773            }
774
775            pub fn sum(&self) -> $py_single_attribute_without_index_operand {
776                self.0.sum().into()
777            }
778
779            pub fn random(&self) -> $py_single_attribute_with_index_operand {
780                self.0.random().into()
781            }
782
783            pub fn greater_than(&self, attribute: PySingleAttributeComparisonOperand) {
784                self.0.greater_than(attribute);
785            }
786
787            pub fn greater_than_or_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
788                self.0.greater_than_or_equal_to(attribute);
789            }
790
791            pub fn less_than(&self, attribute: PySingleAttributeComparisonOperand) {
792                self.0.less_than(attribute);
793            }
794
795            pub fn less_than_or_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
796                self.0.less_than_or_equal_to(attribute);
797            }
798
799            pub fn equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
800                self.0.equal_to(attribute);
801            }
802
803            pub fn not_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
804                self.0.not_equal_to(attribute);
805            }
806
807            pub fn starts_with(&self, attribute: PySingleAttributeComparisonOperand) {
808                self.0.starts_with(attribute);
809            }
810
811            pub fn ends_with(&self, attribute: PySingleAttributeComparisonOperand) {
812                self.0.ends_with(attribute);
813            }
814
815            pub fn contains(&self, attribute: PySingleAttributeComparisonOperand) {
816                self.0.contains(attribute);
817            }
818
819            pub fn is_in(&self, attributes: PyMultipleAttributesComparisonOperand) {
820                self.0.is_in(attributes);
821            }
822
823            pub fn is_not_in(&self, attributes: PyMultipleAttributesComparisonOperand) {
824                self.0.is_not_in(attributes);
825            }
826
827            pub fn add(&self, attribute: PySingleAttributeComparisonOperand) {
828                self.0.add(attribute);
829            }
830
831            pub fn sub(&self, attribute: PySingleAttributeComparisonOperand) {
832                self.0.sub(attribute);
833            }
834
835            pub fn mul(&self, attribute: PySingleAttributeComparisonOperand) {
836                self.0.mul(attribute);
837            }
838
839            pub fn pow(&self, attribute: PySingleAttributeComparisonOperand) {
840                self.0.pow(attribute);
841            }
842
843            pub fn r#mod(&self, attribute: PySingleAttributeComparisonOperand) {
844                self.0.r#mod(attribute);
845            }
846
847            pub fn abs(&self) {
848                self.0.abs();
849            }
850
851            pub fn trim(&self) {
852                self.0.trim();
853            }
854
855            pub fn trim_start(&self) {
856                self.0.trim_start();
857            }
858
859            pub fn trim_end(&self) {
860                self.0.trim_end();
861            }
862
863            pub fn lowercase(&self) {
864                self.0.lowercase();
865            }
866
867            pub fn uppercase(&self) {
868                self.0.uppercase();
869            }
870
871            pub fn to_values(&self) -> $py_multiple_values_operand {
872                self.0.to_values().into()
873            }
874
875            pub fn slice(&self, start: usize, end: usize) {
876                self.0.slice(start, end);
877            }
878
879            pub fn is_string(&self) {
880                self.0.is_string();
881            }
882
883            pub fn is_int(&self) {
884                self.0.is_int();
885            }
886
887            pub fn is_max(&self) {
888                self.0.is_max();
889            }
890
891            pub fn is_min(&self) {
892                self.0.is_min();
893            }
894
895            pub fn either_or(&self, either: &Bound<'_, PyFunction>, or: &Bound<'_, PyFunction>) {
896                self.0.either_or(
897                    |operand| {
898                        either
899                            .call1(($name::from(operand.clone()),))
900                            .expect("Call must succeed");
901                    },
902                    |operand| {
903                        or.call1(($name::from(operand.clone()),))
904                            .expect("Call must succeed");
905                    },
906                );
907            }
908
909            pub fn exclude(&self, query: &Bound<'_, PyFunction>) {
910                self.0.exclude(|operand| {
911                    query
912                        .call1(($name::from(operand.clone()),))
913                        .expect("Call must succeed");
914                });
915            }
916
917            pub fn deep_clone(&self) -> $name {
918                self.0.deep_clone().into()
919            }
920        }
921    };
922}
923
924implement_multiple_attributes_operand!(
925    PyNodeMultipleAttributesWithIndexOperand,
926    MultipleAttributesWithIndexOperand,
927    NodeOperand,
928    PyNodeSingleAttributeWithIndexOperand,
929    PyNodeSingleAttributeWithoutIndexOperand,
930    PyNodeMultipleValuesWithIndexOperand
931);
932implement_multiple_attributes_operand!(
933    PyNodeMultipleAttributesWithoutIndexOperand,
934    MultipleAttributesWithoutIndexOperand,
935    NodeOperand,
936    PyNodeSingleAttributeWithoutIndexOperand,
937    PyNodeSingleAttributeWithoutIndexOperand
938);
939implement_multiple_attributes_operand!(
940    PyEdgeMultipleAttributesWithIndexOperand,
941    MultipleAttributesWithIndexOperand,
942    EdgeOperand,
943    PyEdgeSingleAttributeWithIndexOperand,
944    PyEdgeSingleAttributeWithoutIndexOperand,
945    PyEdgeMultipleValuesWithIndexOperand
946);
947implement_multiple_attributes_operand!(
948    PyEdgeMultipleAttributesWithoutIndexOperand,
949    MultipleAttributesWithoutIndexOperand,
950    EdgeOperand,
951    PyEdgeSingleAttributeWithoutIndexOperand,
952    PyEdgeSingleAttributeWithoutIndexOperand
953);
954
955macro_rules! implement_multiple_attributes_grouped_operand {
956    ($name:ident, $ungrouped_name:ident, $kind:ident, $generic:ty, $py_single_attribute_with_index_operand:ty, $py_single_attribute_without_index_operand:ty, $py_multiple_values_operand:ty) => {
957        #[pyclass(frozen)]
958        #[repr(transparent)]
959        #[derive(Clone)]
960        pub struct $name(Wrapper<GroupOperand<$kind<$generic>>>);
961
962        impl From<Wrapper<GroupOperand<$kind<$generic>>>> for $name {
963            fn from(operand: Wrapper<GroupOperand<$kind<$generic>>>) -> Self {
964                Self(operand)
965            }
966        }
967
968        impl From<$name> for Wrapper<GroupOperand<$kind<$generic>>> {
969            fn from(operand: $name) -> Self {
970                operand.0
971            }
972        }
973
974        impl Deref for $name {
975            type Target = Wrapper<GroupOperand<$kind<$generic>>>;
976
977            fn deref(&self) -> &Self::Target {
978                &self.0
979            }
980        }
981
982        #[pymethods]
983        impl $name {
984            pub fn max(&self) -> $py_single_attribute_with_index_operand {
985                self.0.max().into()
986            }
987
988            pub fn min(&self) -> $py_single_attribute_with_index_operand {
989                self.0.min().into()
990            }
991
992            pub fn count(&self) -> $py_single_attribute_without_index_operand {
993                self.0.count().into()
994            }
995
996            pub fn sum(&self) -> $py_single_attribute_without_index_operand {
997                self.0.sum().into()
998            }
999
1000            pub fn random(&self) -> $py_single_attribute_with_index_operand {
1001                self.0.random().into()
1002            }
1003
1004            pub fn greater_than(&self, attribute: PySingleAttributeComparisonOperand) {
1005                self.0.greater_than(attribute);
1006            }
1007
1008            pub fn greater_than_or_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
1009                self.0.greater_than_or_equal_to(attribute);
1010            }
1011
1012            pub fn less_than(&self, attribute: PySingleAttributeComparisonOperand) {
1013                self.0.less_than(attribute);
1014            }
1015
1016            pub fn less_than_or_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
1017                self.0.less_than_or_equal_to(attribute);
1018            }
1019
1020            pub fn equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
1021                self.0.equal_to(attribute);
1022            }
1023
1024            pub fn not_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
1025                self.0.not_equal_to(attribute);
1026            }
1027
1028            pub fn starts_with(&self, attribute: PySingleAttributeComparisonOperand) {
1029                self.0.starts_with(attribute);
1030            }
1031
1032            pub fn ends_with(&self, attribute: PySingleAttributeComparisonOperand) {
1033                self.0.ends_with(attribute);
1034            }
1035
1036            pub fn contains(&self, attribute: PySingleAttributeComparisonOperand) {
1037                self.0.contains(attribute);
1038            }
1039
1040            pub fn is_in(&self, attributes: PyMultipleAttributesComparisonOperand) {
1041                self.0.is_in(attributes);
1042            }
1043
1044            pub fn is_not_in(&self, attributes: PyMultipleAttributesComparisonOperand) {
1045                self.0.is_not_in(attributes);
1046            }
1047
1048            pub fn add(&self, attribute: PySingleAttributeComparisonOperand) {
1049                self.0.add(attribute);
1050            }
1051
1052            pub fn sub(&self, attribute: PySingleAttributeComparisonOperand) {
1053                self.0.sub(attribute);
1054            }
1055
1056            pub fn mul(&self, attribute: PySingleAttributeComparisonOperand) {
1057                self.0.mul(attribute);
1058            }
1059
1060            pub fn pow(&self, attribute: PySingleAttributeComparisonOperand) {
1061                self.0.pow(attribute);
1062            }
1063
1064            pub fn r#mod(&self, attribute: PySingleAttributeComparisonOperand) {
1065                self.0.r#mod(attribute);
1066            }
1067
1068            pub fn abs(&self) {
1069                self.0.abs();
1070            }
1071
1072            pub fn trim(&self) {
1073                self.0.trim();
1074            }
1075
1076            pub fn trim_start(&self) {
1077                self.0.trim_start();
1078            }
1079
1080            pub fn trim_end(&self) {
1081                self.0.trim_end();
1082            }
1083
1084            pub fn lowercase(&self) {
1085                self.0.lowercase();
1086            }
1087
1088            pub fn uppercase(&self) {
1089                self.0.uppercase();
1090            }
1091
1092            pub fn to_values(&self) -> $py_multiple_values_operand {
1093                self.0.to_values().into()
1094            }
1095
1096            pub fn slice(&self, start: usize, end: usize) {
1097                self.0.slice(start, end);
1098            }
1099
1100            pub fn is_string(&self) {
1101                self.0.is_string();
1102            }
1103
1104            pub fn is_int(&self) {
1105                self.0.is_int();
1106            }
1107
1108            pub fn is_max(&self) {
1109                self.0.is_max();
1110            }
1111
1112            pub fn is_min(&self) {
1113                self.0.is_min();
1114            }
1115
1116            pub fn either_or(&self, either: &Bound<'_, PyFunction>, or: &Bound<'_, PyFunction>) {
1117                self.0.either_or(
1118                    |operand| {
1119                        either
1120                            .call1(($ungrouped_name::from(operand.clone()),))
1121                            .expect("Call must succeed");
1122                    },
1123                    |operand| {
1124                        or.call1(($ungrouped_name::from(operand.clone()),))
1125                            .expect("Call must succeed");
1126                    },
1127                );
1128            }
1129
1130            pub fn exclude(&self, query: &Bound<'_, PyFunction>) {
1131                self.0.exclude(|operand| {
1132                    query
1133                        .call1(($ungrouped_name::from(operand.clone()),))
1134                        .expect("Call must succeed");
1135                });
1136            }
1137
1138            pub fn ungroup(&self) -> $ungrouped_name {
1139                self.0.ungroup().into()
1140            }
1141
1142            pub fn deep_clone(&self) -> $name {
1143                self.0.deep_clone().into()
1144            }
1145        }
1146    };
1147}
1148
1149implement_multiple_attributes_grouped_operand!(
1150    PyNodeMultipleAttributesWithIndexGroupOperand,
1151    PyNodeMultipleAttributesWithIndexOperand,
1152    MultipleAttributesWithIndexOperand,
1153    NodeOperand,
1154    PyNodeSingleAttributeWithIndexGroupOperand,
1155    PyNodeSingleAttributeWithoutIndexGroupOperand,
1156    PyNodeMultipleValuesWithIndexGroupOperand
1157);
1158implement_multiple_attributes_grouped_operand!(
1159    PyEdgeMultipleAttributesWithIndexGroupOperand,
1160    PyEdgeMultipleAttributesWithIndexOperand,
1161    MultipleAttributesWithIndexOperand,
1162    EdgeOperand,
1163    PyEdgeSingleAttributeWithIndexGroupOperand,
1164    PyEdgeSingleAttributeWithoutIndexGroupOperand,
1165    PyEdgeMultipleValuesWithIndexGroupOperand
1166);
1167
1168macro_rules! implement_single_attribute_operand {
1169    ($name:ident, $kind:ident, $generic:ty) => {
1170        #[pyclass(frozen)]
1171        #[repr(transparent)]
1172        #[derive(Clone)]
1173        pub struct $name(Wrapper<$kind<$generic>>);
1174
1175        impl From<Wrapper<$kind<$generic>>> for $name {
1176            fn from(operand: Wrapper<$kind<$generic>>) -> Self {
1177                Self(operand)
1178            }
1179        }
1180
1181        impl From<$name> for Wrapper<$kind<$generic>> {
1182            fn from(operand: $name) -> Self {
1183                operand.0
1184            }
1185        }
1186
1187        impl Deref for $name {
1188            type Target = Wrapper<$kind<$generic>>;
1189
1190            fn deref(&self) -> &Self::Target {
1191                &self.0
1192            }
1193        }
1194
1195        #[pymethods]
1196        impl $name {
1197            pub fn greater_than(&self, attribute: PySingleAttributeComparisonOperand) {
1198                self.0.greater_than(attribute);
1199            }
1200
1201            pub fn greater_than_or_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
1202                self.0.greater_than_or_equal_to(attribute);
1203            }
1204
1205            pub fn less_than(&self, attribute: PySingleAttributeComparisonOperand) {
1206                self.0.less_than(attribute);
1207            }
1208
1209            pub fn less_than_or_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
1210                self.0.less_than_or_equal_to(attribute);
1211            }
1212
1213            pub fn equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
1214                self.0.equal_to(attribute);
1215            }
1216
1217            pub fn not_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
1218                self.0.not_equal_to(attribute);
1219            }
1220
1221            pub fn starts_with(&self, attribute: PySingleAttributeComparisonOperand) {
1222                self.0.starts_with(attribute);
1223            }
1224
1225            pub fn ends_with(&self, attribute: PySingleAttributeComparisonOperand) {
1226                self.0.ends_with(attribute);
1227            }
1228
1229            pub fn contains(&self, attribute: PySingleAttributeComparisonOperand) {
1230                self.0.contains(attribute);
1231            }
1232
1233            pub fn is_in(&self, attributes: PyMultipleAttributesComparisonOperand) {
1234                self.0.is_in(attributes);
1235            }
1236
1237            pub fn is_not_in(&self, attributes: PyMultipleAttributesComparisonOperand) {
1238                self.0.is_not_in(attributes);
1239            }
1240
1241            pub fn add(&self, attribute: PySingleAttributeComparisonOperand) {
1242                self.0.add(attribute);
1243            }
1244
1245            pub fn sub(&self, attribute: PySingleAttributeComparisonOperand) {
1246                self.0.sub(attribute);
1247            }
1248
1249            pub fn mul(&self, attribute: PySingleAttributeComparisonOperand) {
1250                self.0.mul(attribute);
1251            }
1252
1253            pub fn pow(&self, attribute: PySingleAttributeComparisonOperand) {
1254                self.0.pow(attribute);
1255            }
1256
1257            pub fn r#mod(&self, attribute: PySingleAttributeComparisonOperand) {
1258                self.0.r#mod(attribute);
1259            }
1260
1261            pub fn abs(&self) {
1262                self.0.abs();
1263            }
1264
1265            pub fn trim(&self) {
1266                self.0.trim();
1267            }
1268
1269            pub fn trim_start(&self) {
1270                self.0.trim_start();
1271            }
1272
1273            pub fn trim_end(&self) {
1274                self.0.trim_end();
1275            }
1276
1277            pub fn lowercase(&self) {
1278                self.0.lowercase();
1279            }
1280
1281            pub fn uppercase(&self) {
1282                self.0.uppercase();
1283            }
1284
1285            pub fn slice(&self, start: usize, end: usize) {
1286                self.0.slice(start, end);
1287            }
1288
1289            pub fn is_string(&self) {
1290                self.0.is_string();
1291            }
1292
1293            pub fn is_int(&self) {
1294                self.0.is_int();
1295            }
1296
1297            pub fn either_or(&self, either: &Bound<'_, PyFunction>, or: &Bound<'_, PyFunction>) {
1298                self.0.either_or(
1299                    |operand| {
1300                        either
1301                            .call1(($name::from(operand.clone()),))
1302                            .expect("Call must succeed");
1303                    },
1304                    |operand| {
1305                        or.call1(($name::from(operand.clone()),))
1306                            .expect("Call must succeed");
1307                    },
1308                );
1309            }
1310
1311            pub fn exclude(&self, query: &Bound<'_, PyFunction>) {
1312                self.0.exclude(|operand| {
1313                    query
1314                        .call1(($name::from(operand.clone()),))
1315                        .expect("Call must succeed");
1316                });
1317            }
1318
1319            pub fn deep_clone(&self) -> $name {
1320                self.0.deep_clone().into()
1321            }
1322        }
1323    };
1324}
1325
1326implement_single_attribute_operand!(
1327    PyNodeSingleAttributeWithIndexOperand,
1328    SingleAttributeWithIndexOperand,
1329    NodeOperand
1330);
1331implement_single_attribute_operand!(
1332    PyNodeSingleAttributeWithoutIndexOperand,
1333    SingleAttributeWithoutIndexOperand,
1334    NodeOperand
1335);
1336implement_single_attribute_operand!(
1337    PyEdgeSingleAttributeWithIndexOperand,
1338    SingleAttributeWithIndexOperand,
1339    EdgeOperand
1340);
1341implement_single_attribute_operand!(
1342    PyEdgeSingleAttributeWithoutIndexOperand,
1343    SingleAttributeWithoutIndexOperand,
1344    EdgeOperand
1345);
1346
1347macro_rules! implement_single_attribute_grouped_operand {
1348    ($name:ident, $ungrouped_name:ident, $ungrouped_operand_name:ident, $kind:ident, $generic:ty) => {
1349        #[pyclass(frozen)]
1350        #[repr(transparent)]
1351        #[derive(Clone)]
1352        pub struct $name(Wrapper<GroupOperand<$kind<$generic>>>);
1353
1354        impl From<Wrapper<GroupOperand<$kind<$generic>>>> for $name {
1355            fn from(operand: Wrapper<GroupOperand<$kind<$generic>>>) -> Self {
1356                Self(operand)
1357            }
1358        }
1359
1360        impl From<$name> for Wrapper<GroupOperand<$kind<$generic>>> {
1361            fn from(operand: $name) -> Self {
1362                operand.0
1363            }
1364        }
1365
1366        impl Deref for $name {
1367            type Target = Wrapper<GroupOperand<$kind<$generic>>>;
1368
1369            fn deref(&self) -> &Self::Target {
1370                &self.0
1371            }
1372        }
1373
1374        #[pymethods]
1375        impl $name {
1376            pub fn greater_than(&self, attribute: PySingleAttributeComparisonOperand) {
1377                self.0.greater_than(attribute);
1378            }
1379
1380            pub fn greater_than_or_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
1381                self.0.greater_than_or_equal_to(attribute);
1382            }
1383
1384            pub fn less_than(&self, attribute: PySingleAttributeComparisonOperand) {
1385                self.0.less_than(attribute);
1386            }
1387
1388            pub fn less_than_or_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
1389                self.0.less_than_or_equal_to(attribute);
1390            }
1391
1392            pub fn equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
1393                self.0.equal_to(attribute);
1394            }
1395
1396            pub fn not_equal_to(&self, attribute: PySingleAttributeComparisonOperand) {
1397                self.0.not_equal_to(attribute);
1398            }
1399
1400            pub fn starts_with(&self, attribute: PySingleAttributeComparisonOperand) {
1401                self.0.starts_with(attribute);
1402            }
1403
1404            pub fn ends_with(&self, attribute: PySingleAttributeComparisonOperand) {
1405                self.0.ends_with(attribute);
1406            }
1407
1408            pub fn contains(&self, attribute: PySingleAttributeComparisonOperand) {
1409                self.0.contains(attribute);
1410            }
1411
1412            pub fn is_in(&self, attributes: PyMultipleAttributesComparisonOperand) {
1413                self.0.is_in(attributes);
1414            }
1415
1416            pub fn is_not_in(&self, attributes: PyMultipleAttributesComparisonOperand) {
1417                self.0.is_not_in(attributes);
1418            }
1419
1420            pub fn add(&self, attribute: PySingleAttributeComparisonOperand) {
1421                self.0.add(attribute);
1422            }
1423
1424            pub fn sub(&self, attribute: PySingleAttributeComparisonOperand) {
1425                self.0.sub(attribute);
1426            }
1427
1428            pub fn mul(&self, attribute: PySingleAttributeComparisonOperand) {
1429                self.0.mul(attribute);
1430            }
1431
1432            pub fn pow(&self, attribute: PySingleAttributeComparisonOperand) {
1433                self.0.pow(attribute);
1434            }
1435
1436            pub fn r#mod(&self, attribute: PySingleAttributeComparisonOperand) {
1437                self.0.r#mod(attribute);
1438            }
1439
1440            pub fn abs(&self) {
1441                self.0.abs();
1442            }
1443
1444            pub fn trim(&self) {
1445                self.0.trim();
1446            }
1447
1448            pub fn trim_start(&self) {
1449                self.0.trim_start();
1450            }
1451
1452            pub fn trim_end(&self) {
1453                self.0.trim_end();
1454            }
1455
1456            pub fn lowercase(&self) {
1457                self.0.lowercase();
1458            }
1459
1460            pub fn uppercase(&self) {
1461                self.0.uppercase();
1462            }
1463
1464            pub fn slice(&self, start: usize, end: usize) {
1465                self.0.slice(start, end);
1466            }
1467
1468            pub fn is_string(&self) {
1469                self.0.is_string();
1470            }
1471
1472            pub fn is_int(&self) {
1473                self.0.is_int();
1474            }
1475
1476            pub fn either_or(&self, either: &Bound<'_, PyFunction>, or: &Bound<'_, PyFunction>) {
1477                self.0.either_or(
1478                    |operand| {
1479                        either
1480                            .call1(($ungrouped_name::from(operand.clone()),))
1481                            .expect("Call must succeed");
1482                    },
1483                    |operand| {
1484                        or.call1(($ungrouped_name::from(operand.clone()),))
1485                            .expect("Call must succeed");
1486                    },
1487                );
1488            }
1489
1490            pub fn exclude(&self, query: &Bound<'_, PyFunction>) {
1491                self.0.exclude(|operand| {
1492                    query
1493                        .call1(($ungrouped_name::from(operand.clone()),))
1494                        .expect("Call must succeed");
1495                });
1496            }
1497
1498            pub fn ungroup(&self) -> $ungrouped_operand_name {
1499                self.0.ungroup().into()
1500            }
1501
1502            pub fn deep_clone(&self) -> $name {
1503                self.0.deep_clone().into()
1504            }
1505        }
1506    };
1507}
1508
1509implement_single_attribute_grouped_operand!(
1510    PyNodeSingleAttributeWithIndexGroupOperand,
1511    PyNodeSingleAttributeWithIndexOperand,
1512    PyNodeMultipleAttributesWithIndexOperand,
1513    SingleAttributeWithIndexOperand,
1514    NodeOperand
1515);
1516implement_single_attribute_grouped_operand!(
1517    PyNodeSingleAttributeWithoutIndexGroupOperand,
1518    PyNodeSingleAttributeWithoutIndexOperand,
1519    PyNodeMultipleAttributesWithoutIndexOperand,
1520    SingleAttributeWithoutIndexOperand,
1521    NodeOperand
1522);
1523implement_single_attribute_grouped_operand!(
1524    PyEdgeSingleAttributeWithIndexGroupOperand,
1525    PyEdgeSingleAttributeWithIndexOperand,
1526    PyEdgeMultipleAttributesWithIndexOperand,
1527    SingleAttributeWithIndexOperand,
1528    EdgeOperand
1529);
1530implement_single_attribute_grouped_operand!(
1531    PyEdgeSingleAttributeWithoutIndexGroupOperand,
1532    PyEdgeSingleAttributeWithoutIndexOperand,
1533    PyEdgeMultipleAttributesWithoutIndexOperand,
1534    SingleAttributeWithoutIndexOperand,
1535    EdgeOperand
1536);