GeneralManager 0.5.0__tar.gz → 0.5.1__tar.gz

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.
Files changed (77) hide show
  1. {generalmanager-0.5.0 → generalmanager-0.5.1}/GeneralManager.egg-info/PKG-INFO +1 -1
  2. {generalmanager-0.5.0 → generalmanager-0.5.1}/GeneralManager.egg-info/SOURCES.txt +1 -0
  3. {generalmanager-0.5.0 → generalmanager-0.5.1}/PKG-INFO +1 -1
  4. {generalmanager-0.5.0 → generalmanager-0.5.1}/pyproject.toml +1 -1
  5. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/interface/baseInterface.py +6 -6
  6. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/manager/groupManager.py +34 -22
  7. generalmanager-0.5.1/tests/test_groupManager.py +319 -0
  8. {generalmanager-0.5.0 → generalmanager-0.5.1}/GeneralManager.egg-info/dependency_links.txt +0 -0
  9. {generalmanager-0.5.0 → generalmanager-0.5.1}/GeneralManager.egg-info/requires.txt +0 -0
  10. {generalmanager-0.5.0 → generalmanager-0.5.1}/GeneralManager.egg-info/top_level.txt +0 -0
  11. {generalmanager-0.5.0 → generalmanager-0.5.1}/LICENSE +0 -0
  12. {generalmanager-0.5.0 → generalmanager-0.5.1}/README.md +0 -0
  13. {generalmanager-0.5.0 → generalmanager-0.5.1}/setup.cfg +0 -0
  14. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/__init__.py +0 -0
  15. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/api/graphql.py +0 -0
  16. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/api/mutation.py +0 -0
  17. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/api/property.py +0 -0
  18. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/apps.py +0 -0
  19. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/auxiliary/__init__.py +0 -0
  20. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/auxiliary/argsToKwargs.py +0 -0
  21. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/auxiliary/filterParser.py +0 -0
  22. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/auxiliary/jsonEncoder.py +0 -0
  23. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/auxiliary/makeCacheKey.py +0 -0
  24. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/auxiliary/noneToZero.py +0 -0
  25. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/auxiliary/pathMapping.py +0 -0
  26. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/cache/cacheDecorator.py +0 -0
  27. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/cache/cacheTracker.py +0 -0
  28. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/cache/dependencyIndex.py +0 -0
  29. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/cache/modelDependencyCollector.py +0 -0
  30. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/cache/signals.py +0 -0
  31. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/factory/__init__.py +0 -0
  32. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/factory/autoFactory.py +0 -0
  33. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/factory/factories.py +0 -0
  34. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/factory/factoryMethods.py +0 -0
  35. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/interface/__init__.py +0 -0
  36. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/interface/calculationInterface.py +0 -0
  37. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/interface/databaseInterface.py +0 -0
  38. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/manager/__init__.py +0 -0
  39. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/manager/generalManager.py +0 -0
  40. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/manager/input.py +0 -0
  41. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/manager/meta.py +0 -0
  42. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/measurement/__init__.py +0 -0
  43. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/measurement/measurement.py +0 -0
  44. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/measurement/measurementField.py +0 -0
  45. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/permission/__init__.py +0 -0
  46. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/permission/basePermission.py +0 -0
  47. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/permission/fileBasedPermission.py +0 -0
  48. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/permission/managerBasedPermission.py +0 -0
  49. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/permission/permissionChecks.py +0 -0
  50. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/permission/permissionDataManager.py +0 -0
  51. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/rule/__init__.py +0 -0
  52. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/rule/handler.py +0 -0
  53. {generalmanager-0.5.0 → generalmanager-0.5.1}/src/general_manager/rule/rule.py +0 -0
  54. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_argsToKwargs.py +0 -0
  55. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_autoFactory.py +0 -0
  56. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_basePermission.py +0 -0
  57. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_cacheDecorator.py +0 -0
  58. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_cacheTracker.py +0 -0
  59. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_dependencyIndex.py +0 -0
  60. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_factories.py +0 -0
  61. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_factoryMethods.py +0 -0
  62. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_filterParser.py +0 -0
  63. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_generalManager.py +0 -0
  64. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_generalManagerMeta.py +0 -0
  65. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_graph_ql.py +0 -0
  66. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_input.py +0 -0
  67. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_jsonEncoder.py +0 -0
  68. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_makeCacheKey.py +0 -0
  69. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_managerBasedPermission.py +0 -0
  70. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_measurement.py +0 -0
  71. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_measurement_field.py +0 -0
  72. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_modelDependencyCollector.py +0 -0
  73. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_noneToZero.py +0 -0
  74. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_rule_handler.py +0 -0
  75. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_rules.py +0 -0
  76. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_settings.py +0 -0
  77. {generalmanager-0.5.0 → generalmanager-0.5.1}/tests/test_signals.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: Kurzbeschreibung deines Pakets
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License-Expression: MIT
@@ -60,6 +60,7 @@ tests/test_filterParser.py
60
60
  tests/test_generalManager.py
