orionis 0.712.0__py3-none-any.whl → 0.713.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1381 @@
1
+ import json
2
+ import operator
3
+ import random
4
+ from functools import reduce
5
+ from typing import Any, Callable, Dict, List, Optional, Union
6
+ from dotty_dict import Dotty, dotty
7
+ from orionis.support.collections.contracts.collection import ICollection
8
+
9
+ class Collection(ICollection):
10
+
11
+ def __init__(self, items: Optional[List[Any]] = None) -> None:
12
+ """Initialize a new collection instance.
13
+
14
+ Parameters
15
+ ----------
16
+ items : list, optional
17
+ Initial items for the collection, by default None
18
+ """
19
+ self._items = items or []
20
+ self.__appends__ = []
21
+
22
+ def take(self, number: int) -> 'Collection':
23
+ """Take a specific number of results from the items.
24
+
25
+ Parameters
26
+ ----------
27
+ number : int
28
+ The number of results to take. If negative, takes from the end.
29
+
30
+ Returns
31
+ -------
32
+ Collection
33
+ A new collection with the specified number of items.
34
+ """
35
+ if number < 0:
36
+ return self[number:]
37
+
38
+ return self[:number]
39
+
40
+ def first(self, callback: Optional[Callable] = None) -> Any:
41
+ """Get the first result in the items.
42
+
43
+ Parameters
44
+ ----------
45
+ callback : callable, optional
46
+ Filter function to apply before returning the first item, by default None
47
+
48
+ Returns
49
+ -------
50
+ mixed
51
+ The first item in the collection, or None if empty.
52
+ """
53
+ filtered = self
54
+ if callback:
55
+ filtered = self.filter(callback)
56
+ response = None
57
+ if filtered:
58
+ response = filtered[0]
59
+ return response
60
+
61
+ def last(self, callback: Optional[Callable] = None) -> Any:
62
+ """Get the last result in the items.
63
+
64
+ Parameters
65
+ ----------
66
+ callback : callable, optional
67
+ Filter function to apply before returning the last item, by default None
68
+
69
+ Returns
70
+ -------
71
+ mixed
72
+ The last item in the collection.
73
+ """
74
+ filtered = self
75
+ if callback:
76
+ filtered = self.filter(callback)
77
+ return filtered[-1]
78
+
79
+ def all(self) -> List[Any]:
80
+ """Get all items in the collection.
81
+
82
+ Returns
83
+ -------
84
+ list
85
+ All items in the collection.
86
+ """
87
+ return self._items
88
+
89
+ def avg(self, key: Optional[str] = None) -> float:
90
+ """Calculate the average of the items.
91
+
92
+ Parameters
93
+ ----------
94
+ key : str, optional
95
+ The key to use for calculating the average of values, by default None
96
+
97
+ Returns
98
+ -------
99
+ float
100
+ The average value.
101
+ """
102
+ result = 0
103
+ items = self.__getValue(key) or self._items
104
+ try:
105
+ result = sum(items) / len(items)
106
+ except TypeError:
107
+ pass
108
+ return result
109
+
110
+ def max(self, key: Optional[str] = None) -> Any:
111
+ """Get the maximum value from the items.
112
+
113
+ Parameters
114
+ ----------
115
+ key : str, optional
116
+ The key to use for finding the maximum value, by default None
117
+
118
+ Returns
119
+ -------
120
+ mixed
121
+ The maximum value.
122
+ """
123
+ result = 0
124
+ items = self.__getValue(key) or self._items
125
+
126
+ try:
127
+ return max(items)
128
+ except (TypeError, ValueError):
129
+ pass
130
+ return result
131
+
132
+ def min(self, key: Optional[str] = None) -> Any:
133
+ """Get the minimum value from the items.
134
+
135
+ Parameters
136
+ ----------
137
+ key : str, optional
138
+ The key to use for finding the minimum value, by default None
139
+
140
+ Returns
141
+ -------
142
+ mixed
143
+ The minimum value.
144
+ """
145
+ result = 0
146
+ items = self.__getValue(key) or self._items
147
+
148
+ try:
149
+ return min(items)
150
+ except (TypeError, ValueError):
151
+ pass
152
+ return result
153
+
154
+ def chunk(self, size: int) -> 'Collection':
155
+ """Break the collection into multiple smaller collections of a given size.
156
+
157
+ Parameters
158
+ ----------
159
+ size : int
160
+ The number of values in each chunk.
161
+
162
+ Returns
163
+ -------
164
+ Collection
165
+ A new collection containing the chunks.
166
+ """
167
+ items = []
168
+ for i in range(0, self.count(), size):
169
+ items.append(self[i : i + size])
170
+ return self.__class__(items)
171
+
172
+ def collapse(self) -> 'Collection':
173
+ """Collapse the collection of arrays into a single, flat collection.
174
+
175
+ Returns
176
+ -------
177
+ Collection
178
+ A new flattened collection.
179
+ """
180
+ items = []
181
+ for item in self:
182
+ items += self.__getItems(item)
183
+ return self.__class__(items)
184
+
185
+ def contains(self, key: Union[str, Callable], value: Any = None) -> bool:
186
+ """Determine if the collection contains a given item.
187
+
188
+ Parameters
189
+ ----------
190
+ key : mixed
191
+ The key or callback function to check for
192
+ value : mixed, optional
193
+ The value to match when key is a string, by default None
194
+
195
+ Returns
196
+ -------
197
+ bool
198
+ True if the item is found, False otherwise.
199
+ """
200
+ if value:
201
+ return self.contains(lambda x: self.__dataGet(x, key) == value)
202
+
203
+ if self.__checkIsCallable(key, raise_exception=False):
204
+ return self.first(key) is not None
205
+
206
+ return key in self
207
+
208
+ def count(self) -> int:
209
+ """Get the number of items in the collection.
210
+
211
+ Returns
212
+ -------
213
+ int
214
+ The number of items.
215
+ """
216
+ return len(self._items)
217
+
218
+ def diff(self, items: Union[List[Any], 'Collection']) -> 'Collection':
219
+ """Get the items that are not present in the given collection.
220
+
221
+ Parameters
222
+ ----------
223
+ items : mixed
224
+ The items to diff against
225
+
226
+ Returns
227
+ -------
228
+ Collection
229
+ A new collection with the difference.
230
+ """
231
+ items = self.__getItems(items)
232
+ return self.__class__([x for x in self if x not in items])
233
+
234
+ def each(self, callback: Callable) -> 'Collection':
235
+ """Iterate over the items in the collection and pass each item to the given callback.
236
+
237
+ Parameters
238
+ ----------
239
+ callback : callable
240
+ The callback function to apply to each item
241
+
242
+ Returns
243
+ -------
244
+ Collection
245
+ The current collection instance.
246
+ """
247
+ self.__checkIsCallable(callback)
248
+
249
+ for k, v in enumerate(self):
250
+ result = callback(v)
251
+ if not result:
252
+ break
253
+ self[k] = result
254
+
255
+ return self
256
+
257
+ def every(self, callback: Callable) -> bool:
258
+ """Determine if all items pass the given callback test.
259
+
260
+ Parameters
261
+ ----------
262
+ callback : callable
263
+ The callback function to test each item
264
+
265
+ Returns
266
+ -------
267
+ bool
268
+ True if all items pass the test, False otherwise.
269
+ """
270
+ self.__checkIsCallable(callback)
271
+ return all(callback(x) for x in self)
272
+
273
+ def filter(self, callback: Callable) -> 'Collection':
274
+ """Filter the collection using the given callback.
275
+
276
+ Parameters
277
+ ----------
278
+ callback : callable
279
+ The callback function to filter items
280
+
281
+ Returns
282
+ -------
283
+ Collection
284
+ A new filtered collection.
285
+ """
286
+ self.__checkIsCallable(callback)
287
+ return self.__class__(list(filter(callback, self)))
288
+
289
+ def flatten(self) -> 'Collection': # NOSONAR
290
+ """Flatten a multi-dimensional collection into a single dimension.
291
+
292
+ Returns
293
+ -------
294
+ Collection
295
+ A new flattened collection.
296
+ """
297
+ def _flatten(items):
298
+ if isinstance(items, dict):
299
+ for v in items.values():
300
+ for x in _flatten(v):
301
+ yield x
302
+ elif isinstance(items, list):
303
+ for i in items:
304
+ for j in _flatten(i):
305
+ yield j
306
+ else:
307
+ yield items
308
+
309
+ return self.__class__(list(_flatten(self._items)))
310
+
311
+ def forget(self, *keys: Any) -> 'Collection':
312
+ """Remove an item from the collection by key.
313
+
314
+ Parameters
315
+ ----------
316
+ *keys : mixed
317
+ The keys to remove from the collection
318
+
319
+ Returns
320
+ -------
321
+ Collection
322
+ The current collection instance.
323
+ """
324
+ keys = sorted(keys, reverse=True)
325
+
326
+ for key in keys:
327
+ del self[key]
328
+
329
+ return self
330
+
331
+ def forPage(self, page: int, number: int) -> 'Collection':
332
+ """Slice the underlying collection array for pagination.
333
+
334
+ Parameters
335
+ ----------
336
+ page : int
337
+ The page number
338
+ number : int
339
+ Number of items per page
340
+
341
+ Returns
342
+ -------
343
+ Collection
344
+ A new collection with the paginated items.
345
+ """
346
+ return self.__class__(self[page:number])
347
+
348
+ def get(self, key: Any, default: Any = None) -> Any:
349
+ """Get an item from the collection by key.
350
+
351
+ Parameters
352
+ ----------
353
+ key : mixed
354
+ The key to retrieve
355
+ default : mixed, optional
356
+ The default value to return if key not found, by default None
357
+
358
+ Returns
359
+ -------
360
+ mixed
361
+ The item at the specified key or default value.
362
+ """
363
+ try:
364
+ return self[key]
365
+ except IndexError:
366
+ pass
367
+
368
+ return self.__value(default)
369
+
370
+ def implode(self, glue: str = ",", key: Optional[str] = None) -> str:
371
+ """Join all items from the collection using a string.
372
+
373
+ Parameters
374
+ ----------
375
+ glue : str, optional
376
+ The string to use for joining, by default ","
377
+ key : str, optional
378
+ The key to pluck from items before joining, by default None
379
+
380
+ Returns
381
+ -------
382
+ str
383
+ The joined string.
384
+ """
385
+ first = self.first()
386
+ if not isinstance(first, str) and key:
387
+ return glue.join(self.pluck(key))
388
+ return glue.join([str(x) for x in self])
389
+
390
+ def isEmpty(self) -> bool:
391
+ """Determine if the collection is empty.
392
+
393
+ Returns
394
+ -------
395
+ bool
396
+ True if the collection is empty, False otherwise.
397
+ """
398
+ return not self
399
+
400
+ def map(self, callback: Callable) -> 'Collection':
401
+ """Run a map over each of the items.
402
+
403
+ Parameters
404
+ ----------
405
+ callback : callable
406
+ The callback function to apply to each item
407
+
408
+ Returns
409
+ -------
410
+ Collection
411
+ A new collection with the mapped items.
412
+ """
413
+ self.__checkIsCallable(callback)
414
+ items = [callback(x) for x in self]
415
+ return self.__class__(items)
416
+
417
+ def mapInto(self, cls: type, method: Optional[str] = None, **kwargs: Any) -> 'Collection':
418
+ """Map items into instances of the given class.
419
+
420
+ Parameters
421
+ ----------
422
+ cls : class
423
+ The class to map items into
424
+ method : str, optional
425
+ The method to call on the class, by default None
426
+ **kwargs : dict
427
+ Additional keyword arguments to pass to the constructor or method
428
+
429
+ Returns
430
+ -------
431
+ Collection
432
+ A new collection with the mapped instances.
433
+ """
434
+ results = []
435
+ for item in self:
436
+ if method:
437
+ results.append(getattr(cls, method)(item, **kwargs))
438
+ else:
439
+ results.append(cls(item))
440
+
441
+ return self.__class__(results)
442
+
443
+ def merge(self, items: Union[List[Any], 'Collection']) -> 'Collection':
444
+ """Merge the collection with the given items.
445
+
446
+ Parameters
447
+ ----------
448
+ items : list or Collection
449
+ The items to merge into the collection
450
+
451
+ Returns
452
+ -------
453
+ Collection
454
+ The current collection instance.
455
+
456
+ Raises
457
+ ------
458
+ ValueError
459
+ If items cannot be merged due to incompatible types.
460
+ """
461
+ if not isinstance(items, list):
462
+ raise ValueError("Unable to merge uncompatible types")
463
+
464
+ items = self.__getItems(items)
465
+
466
+ self._items += items
467
+ return self
468
+
469
+ def pluck(self, value: str, key: Optional[str] = None) -> 'Collection': # NOSONAR
470
+ """Get the values of a given key from all items.
471
+
472
+ Parameters
473
+ ----------
474
+ value : str
475
+ The key to pluck from each item
476
+ key : str, optional
477
+ The key to use as the result key, by default None
478
+
479
+ Returns
480
+ -------
481
+ Collection
482
+ A new collection with the plucked values.
483
+ """
484
+ if key:
485
+ attributes = {}
486
+ else:
487
+ attributes = []
488
+
489
+ if isinstance(self._items, dict):
490
+ return Collection([self._items.get(value)])
491
+
492
+ for item in self:
493
+ if isinstance(item, dict):
494
+ iterable = item.items()
495
+ elif hasattr(item, "serialize"):
496
+ iterable = item.serialize().items()
497
+ else:
498
+ iterable = self.all().items()
499
+
500
+ for k, v in iterable:
501
+ if k == value:
502
+ if key:
503
+ attributes[self.__dataGet(item, key)] = self.__dataGet(
504
+ item, value
505
+ )
506
+ else:
507
+ attributes.append(v)
508
+
509
+ return Collection(attributes)
510
+
511
+ def pop(self) -> Any:
512
+ """Remove and return the last item from the collection.
513
+
514
+ Returns
515
+ -------
516
+ mixed
517
+ The last item from the collection.
518
+ """
519
+ last = self._items.pop()
520
+ return last
521
+
522
+ def prepend(self, value: Any) -> 'Collection':
523
+ """Add an item to the beginning of the collection.
524
+
525
+ Parameters
526
+ ----------
527
+ value : mixed
528
+ The value to prepend
529
+
530
+ Returns
531
+ -------
532
+ Collection
533
+ The current collection instance.
534
+ """
535
+ self._items.insert(0, value)
536
+ return self
537
+
538
+ def pull(self, key: Any) -> Any:
539
+ """Remove an item from the collection and return it.
540
+
541
+ Parameters
542
+ ----------
543
+ key : mixed
544
+ The key of the item to remove
545
+
546
+ Returns
547
+ -------
548
+ mixed
549
+ The removed item.
550
+ """
551
+ value = self.get(key)
552
+ self.forget(key)
553
+ return value
554
+
555
+ def push(self, value: Any) -> 'Collection':
556
+ """Add an item to the end of the collection.
557
+
558
+ Parameters
559
+ ----------
560
+ value : mixed
561
+ The value to add
562
+
563
+ Returns
564
+ -------
565
+ Collection
566
+ The current collection instance.
567
+ """
568
+ self._items.append(value)
569
+ return self
570
+
571
+ def put(self, key: Any, value: Any) -> 'Collection':
572
+ """Put an item in the collection by key.
573
+
574
+ Parameters
575
+ ----------
576
+ key : mixed
577
+ The key to set
578
+ value : mixed
579
+ The value to set
580
+
581
+ Returns
582
+ -------
583
+ Collection
584
+ The current collection instance.
585
+ """
586
+ self[key] = value
587
+ return self
588
+
589
+ def random(self, count: Optional[int] = None) -> Union[Any, 'Collection', None]:
590
+ """Get one or more random items from the collection.
591
+
592
+ Parameters
593
+ ----------
594
+ count : int, optional
595
+ The number of items to return, by default None
596
+
597
+ Returns
598
+ -------
599
+ mixed or Collection
600
+ A single random item if count is None, otherwise a Collection.
601
+
602
+ Raises
603
+ ------
604
+ ValueError
605
+ If count is greater than collection length.
606
+ """
607
+ collection_count = self.count()
608
+ if collection_count == 0:
609
+ return None
610
+ elif count and count > collection_count:
611
+ raise ValueError("count argument must be inferior to collection length.")
612
+ elif count:
613
+ self._items = random.sample(self._items, k=count)
614
+ return self
615
+ else:
616
+ return random.choice(self._items)
617
+
618
+ def reduce(self, callback: Callable, initial: Any = 0) -> Any:
619
+ """Reduce the collection to a single value.
620
+
621
+ Parameters
622
+ ----------
623
+ callback : callable
624
+ The callback function for reduction
625
+ initial : mixed, optional
626
+ The initial value, by default 0
627
+
628
+ Returns
629
+ -------
630
+ mixed
631
+ The reduced value.
632
+ """
633
+ return reduce(callback, self, initial)
634
+
635
+ def reject(self, callback: Callable) -> 'Collection':
636
+ """Filter items that do not pass a given truth test.
637
+
638
+ Parameters
639
+ ----------
640
+ callback : callable
641
+ The callback function to test items
642
+
643
+ Returns
644
+ -------
645
+ Collection
646
+ The current collection instance.
647
+ """
648
+ self.__checkIsCallable(callback)
649
+
650
+ items = self.__getValue(callback) or self._items
651
+ self._items = items
652
+ return self
653
+
654
+ def reverse(self) -> 'Collection':
655
+ """Reverse items order in the collection.
656
+
657
+ Returns
658
+ -------
659
+ Collection
660
+ The current collection instance.
661
+ """
662
+ self._items = self[::-1]
663
+ return self
664
+
665
+ def serialize(self) -> List[Any]:
666
+ """Get the collection items as a serialized array.
667
+
668
+ Returns
669
+ -------
670
+ list
671
+ The serialized items.
672
+ """
673
+ def _serialize(item):
674
+ if self.__appends__:
675
+ item.set_appends(self.__appends__)
676
+
677
+ if hasattr(item, "serialize"):
678
+ return item.serialize()
679
+ elif hasattr(item, "to_dict"):
680
+ return item.to_dict()
681
+ return item
682
+
683
+ return list(map(_serialize, self))
684
+
685
+ def addRelation(self, result: Optional[Dict[str, Any]] = None) -> 'Collection':
686
+ """Add relation data to all models in the collection.
687
+
688
+ Parameters
689
+ ----------
690
+ result : dict, optional
691
+ The relation data to add, by default None
692
+
693
+ Returns
694
+ -------
695
+ Collection
696
+ The current collection instance.
697
+ """
698
+ for model in self._items:
699
+ model.add_relations(result or {})
700
+
701
+ return self
702
+
703
+ def shift(self) -> Any:
704
+ """Remove and return the first item from the collection.
705
+
706
+ Returns
707
+ -------
708
+ mixed
709
+ The first item from the collection.
710
+ """
711
+ return self.pull(0)
712
+
713
+ def sort(self, key: Optional[str] = None) -> 'Collection':
714
+ """Sort through each item with a callback.
715
+
716
+ Parameters
717
+ ----------
718
+ key : str, optional
719
+ The key to sort by, by default None
720
+
721
+ Returns
722
+ -------
723
+ Collection
724
+ The current collection instance.
725
+ """
726
+ if key:
727
+ self._items.sort(key=lambda x: x[key], reverse=False)
728
+ return self
729
+
730
+ self._items = sorted(self)
731
+ return self
732
+
733
+ def sum(self, key: Optional[str] = None) -> float:
734
+ """Get the sum of the given values.
735
+
736
+ Parameters
737
+ ----------
738
+ key : str, optional
739
+ The key to sum by, by default None
740
+
741
+ Returns
742
+ -------
743
+ float
744
+ The sum of the values.
745
+ """
746
+ result = 0
747
+ items = self.__getValue(key) or self._items
748
+ try:
749
+ result = sum(items)
750
+ except TypeError:
751
+ pass
752
+ return result
753
+
754
+ def toJson(self, **kwargs: Any) -> str:
755
+ """Get the collection items as JSON.
756
+
757
+ Parameters
758
+ ----------
759
+ **kwargs : dict
760
+ Additional arguments to pass to json.dumps
761
+
762
+ Returns
763
+ -------
764
+ str
765
+ The JSON representation of the collection.
766
+ """
767
+ return json.dumps(self.serialize(), **kwargs)
768
+
769
+ def groupBy(self, key: str) -> 'Collection':
770
+ """Group the collection items by a given key.
771
+
772
+ Parameters
773
+ ----------
774
+ key : str
775
+ The key to group by
776
+
777
+ Returns
778
+ -------
779
+ Collection
780
+ A new collection with grouped items.
781
+ """
782
+ from itertools import groupby
783
+
784
+ self.sort(key)
785
+
786
+ new_dict = {}
787
+
788
+ for k, v in groupby(self._items, key=lambda x: x[key]):
789
+ new_dict.update({k: list(v)})
790
+
791
+ return Collection(new_dict)
792
+
793
+ def transform(self, callback: Callable) -> 'Collection':
794
+ """Transform each item in the collection using a callback.
795
+
796
+ Parameters
797
+ ----------
798
+ callback : callable
799
+ The callback function to transform items
800
+
801
+ Returns
802
+ -------
803
+ Collection
804
+ The current collection instance.
805
+ """
806
+ self.__checkIsCallable(callback)
807
+ self._items = self.__getValue(callback)
808
+ return self
809
+
810
+ def unique(self, key: Optional[str] = None) -> 'Collection':
811
+ """Return only unique items from the collection array.
812
+
813
+ Parameters
814
+ ----------
815
+ key : str, optional
816
+ The key to use for uniqueness comparison, by default None
817
+
818
+ Returns
819
+ -------
820
+ Collection
821
+ A new collection with unique items.
822
+ """
823
+ if not key:
824
+ items = list(set(self._items))
825
+ return self.__class__(items)
826
+
827
+ keys = set()
828
+ items = []
829
+ if isinstance(self.all(), dict):
830
+ return self
831
+
832
+ for item in self:
833
+ if isinstance(item, dict):
834
+ comparison = item.get(key)
835
+ elif isinstance(item, str):
836
+ comparison = item
837
+ else:
838
+ comparison = getattr(item, key)
839
+ if comparison not in keys:
840
+ items.append(item)
841
+ keys.add(comparison)
842
+
843
+ return self.__class__(items)
844
+
845
+ def where(self, key: str, *args: Any) -> 'Collection':
846
+ """Filter items by a given key value pair.
847
+
848
+ Parameters
849
+ ----------
850
+ key : str
851
+ The key to filter by
852
+ *args : mixed
853
+ The operator and value, or just the value
854
+
855
+ Returns
856
+ -------
857
+ Collection
858
+ A new collection with filtered items.
859
+ """
860
+ op = "=="
861
+ value = args[0]
862
+
863
+ if len(args) >= 2:
864
+ op = args[0]
865
+ value = args[1]
866
+
867
+ attributes = []
868
+
869
+ for item in self._items:
870
+ if isinstance(item, dict):
871
+ comparison = item.get(key)
872
+ else:
873
+ comparison = getattr(item, key)
874
+ if self.__makeComparison(comparison, value, op):
875
+ attributes.append(item)
876
+
877
+ return self.__class__(attributes)
878
+
879
+ def whereIn(self, key: str, values: Union[List[Any], 'Collection']) -> 'Collection':
880
+ """Filter items where a given key's value is in a list of values.
881
+
882
+ Parameters
883
+ ----------
884
+ key : str
885
+ The key to filter by
886
+ values : list or Collection
887
+ The list of values to check against
888
+
889
+ Returns
890
+ -------
891
+ Collection
892
+ A new collection with filtered items.
893
+ """
894
+ values = self.__getItems(values)
895
+ attributes = []
896
+
897
+ for item in self._items:
898
+ if isinstance(item, dict):
899
+ comparison = item.get(key)
900
+ else:
901
+ comparison = getattr(item, key, None)
902
+
903
+ # Handle string comparison for numeric values
904
+ if comparison in values or str(comparison) in [str(v) for v in values]:
905
+ attributes.append(item)
906
+
907
+ return self.__class__(attributes)
908
+
909
+ def whereNotIn(self, key: str, values: Union[List[Any], 'Collection']) -> 'Collection':
910
+ """Filter items where a given key's value is not in a list of values.
911
+
912
+ Parameters
913
+ ----------
914
+ key : str
915
+ The key to filter by
916
+ values : list or Collection
917
+ The list of values to check against
918
+
919
+ Returns
920
+ -------
921
+ Collection
922
+ A new collection with filtered items.
923
+ """
924
+ values = self.__getItems(values)
925
+ attributes = []
926
+
927
+ for item in self._items:
928
+ if isinstance(item, dict):
929
+ comparison = item.get(key)
930
+ else:
931
+ comparison = getattr(item, key, None)
932
+
933
+ # Handle string comparison for numeric values
934
+ if comparison not in values and str(comparison) not in [str(v) for v in values]:
935
+ attributes.append(item)
936
+
937
+ return self.__class__(attributes)
938
+
939
+ def zip(self, items: Union[List[Any], 'Collection']) -> 'Collection':
940
+ """Merge the collection with the given items by index.
941
+
942
+ Parameters
943
+ ----------
944
+ items : list or Collection
945
+ The items to zip with
946
+
947
+ Returns
948
+ -------
949
+ Collection
950
+ A new collection with zipped items.
951
+
952
+ Raises
953
+ ------
954
+ ValueError
955
+ If items parameter is not a list or Collection.
956
+ """
957
+ items = self.__getItems(items)
958
+ if not isinstance(items, list):
959
+ raise ValueError("The 'items' parameter must be a list or a Collection")
960
+
961
+ _items = []
962
+ for x, y in zip(self, items):
963
+ _items.append([x, y])
964
+ return self.__class__(_items)
965
+
966
+ def setAppends(self, appends: List[str]) -> 'Collection':
967
+ """Set the attributes that should be appended to the Collection.
968
+
969
+ Parameters
970
+ ----------
971
+ appends : list
972
+ The attributes to append
973
+
974
+ Returns
975
+ -------
976
+ Collection
977
+ The current collection instance.
978
+ """
979
+ self.__appends__ += appends
980
+ return self
981
+
982
+ def __getValue(self, key: Union[str, Callable, None]) -> Optional[List[Any]]:
983
+ """Get values from items using a key or callback.
984
+
985
+ Parameters
986
+ ----------
987
+ key : str or callable
988
+ The key to extract or callback to apply
989
+
990
+ Returns
991
+ -------
992
+ list
993
+ List of extracted values.
994
+ """
995
+ if not key:
996
+ return None
997
+
998
+ items = []
999
+ for item in self:
1000
+ if isinstance(key, str):
1001
+ if hasattr(item, key) or (key in item):
1002
+ items.append(getattr(item, key, item[key]))
1003
+ elif callable(key):
1004
+ result = key(item)
1005
+ if result:
1006
+ items.append(result)
1007
+ return items
1008
+
1009
+ def __dataGet(self, item: Any, key: str, default: Any = None) -> Any:
1010
+ """Read dictionary value from key using nested notation.
1011
+
1012
+ Parameters
1013
+ ----------
1014
+ item : mixed
1015
+ The item to extract data from
1016
+ key : str
1017
+ The key to look for
1018
+ default : mixed, optional
1019
+ Default value if key not found, by default None
1020
+
1021
+ Returns
1022
+ -------
1023
+ mixed
1024
+ The extracted value or default.
1025
+ """
1026
+ try:
1027
+ if isinstance(item, (list, tuple)):
1028
+ item = item[key]
1029
+ elif isinstance(item, (dict, Dotty)):
1030
+ # Use dotty for nested key access
1031
+ dotty_key = key.replace("*", ":")
1032
+ dotty_item = dotty(item)
1033
+ item = dotty_item.get(dotty_key, default)
1034
+ elif isinstance(item, object):
1035
+ item = getattr(item, key)
1036
+ except (IndexError, AttributeError, KeyError, TypeError):
1037
+ return self.__value(default)
1038
+
1039
+ return item
1040
+
1041
+ def __value(self, value: Any) -> Any:
1042
+ """Get the value from a callable or return the value itself.
1043
+
1044
+ Parameters
1045
+ ----------
1046
+ value : mixed
1047
+ The value or callable to evaluate
1048
+
1049
+ Returns
1050
+ -------
1051
+ mixed
1052
+ The evaluated value.
1053
+ """
1054
+ if callable(value):
1055
+ return value()
1056
+ return value
1057
+
1058
+ def __checkIsCallable(self, callback: Any, raise_exception: bool = True) -> bool:
1059
+ """Check if the given callback is callable.
1060
+
1061
+ Parameters
1062
+ ----------
1063
+ callback : mixed
1064
+ The callback to check
1065
+ raise_exception : bool, optional
1066
+ Whether to raise exception if not callable, by default True
1067
+
1068
+ Returns
1069
+ -------
1070
+ bool
1071
+ True if callable, False otherwise.
1072
+
1073
+ Raises
1074
+ ------
1075
+ ValueError
1076
+ If callback is not callable and raise_exception is True.
1077
+ """
1078
+ if not callable(callback):
1079
+ if not raise_exception:
1080
+ return False
1081
+ raise ValueError("The 'callback' should be a function")
1082
+ return True
1083
+
1084
+ def __makeComparison(self, a: Any, b: Any, op: str) -> bool:
1085
+ """Make a comparison between two values using an operator.
1086
+
1087
+ Parameters
1088
+ ----------
1089
+ a : mixed
1090
+ First value
1091
+ b : mixed
1092
+ Second value
1093
+ op : str
1094
+ Comparison operator
1095
+
1096
+ Returns
1097
+ -------
1098
+ bool
1099
+ Result of the comparison.
1100
+ """
1101
+ operators = {
1102
+ "<": operator.lt,
1103
+ "<=": operator.le,
1104
+ "==": operator.eq,
1105
+ "!=": operator.ne,
1106
+ ">": operator.gt,
1107
+ ">=": operator.ge,
1108
+ }
1109
+ return operators[op](a, b)
1110
+
1111
+ def __iter__(self) -> Any:
1112
+ """
1113
+ Iterate over the items in the collection.
1114
+
1115
+ Returns
1116
+ -------
1117
+ generator
1118
+ A generator yielding each item in the collection.
1119
+ """
1120
+
1121
+ # Yield each item in the internal _items list
1122
+ for item in self._items:
1123
+ yield item
1124
+
1125
+ def __eq__(self, other: Any) -> bool:
1126
+ """
1127
+ Compare the current collection with another object for equality.
1128
+
1129
+ Parameters
1130
+ ----------
1131
+ other : Any
1132
+ The object to compare with the current collection.
1133
+
1134
+ Returns
1135
+ -------
1136
+ bool
1137
+ True if the collections are equal, False otherwise.
1138
+ """
1139
+
1140
+ # If the other object is a Collection, compare its items with self._items
1141
+ if isinstance(other, Collection):
1142
+ return other.all() == self._items
1143
+
1144
+ # Otherwise, compare the other object directly with self._items
1145
+ return other == self._items
1146
+
1147
+ def __getitem__(self, item: Union[int, slice]) -> Union[Any, 'Collection']:
1148
+ """
1149
+ Retrieve an item or a slice of items from the collection.
1150
+
1151
+ Parameters
1152
+ ----------
1153
+ item : int or slice
1154
+ The index or slice to retrieve from the collection.
1155
+
1156
+ Returns
1157
+ -------
1158
+ Any or Collection
1159
+ The item at the specified index, or a new Collection containing the sliced items.
1160
+
1161
+ Notes
1162
+ -----
1163
+ If a slice is provided, a new Collection instance is returned containing the sliced items.
1164
+ If an integer index is provided, the corresponding item is returned.
1165
+ """
1166
+
1167
+ # If the item is a slice, return a new Collection with the sliced items
1168
+ if isinstance(item, slice):
1169
+ return self.__class__(self._items[item])
1170
+
1171
+ # Otherwise, return the item at the specified index
1172
+ return self._items[item]
1173
+
1174
+ def __setitem__(self, key: Any, value: Any) -> None:
1175
+ """
1176
+ Set the value of an item in the collection at the specified key.
1177
+
1178
+ Parameters
1179
+ ----------
1180
+ key : Any
1181
+ The index or key at which to set the value.
1182
+ value : Any
1183
+ The value to assign at the specified key.
1184
+
1185
+ Returns
1186
+ -------
1187
+ None
1188
+ This method does not return a value.
1189
+
1190
+ Notes
1191
+ -----
1192
+ Updates the internal _items list at the given key with the provided value.
1193
+ """
1194
+
1195
+ # Assign the value to the specified key in the internal _items list
1196
+ self._items[key] = value
1197
+
1198
+ def __delitem__(self, key: Any) -> None:
1199
+ """
1200
+ Remove an item from the collection at the specified key.
1201
+
1202
+ Parameters
1203
+ ----------
1204
+ key : Any
1205
+ The index or key of the item to remove from the collection.
1206
+
1207
+ Returns
1208
+ -------
1209
+ None
1210
+ This method does not return a value.
1211
+
1212
+ Notes
1213
+ -----
1214
+ Deletes the item at the given key from the internal _items list.
1215
+ """
1216
+
1217
+ # Delete the item at the specified key from the internal _items list
1218
+ del self._items[key]
1219
+
1220
+ def __ne__(self, other: Any) -> bool:
1221
+ """
1222
+ Determine if the current collection is not equal to another object.
1223
+
1224
+ Parameters
1225
+ ----------
1226
+ other : Any
1227
+ The object to compare with the current collection.
1228
+
1229
+ Returns
1230
+ -------
1231
+ bool
1232
+ True if the collections are not equal, False otherwise.
1233
+
1234
+ Notes
1235
+ -----
1236
+ Uses the internal items for comparison. If `other` is a Collection, compares its items.
1237
+ """
1238
+
1239
+ # Extract items from the other object if it is a Collection
1240
+ other = self.__getItems(other)
1241
+
1242
+ # Return True if the items are not equal, False otherwise
1243
+ return other != self._items
1244
+
1245
+ def __len__(self) -> int:
1246
+ """
1247
+ Return the number of items in the collection.
1248
+
1249
+ Returns
1250
+ -------
1251
+ int
1252
+ The total number of items contained in the collection.
1253
+ """
1254
+
1255
+ # Return the length of the internal _items list
1256
+ return len(self._items)
1257
+
1258
+ def __le__(self, other: Any) -> bool:
1259
+ """
1260
+ Determine if the current collection is less than or equal to another object.
1261
+
1262
+ Parameters
1263
+ ----------
1264
+ other : Any
1265
+ The object to compare with the current collection.
1266
+
1267
+ Returns
1268
+ -------
1269
+ bool
1270
+ True if the current collection is less than or equal to the other object, False otherwise.
1271
+
1272
+ Notes
1273
+ -----
1274
+ Uses the internal items for comparison. If `other` is a Collection, compares its items.
1275
+ """
1276
+
1277
+ # Extract items from the other object if it is a Collection
1278
+ other = self.__getItems(other)
1279
+
1280
+ # Return True if the items are less than or equal, False otherwise
1281
+ return self._items <= other
1282
+
1283
+ def __lt__(self, other: Any) -> bool:
1284
+ """
1285
+ Determine if the current collection is less than another object.
1286
+
1287
+ Parameters
1288
+ ----------
1289
+ other : Any
1290
+ The object to compare with the current collection.
1291
+
1292
+ Returns
1293
+ -------
1294
+ bool
1295
+ True if the current collection is less than the other object, False otherwise.
1296
+
1297
+ Notes
1298
+ -----
1299
+ Uses the internal items for comparison. If `other` is a Collection, compares its items.
1300
+ """
1301
+
1302
+ # Extract items from the other object if it is a Collection
1303
+ other = self.__getItems(other)
1304
+
1305
+ # Return True if the items are less than, False otherwise
1306
+ return self._items < other
1307
+
1308
+ def __ge__(self, other: Any) -> bool:
1309
+ """
1310
+ Determine if the current collection is greater than or equal to another object.
1311
+
1312
+ Parameters
1313
+ ----------
1314
+ other : Any
1315
+ The object to compare with the current collection.
1316
+
1317
+ Returns
1318
+ -------
1319
+ bool
1320
+ True if the current collection is greater than or equal to the other object, False otherwise.
1321
+
1322
+ Notes
1323
+ -----
1324
+ Uses the internal items for comparison. If `other` is a Collection, compares its items.
1325
+ """
1326
+
1327
+ # Extract items from the other object if it is a Collection
1328
+ other = self.__getItems(other)
1329
+
1330
+ # Return True if the items are greater than or equal, False otherwise
1331
+ return self._items >= other
1332
+
1333
+ def __gt__(self, other: Any) -> bool:
1334
+ """
1335
+ Determine if the current collection is greater than another object.
1336
+
1337
+ Parameters
1338
+ ----------
1339
+ other : Any
1340
+ The object to compare with the current collection.
1341
+
1342
+ Returns
1343
+ -------
1344
+ bool
1345
+ True if the current collection is greater than the other object, False otherwise.
1346
+
1347
+ Notes
1348
+ -----
1349
+ Uses the internal items for comparison. If `other` is a Collection, compares its items.
1350
+ """
1351
+
1352
+ # Extract items from the other object if it is a Collection
1353
+ other = self.__getItems(other)
1354
+
1355
+ # Return True if the items are greater than, False otherwise
1356
+ return self._items > other
1357
+
1358
+ @classmethod
1359
+ def __getItems(cls, items: Any) -> Any:
1360
+ """
1361
+ Extracts the underlying items from a Collection instance or returns the input as-is.
1362
+
1363
+ Parameters
1364
+ ----------
1365
+ items : Collection or Any
1366
+ The input to extract items from. If a Collection, its items are returned; otherwise, the input is returned unchanged.
1367
+
1368
+ Returns
1369
+ -------
1370
+ Any
1371
+ The extracted items if `items` is a Collection, otherwise the original input.
1372
+ """
1373
+
1374
+ # If the input is a Collection, extract its items using the all() method
1375
+ if isinstance(items, Collection):
1376
+ items = items.all()
1377
+
1378
+ # Return the extracted items or the original input
1379
+ return items
1380
+
1381
+