61
61
  tests/test_generalManagerMeta.py
62
62
  tests/test_graph_ql.py
63
+ tests/test_groupManager.py
63
64
  tests/test_input.py
64
65
  tests/test_jsonEncoder.py
65
66
  tests/test_makeCacheKey.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: Kurzbeschreibung deines Pakets
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License-Expression: MIT
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "GeneralManager"
10
- version = "0.5.0"
10
+ version = "0.5.1"
11
11
  description = "Kurzbeschreibung deines Pakets"
12
12
  readme = "README.md"
13
13
  authors = [
@@ -21,7 +21,7 @@ if TYPE_CHECKING:
21
21
  from general_manager.manager.input import Input
22
22
  from general_manager.manager.generalManager import GeneralManager
23
23
  from general_manager.manager.meta import GeneralManagerMeta
24
- from general_manager.manager.groupManager import GroupedManager, GroupBucket
24
+ from general_manager.manager.groupManager import GroupManager, GroupBucket
25
25
 
26
26
 
27
27
  GeneralManagerType = TypeVar("GeneralManagerType", bound="GeneralManager")
@@ -239,7 +239,7 @@ class Bucket(ABC, Generic[GeneralManagerType]):
239
239
  @abstractmethod
240
240
  def __iter__(
241
241
  self,
242
- ) -> Generator[GeneralManagerType | GroupedManager[GeneralManagerType]]:
242
+ ) -> Generator[GeneralManagerType | GroupManager[GeneralManagerType]]:
243
243
  raise NotImplementedError
244
244
 
245
245
  @abstractmethod
@@ -251,11 +251,11 @@ class Bucket(ABC, Generic[GeneralManagerType]):
251
251
  raise NotImplementedError
252
252
 
253
253
  @abstractmethod
254
- def first(self) -> GeneralManagerType | GroupedManager[GeneralManagerType] | None:
254
+ def first(self) -> GeneralManagerType | GroupManager[GeneralManagerType] | None:
255
255
  raise NotImplementedError
256
256
 
257
257
  @abstractmethod
258
- def last(self) -> GeneralManagerType | GroupedManager[GeneralManagerType] | None:
258
+ def last(self) -> GeneralManagerType | GroupManager[GeneralManagerType] | None:
259
259
  raise NotImplementedError
260
260
 
261
261
  @abstractmethod
@@ -269,7 +269,7 @@ class Bucket(ABC, Generic[GeneralManagerType]):
269
269
  @abstractmethod
270
270
  def get(
271
271
  self, **kwargs: Any
272
- ) -> GeneralManagerType | GroupedManager[GeneralManagerType]:
272
+ ) -> GeneralManagerType | GroupManager[GeneralManagerType]:
273
273
  raise NotImplementedError
274
274
 
275
275
  @abstractmethod
@@ -277,7 +277,7 @@ class Bucket(ABC, Generic[GeneralManagerType]):
277
277
  self, item: int | slice
278
278
  ) -> (
279
279
  GeneralManagerType
280
- | GroupedManager[GeneralManagerType]
280
+ | GroupManager[GeneralManagerType]
281
281
  | Bucket[GeneralManagerType]
282
282
  ):
283
283
  raise NotImplementedError
@@ -32,6 +32,15 @@ class GroupBucket(Bucket[GeneralManagerType]):
32
32
  self._data = self.__buildGroupedManager(data)
33
33
  self._basis_data = data
34
34
 
35
+ def __eq__(self, other: object) -> bool:
36
+ if not isinstance(other, self.__class__):
37
+ return False
38
+ return (
39
+ self._data == other._data
40
+ and self._manager_class == other._manager_class
41
+ and self._group_by_keys == other._group_by_keys
42
+ )
43
+
35
44
  def __checkGroupByArguments(self, group_by_keys: tuple[str, ...]) -> None:
36
45
  """
37
46
  This method checks if the given arguments are valid for the groupBy method.
@@ -50,7 +59,7 @@ class GroupBucket(Bucket[GeneralManagerType]):
50
59
  def __buildGroupedManager(
51
60
  self,
52
61
  data: Bucket[GeneralManagerType],
53
- ) -> list[GroupedManager[GeneralManagerType]]:
62
+ ) -> list[GroupManager[GeneralManagerType]]:
54
63
  """
55
64
  This method builds the grouped manager.
56
65
  It returns a GroupBucket with the grouped data.
@@ -63,11 +72,11 @@ class GroupBucket(Bucket[GeneralManagerType]):
63
72
  group_by_values.add(json.dumps(group_by_value))
64
73
 
65
74
  groups = []
66
- for group_by_value in group_by_values:
75
+ for group_by_value in sorted(group_by_values):
67
76
  group_by_value = json.loads(group_by_value)
68
77
  grouped_manager_objects = data.filter(**group_by_value)
69
78
  groups.append(
70
- GroupedManager(
79
+ GroupManager(
71
80
  self._manager_class, group_by_value, grouped_manager_objects
72
81
  )
73
82
  )
@@ -84,7 +93,7 @@ class GroupBucket(Bucket[GeneralManagerType]):
84
93
  self._basis_data | other._basis_data,
85
94
  )
86
95
 
87
- def __iter__(self) -> Generator[GroupedManager[GeneralManagerType]]:
96
+ def __iter__(self) -> Generator[GroupManager[GeneralManagerType]]:
88
97
  for grouped_manager in self._data:
89
98
  yield grouped_manager
90
99
 
@@ -104,13 +113,13 @@ class GroupBucket(Bucket[GeneralManagerType]):
104
113
  new_basis_data,
105
114
  )
106
115
 
107
- def first(self) -> GroupedManager[GeneralManagerType] | None:
116
+ def first(self) -> GroupManager[GeneralManagerType] | None:
108
117
  try:
109
118
  return next(iter(self))
110
119
  except StopIteration:
111
120
  return None
112
121
 
113
- def last(self) -> GroupedManager[GeneralManagerType] | None:
122
+ def last(self) -> GroupManager[GeneralManagerType] | None:
114
123
  items = list(self)
115
124
  if items:
116
125
  return items[-1]
@@ -122,7 +131,7 @@ class GroupBucket(Bucket[GeneralManagerType]):
122
131
  def all(self) -> Bucket[GeneralManagerType]:
123
132
  return self
124
133
 
125
- def get(self, **kwargs: Any) -> GroupedManager[GeneralManagerType]:
134
+ def get(self, **kwargs: Any) -> GroupManager[GeneralManagerType]:
126
135
  first_value = self.filter(**kwargs).first()
127
136
  if first_value is None:
128
137
  raise ValueError(
@@ -132,7 +141,7 @@ class GroupBucket(Bucket[GeneralManagerType]):
132
141
 
133
142
  def __getitem__(
134
143
  self, item: int | slice
135
- ) -> GroupedManager[GeneralManagerType] | GroupBucket[GeneralManagerType]:
144
+ ) -> GroupManager[GeneralManagerType] | GroupBucket[GeneralManagerType]:
136
145
  if isinstance(item, int):
137
146
  return self._data[item]
138
147
  elif isinstance(item, slice):
@@ -162,12 +171,14 @@ class GroupBucket(Bucket[GeneralManagerType]):
162
171
  if isinstance(key, str):
163
172
  key = (key,)
164
173
  if reverse:
165
- sorted_data = self._data.sort(
166
- key=lambda x: tuple([-getattr(x, k) for k in key])
174
+ sorted_data = sorted(
175
+ self._data,
176
+ key=lambda x: tuple(getattr(x, k) for k in key),
177
+ reverse=True,
167
178
  )
168
179
  else:
169
- sorted_data = self._data.sort(
170
- key=lambda x: tuple([getattr(x, k) for k in key])
180
+ sorted_data = sorted(
181
+ self._data, key=lambda x: tuple(getattr(x, k) for k in key)
171
182
  )
172
183
 
173
184
  new_bucket = GroupBucket(
@@ -182,11 +193,13 @@ class GroupBucket(Bucket[GeneralManagerType]):
182
193
  It returns a GroupBucket with the grouped data.
183
194
  """
184
195
  return GroupBucket(
185
- self._manager_class, tuple([*self._group_by_keys, *group_by_keys]), self
196
+ self._manager_class,
197
+ tuple([*self._group_by_keys, *group_by_keys]),
198
+ self._basis_data,
186
199
  )
187
200
 
188
201
 
189
- class GroupedManager(Generic[GeneralManagerType]):
202
+ class GroupManager(Generic[GeneralManagerType]):
190
203
  """
191
204
  This class is used to group the data of a GeneralManager.
192
205
  It is used to create a new GeneralManager with the grouped data.
@@ -212,11 +225,6 @@ class GroupedManager(Generic[GeneralManagerType]):
212
225
  and self._group_by_value == other._group_by_value
213
226
  )
214
227
 
215
- def __hash__(self) -> int:
216
- return hash(
217
- (self._manager_class, frozenset(self._group_by_value.items()), self._data)
218
- )
219
-
220
228
  def __repr__(self) -> str:
221
229
  return f"{self.__class__.__name__}({self._manager_class}, {self._group_by_value}, {self._data})"
222
230
 
@@ -277,12 +285,16 @@ class GroupedManager(Generic[GeneralManagerType]):
277
285
  for entry in total_data:
278
286
  new_data.update(entry)
279
287
  elif issubclass(data_type, str):
280
- new_data = " ".join(total_data)
288
+ temp_data = []
289
+ for entry in total_data:
290
+ if entry not in temp_data:
291
+ temp_data.append(str(entry))
292
+ new_data = ", ".join(temp_data)
293
+ elif issubclass(data_type, bool):
294
+ new_data = any(total_data)
281
295
  elif issubclass(data_type, (int, float, Measurement)):
282
296
  new_data = sum(total_data)
283
297
  elif issubclass(data_type, (datetime, date, time)):
284
298
  new_data = max(total_data)
285
- elif issubclass(data_type, bool):
286
- new_data = any(total_data)
287
299
 
288
300
  return new_data
@@ -0,0 +1,319 @@
1
+ # type: ignore
2
+ from datetime import date
3
+ from django.test import TestCase
4
+ from general_manager.api.graphql import GraphQLProperty
5
+ from general_manager.manager.groupManager import (
6
+ GroupBucket,
7
+ GroupManager,
8
+ )
9
+ from general_manager.measurement import Measurement
10
+
11
+
12
+ # Stub Interface to simulate attribute definitions
13
+ class DummyInterface:
14
+ attr_types = {
15
+ "a": {"type": int},
16
+ "b": {"type": str},
17
+ "c": {"type": list},
18
+ "date": {"type": date},
19
+ "flag": {"type": bool},
20
+ "items": {"type": dict},
21
+ }
22
+
23
+ @staticmethod
24
+ def getAttributes():
25
+ return {attr: {} for attr in DummyInterface.attr_types}
26
+
27
+ @staticmethod
28
+ def getAttributeTypes():
29
+ return DummyInterface.attr_types
30
+
31
+
32
+ # Stub Manager to use with GroupBucket
33
+ class DummyManager:
34
+ Interface = DummyInterface
35
+
36
+ def __init__(self, **attrs):
37
+ for name, value in attrs.items():
38
+ setattr(self, name, value)
39
+
40
+ @GraphQLProperty
41
+ def extraMethod(self) -> str:
42
+ return "extra method result"
43
+
44
+
45
+ # Simple list-based Bucket stub
46
+ class ListBucket(list):
47
+ def __init__(self, items):
48
+ super().__init__(items)
49
+
50
+ def filter(self, **kwargs):
51
+ # Return items matching all kwargs
52
+ return ListBucket(
53
+ [
54
+ item
55
+ for item in self
56
+ if all(getattr(item, k) == v for k, v in kwargs.items())
57
+ ]
58
+ )
59
+
60
+ def exclude(self, **kwargs):
61
+ # Return items not matching any kwargs
62
+ return ListBucket(
63
+ [
64
+ item
65
+ for item in self
66
+ if not all(getattr(item, k) == v for k, v in kwargs.items())
67
+ ]
68
+ )
69
+
70
+ def sort(self, key, **kwargs):
71
+ # Sort using given key function
72
+ return ListBucket(sorted(self, key=key))
73
+
74
+ def __or__(self, other):
75
+ # Combine two buckets
76
+ return ListBucket(list(self) + list(other))
77
+
78
+
79
+ class GroupBucketTests(TestCase):
80
+ # Test that non-string group_by arguments raise TypeError
81
+ def test_invalid_group_by_type_raises(self):
82
+ with self.assertRaises(TypeError):
83
+ GroupBucket(DummyManager, (123,), ListBucket([]))
84
+
85
+ # Test that invalid attribute names raise TypeError
86
+ def test_invalid_group_by_key_raises(self):
87
+ with self.assertRaises(TypeError):
88
+ GroupBucket(DummyManager, ("nonexistent",), ListBucket([]))
89
+
90
+ # Test grouping logic produces correct number of groups and keys
91
+ def test_build_grouped_manager(self):
92
+ items = [
93
+ DummyManager(
94
+ a=1, b="x", c=[1], date=date(2020, 1, 1), flag=True, items={"k": 1}
95
+ ),
96
+ DummyManager(
97
+ a=1, b="x", c=[2], date=date(2021, 1, 1), flag=False, items={"k2": 2}
98
+ ),
99
+ DummyManager(
100
+ a=2, b="y", c=[3], date=date(2019, 1, 1), flag=True, items={"k3": 3}
101
+ ),
102
+ ]
103
+ bucket = GroupBucket(DummyManager, ("a", "b"), ListBucket(items))
104
+ # There should be two groups: (1, 'x') and (2, 'y')
105
+ self.assertEqual(bucket.count(), 2)
106
+ keys = {(group.a, group.b) for group in bucket}
107
+ self.assertSetEqual(keys, {(1, "x"), (2, "y")})
108
+
109
+ # Test that __or__ combines two buckets correctly
110
+ def test_or_combines_buckets(self):
111
+ b1 = GroupBucket(DummyManager, ("a",), ListBucket([DummyManager(a=1)]))
112
+ b2 = GroupBucket(DummyManager, ("a",), ListBucket([DummyManager(a=2)]))
113
+ combined = b1 | b2
114
+ self.assertEqual(combined.count(), 2)
115
+
116
+ # Test that filter and exclude delegate to underlying bucket
117
+ def test_filter_and_exclude_delegate(self):
118
+ items = [DummyManager(a=1), DummyManager(a=2)]
119
+ gb = GroupBucket(DummyManager, ("a",), ListBucket(items))
120
+ filtered = gb.filter(a=1)
121
+ self.assertTrue(all(group.a == 1 for group in filtered))
122
+
123
+ excluded = gb.exclude(a=1)
124
+ self.assertTrue(all(group.a != 1 for group in excluded))
125
+
126
+ # Test indexing and slicing behavior
127
+ def test_getitem_and_slice(self):
128
+ items = [DummyManager(a=i) for i in (1, 1, 2, 2)]
129
+ gb = GroupBucket(DummyManager, ("a",), ListBucket(items))
130
+ # Single index returns a GroupManager
131
+ gm0 = gb[0]
132
+ self.assertIsInstance(gm0, GroupManager)
133
+ # Slice returns a GroupBucket with combined base data
134
+ slice_gb = gb[0:1]
135
+ self.assertIsInstance(slice_gb, GroupBucket)
136
+ self.assertEqual(slice_gb.count(), 1)
137
+
138
+ # Test get() method for present and missing values
139
+ def test_get_returns_and_raises(self):
140
+ items = [DummyManager(a=1), DummyManager(a=2)]
141
+ gb = GroupBucket(DummyManager, ("a",), ListBucket(items))
142
+ # Getting existing group
143
+ result = gb.get(a=2)
144
+ self.assertEqual(result.a, 2)
145
+ # Getting non-existing raises ValueError
146
+ with self.assertRaises(ValueError):
147
+ gb.get(a=3)
148
+
149
+ def test_last(self):
150
+ # Test that last() returns the last item based on the grouping key
151
+ items = [
152
+ DummyManager(a=1),
153
+ DummyManager(a=2),
154
+ DummyManager(a=5),
155
+ DummyManager(a=4),
156
+ ]
157
+ gb = GroupBucket(DummyManager, ("a",), ListBucket(items))
158
+ result = gb.last()
159
+ self.assertEqual(result.a, 5)
160
+
161
+ def test_group_manager_data_order(self):
162
+ items = [
163
+ DummyManager(a=1),
164
+ DummyManager(a=2),
165
+ DummyManager(a=3),
166
+ DummyManager(a=4),
167
+ ]
168
+ gb1 = GroupBucket(DummyManager, ("a",), ListBucket(items))
169
+ gb2 = GroupBucket(DummyManager, ("a",), ListBucket(items))
170
+
171
+ self.assertEqual(gb1, gb2)
172
+ for i in range(4):
173
+ self.assertEqual(gb1[i].a, gb2[i].a)
174
+
175
+ def test_group_manager_data_with_sorting(self):
176
+ # Test sorting within a GroupBucket
177
+ items = [
178
+ DummyManager(a=1, b="d"),
179
+ DummyManager(a=2, b="b"),
180
+ DummyManager(a=3, b="c"),
181
+ DummyManager(a=4, b="a"),
182
+ ]
183
+ gb = GroupBucket(DummyManager, ("a",), ListBucket(items))
184
+ sorted_gm = gb.sort("b")
185
+ self.assertEqual(
186
+ [gm.b for gm in sorted_gm],
187
+ ["a", "b", "c", "d"],
188
+ )
189
+ reverse_sorted_gm = gb.sort("b", reverse=True)
190
+ self.assertEqual(
191
+ [gm.b for gm in reverse_sorted_gm],
192
+ ["d", "c", "b", "a"],
193
+ )
194
+
195
+ def test_group_manager_all(self):
196
+ # Test that all() returns a GroupManager with all items
197
+ items = [DummyManager(a=i) for i in range(5)]
198
+ gb = GroupBucket(DummyManager, ("a",), ListBucket(items))
199
+ all_gm = gb.all()
200
+ self.assertIsInstance(all_gm, GroupBucket)
201
+ self.assertEqual(len(all_gm), 5)
202
+ self.assertEqual(all_gm[0].a, 0)
203
+ self.assertEqual(all_gm[4].a, 4)
204
+
205
+ def test_group_manager_count(self):
206
+ # Test that count() returns the correct number of groups
207
+ items = [DummyManager(a=i) for i in range(5)]
208
+ gb = GroupBucket(DummyManager, ("a",), ListBucket(items))
209
+ self.assertEqual(gb.count(), 5)
210
+
211
+ def test_group_manager_contains(self):
212
+ # Test that __contains__ checks for group existence
213
+ items = [DummyManager(a=i) for i in range(5)]
214
+ gb = GroupBucket(DummyManager, ("a",), ListBucket(items))
215
+ self.assertTrue(items[0] in gb)
216
+ self.assertFalse(DummyManager(a=6) in gb)
217
+
218
+ def test_double_grouping(self):
219
+ # Test grouping by multiple attributes
220
+ items = [
221
+ DummyManager(a=1, b="x"),
222
+ DummyManager(a=1, b="y"),
223
+ DummyManager(a=2, b="x"),
224
+ ]
225
+ gb = GroupBucket(DummyManager, ("a", "b"), ListBucket(items))
226
+ self.assertEqual(gb.count(), 3)
227
+ keys = {(group.a, group.b) for group in gb}
228
+ self.assertSetEqual(keys, {(1, "x"), (1, "y"), (2, "x")})
229
+
230
+ def test_serial_grouping(self):
231
+ # Test that serializing and deserializing works correctly
232
+ items = [
233
+ DummyManager(a=1, b="x", c=[1]),
234
+ DummyManager(a=1, b="y", c=[2]),
235
+ DummyManager(a=2, b="x", c=[3]),
236
+ ]
237
+ gb = GroupBucket(DummyManager, ("a",), ListBucket(items))
238
+
239
+ self.assertEqual(gb.count(), 2)
240
+ gb = gb.group_by("b")
241
+ self.assertEqual(gb.count(), 3)
242
+
243
+
244
+ class GroupManagerCombineValueTests(TestCase):
245
+ def setUp(self):
246
+ self.original_attr_types = DummyInterface.attr_types.copy()
247
+
248
+ def tearDown(self) -> None:
249
+ DummyInterface.attr_types = self.original_attr_types
250
+
251
+ # Parametrized tests for combineValue on various data types
252
+ def helper_make_group_manager(self, values, value_type):
253
+ # Create dummy entries with attribute 'field' set to each value
254
+ entries = [DummyManager(field=v) for v in values]
255
+ bucket = ListBucket(entries)
256
+ # Temporarily inject type info for 'field'
257
+ DummyInterface.attr_types["field"] = {"type": value_type}
258
+ return GroupManager(DummyManager, {}, bucket)
259
+
260
+ def test_combine_integers_sum(self):
261
+ gm = self.helper_make_group_manager([1, 2, 3], int)
262
+ self.assertEqual(gm.combineValue("field"), 6)
263
+
264
+ def test_combine_strings_concat(self):
265
+ gm = self.helper_make_group_manager(["a", "b"], str)
266
+ self.assertEqual(gm.combineValue("field"), "a, b")
267
+
268
+ def test_combine_unique_strings_concat(self):
269
+ gm = self.helper_make_group_manager(["a", "b", "b", "a"], str)
270
+ self.assertEqual(gm.combineValue("field"), "a, b")
271
+
272
+ def test_combine_lists_extend(self):
273
+ gm = self.helper_make_group_manager([[1], [2, 3]], list)
274
+ self.assertEqual(gm.combineValue("field"), [1, 2, 3])
275
+
276
+ def test_combine_only_none(self):
277
+ gm = self.helper_make_group_manager([None, None], type(None))
278
+ self.assertIsNone(gm.combineValue("field"))
279
+
280
+ def test_combine_none_and_value(self):
281
+ gm = self.helper_make_group_manager([None, 1], int)
282
+ self.assertEqual(gm.combineValue("field"), 1)
283
+
284
+ def test_combine_dicts_merge(self):
285
+ gm = self.helper_make_group_manager([{"x": 1}, {"y": 2}], dict)
286
+ self.assertEqual(gm.combineValue("field"), {"x": 1, "y": 2})
287
+
288
+ def test_combine_bools_any(self):
289
+ gm = self.helper_make_group_manager([True, False], bool)
290
+ self.assertTrue(gm.combineValue("field"))
291
+
292
+ def test_combine_dates_max(self):
293
+ dates = [date(2020, 1, 1), date(2021, 1, 1)]
294
+ gm = self.helper_make_group_manager(dates, date)
295
+ self.assertEqual(gm.combineValue("field"), date(2021, 1, 1))
296
+
297
+ def test_combine_measurement_sum(self):
298
+
299
+ gm = self.helper_make_group_manager(
300
+ [Measurement(1, "m"), Measurement(2, "m")], Measurement
301
+ )
302
+ result = gm.combineValue("field")
303
+ self.assertEqual(result, Measurement(3, "m"))
304
+
305
+ def test_iterate_group_manager(self):
306
+ # Test that iterating over GroupManager yields correct items
307
+ DummyInterface.attr_types = {
308
+ "a": {"type": int},
309
+ "b": {"type": str},
310
+ }
311
+ items = [DummyManager(a=i, b=str(i**2)) for i in range(5)]
312
+ gb = GroupBucket(DummyManager, ("a",), ListBucket(items))
313
+ gm = gb.all()
314
+ self.assertEqual(len(list(gm)), 5)
315
+ for i, item in enumerate(gm):
316
+ self.assertEqual(
317
+ dict(item),
318
+ {"a": i, "b": str(i**2), "extraMethod": "extra method result"},
319
+ )
File without changes
File without changes
File without changes