GeneralManager 0.5.1__py3-none-any.whl → 0.6.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,235 @@
1
+ from __future__ import annotations
2
+ from typing import (
3
+ Type,
4
+ Any,
5
+ Generator,
6
+ TypeVar,
7
+ )
8
+ from django.db import models
9
+ from general_manager.interface.baseInterface import (
10
+ GeneralManagerType,
11
+ )
12
+ from general_manager.bucket.baseBucket import Bucket
13
+
14
+ from general_manager.manager.generalManager import GeneralManager
15
+
16
+ modelsModel = TypeVar("modelsModel", bound=models.Model)
17
+
18
+
19
+ class DatabaseBucket(Bucket[GeneralManagerType]):
20
+
21
+ def __init__(
22
+ self,
23
+ data: models.QuerySet[modelsModel],
24
+ manager_class: Type[GeneralManagerType],
25
+ filter_definitions: dict[str, list[Any]] | None = None,
26
+ exclude_definitions: dict[str, list[Any]] | None = None,
27
+ ):
28
+ """
29
+ Initializes a DatabaseBucket with a queryset, manager class, and optional filter and exclude definitions.
30
+
31
+ If no queryset is provided, constructs one using the manager class and the given filters and excludes. Stores the queryset, manager class, and copies of the filter and exclude definitions for further operations.
32
+ """
33
+ if data is None:
34
+ data = manager_class.Interface._model.objects.filter(
35
+ **filter_definitions
36
+ ).exclude(**exclude_definitions)
37
+ self._data = data
38
+
39
+ self._manager_class = manager_class
40
+ self.filters = {**(filter_definitions or {})}
41
+ self.excludes = {**(exclude_definitions or {})}
42
+
43
+ def __iter__(self) -> Generator[GeneralManagerType]:
44
+ """
45
+ Yields manager instances for each item in the underlying queryset.
46
+
47
+ Iterates over the queryset, returning a new instance of the manager class for each item's primary key.
48
+ """
49
+ for item in self._data:
50
+ yield self._manager_class(item.pk)
51
+
52
+ def __or__(
53
+ self,
54
+ other: Bucket[GeneralManagerType] | GeneralManager[GeneralManagerType],
55
+ ) -> DatabaseBucket[GeneralManagerType]:
56
+ """
57
+ Combines this bucket with another bucket or manager instance using the union operator.
58
+
59
+ If `other` is a manager instance of the same class, creates a bucket containing only that instance and combines it. If `other` is a compatible bucket, returns a new bucket containing the union of both buckets' querysets. Raises a `ValueError` if the types or manager classes are incompatible.
60
+
61
+ Returns:
62
+ A new `DatabaseBucket` containing the combined items.
63
+ """
64
+ if isinstance(other, GeneralManager) and other.__class__ == self._manager_class:
65
+ return self.__or__(
66
+ self._manager_class.filter(id__in=[other.identification["id"]])
67
+ )
68
+ if not isinstance(other, self.__class__):
69
+ raise ValueError("Cannot combine different bucket types")
70
+ if self._manager_class != other._manager_class:
71
+ raise ValueError("Cannot combine different bucket managers")
72
+ return self.__class__(
73
+ self._data | other._data,
74
+ self._manager_class,
75
+ {},
76
+ )
77
+
78
+ def __mergeFilterDefinitions(
79
+ self, basis: dict[str, list[Any]], **kwargs: Any
80
+ ) -> dict[str, list[Any]]:
81
+ """
82
+ Combines existing filter definitions with additional criteria by appending new values to each key's list.
83
+
84
+ Args:
85
+ basis: Existing filter definitions as a dictionary mapping keys to lists of values.
86
+ **kwargs: Additional filter criteria to merge, with each value appended to the corresponding key.
87
+
88
+ Returns:
89
+ A dictionary containing all filter keys with lists of values from both the original and new criteria.
90
+ """
91
+ kwarg_filter: dict[str, list[Any]] = {}
92
+ for key, value in basis.items():
93
+ kwarg_filter[key] = value
94
+ for key, value in kwargs.items():
95
+ if key not in kwarg_filter:
96
+ kwarg_filter[key] = []
97
+ kwarg_filter[key].append(value)
98
+ return kwarg_filter
99
+
100
+ def filter(self, **kwargs: Any) -> DatabaseBucket[GeneralManagerType]:
101
+ """
102
+ Returns a new bucket with manager instances matching the combined filter criteria.
103
+
104
+ Additional filter arguments are merged with any existing filters to further restrict the queryset, producing a new DatabaseBucket instance.
105
+ """
106
+ merged_filter = self.__mergeFilterDefinitions(self.filters, **kwargs)
107
+ return self.__class__(
108
+ self._data.filter(**kwargs),
109
+ self._manager_class,
110
+ merged_filter,
111
+ self.excludes,
112
+ )
113
+
114
+ def exclude(self, **kwargs: Any) -> DatabaseBucket[GeneralManagerType]:
115
+ """
116
+ Returns a new DatabaseBucket excluding items that match the specified criteria.
117
+
118
+ Keyword arguments specify field lookups to exclude from the queryset. The resulting bucket contains only items that do not satisfy these exclusion filters.
119
+ """
120
+ merged_exclude = self.__mergeFilterDefinitions(self.excludes, **kwargs)
121
+ return self.__class__(
122
+ self._data.exclude(**kwargs),
123
+ self._manager_class,
124
+ self.filters,
125
+ merged_exclude,
126
+ )
127
+
128
+ def first(self) -> GeneralManagerType | None:
129
+ """
130
+ Returns the first item in the queryset wrapped in the manager class, or None if the queryset is empty.
131
+ """
132
+ first_element = self._data.first()
133
+ if first_element is None:
134
+ return None
135
+ return self._manager_class(first_element.pk)
136
+
137
+ def last(self) -> GeneralManagerType | None:
138
+ """
139
+ Returns the last item in the queryset wrapped in the manager class, or None if the queryset is empty.
140
+ """
141
+ first_element = self._data.last()
142
+ if first_element is None:
143
+ return None
144
+ return self._manager_class(first_element.pk)
145
+
146
+ def count(self) -> int:
147
+ """
148
+ Returns the number of items in the bucket.
149
+ """
150
+ return self._data.count()
151
+
152
+ def all(self) -> DatabaseBucket:
153
+ """
154
+ Returns a new DatabaseBucket containing all items from the current queryset.
155
+ """
156
+ return self.__class__(self._data.all(), self._manager_class)
157
+
158
+ def get(self, **kwargs: Any) -> GeneralManagerType:
159
+ """
160
+ Retrieves a single item matching the given criteria and returns it as a manager instance.
161
+
162
+ Args:
163
+ **kwargs: Field lookups to identify the item to retrieve.
164
+
165
+ Returns:
166
+ A manager instance wrapping the uniquely matched model object.
167
+
168
+ Raises:
169
+ Does not handle exceptions; any exceptions raised by the underlying queryset's `get()` method will propagate.
170
+ """
171
+ element = self._data.get(**kwargs)
172
+ return self._manager_class(element.pk)
173
+
174
+ def __getitem__(self, item: int | slice) -> GeneralManagerType | DatabaseBucket:
175
+ """
176
+ Enables indexing and slicing of the bucket.
177
+
178
+ If an integer index is provided, returns the manager instance for the corresponding item. If a slice is provided, returns a new bucket containing the sliced queryset.
179
+ """
180
+ if isinstance(item, slice):
181
+ return self.__class__(self._data[item], self._manager_class)
182
+ return self._manager_class(self._data[item].pk)
183
+
184
+ def __len__(self) -> int:
185
+ """
186
+ Returns the number of items in the bucket.
187
+ """
188
+ return self._data.count()
189
+
190
+ def __repr__(self) -> str:
191
+ """
192
+ Returns a string representation of the bucket, showing the manager class name and the underlying queryset.
193
+ """
194
+ return f"{self._manager_class.__name__}Bucket ({self._data})"
195
+
196
+ def __contains__(self, item: GeneralManagerType | models.Model) -> bool:
197
+ """
198
+ Checks if a manager instance or model instance is present in the bucket.
199
+
200
+ Args:
201
+ item: A manager instance or Django model instance to check for membership.
202
+
203
+ Returns:
204
+ True if the item's primary key exists in the underlying queryset; otherwise, False.
205
+ """
206
+ from general_manager.manager.generalManager import GeneralManager
207
+
208
+ if isinstance(item, GeneralManager):
209
+ return item.identification.get("id", None) in self._data.values_list(
210
+ "pk", flat=True
211
+ )
212
+ return item in self._data
213
+
214
+ def sort(
215
+ self,
216
+ key: tuple[str] | str,
217
+ reverse: bool = False,
218
+ ) -> DatabaseBucket:
219
+ """
220
+ Returns a new DatabaseBucket sorted by the specified field or fields.
221
+
222
+ Args:
223
+ key: A field name or tuple of field names to sort by.
224
+ reverse: If True, sorts in descending order.
225
+
226
+ Returns:
227
+ A new DatabaseBucket instance with the sorted queryset.
228
+ """
229
+ if isinstance(key, str):
230
+ key = (key,)
231
+ if reverse:
232
+ sorted_data = self._data.order_by(*[f"-{k}" for k in key])
233
+ else:
234
+ sorted_data = self._data.order_by(*key)
235
+ return self.__class__(sorted_data, self._manager_class)
@@ -0,0 +1,296 @@
1
+ from __future__ import annotations
2
+ from typing import (
3
+ Type,
4
+ Generator,
5
+ Any,
6
+ )
7
+ import json
8
+ from general_manager.manager.groupManager import GroupManager
9
+ from general_manager.bucket.baseBucket import (
10
+ Bucket,
11
+ GeneralManagerType,
12
+ )
13
+
14
+
15
+ class GroupBucket(Bucket[GeneralManagerType]):
16
+
17
+ def __init__(
18
+ self,
19
+ manager_class: Type[GeneralManagerType],
20
+ group_by_keys: tuple[str, ...],
21
+ data: Bucket[GeneralManagerType],
22
+ ):
23
+ """
24
+ Initializes a GroupBucket by grouping data based on specified attribute keys.
25
+
26
+ Args:
27
+ manager_class: The class type of the manager objects to be grouped.
28
+ group_by_keys: Tuple of attribute names to group the data by.
29
+ data: The underlying Bucket containing manager instances to be grouped.
30
+
31
+ Raises:
32
+ TypeError: If any group-by key is not a string.
33
+ ValueError: If any group-by key is not a valid attribute of the manager class.
34
+ """
35
+ super().__init__(manager_class)
36
+ self.__checkGroupByArguments(group_by_keys)
37
+ self._group_by_keys = group_by_keys
38
+ self._data = self.__buildGroupedManager(data)
39
+ self._basis_data = data
40
+
41
+ def __eq__(self, other: object) -> bool:
42
+ """
43
+ Checks whether this GroupBucket is equal to another by comparing grouped data, manager class, and group-by keys.
44
+
45
+ Returns:
46
+ True if both instances have identical grouped data, manager class, and group-by keys; otherwise, False.
47
+ """
48
+ if not isinstance(other, self.__class__):
49
+ return False
50
+ return (
51
+ set(self._data) == set(other._data)
52
+ and self._manager_class == other._manager_class
53
+ and self._group_by_keys == other._group_by_keys
54
+ )
55
+
56
+ def __checkGroupByArguments(self, group_by_keys: tuple[str, ...]) -> None:
57
+ """
58
+ Checks that each group-by key is a string and a valid attribute of the manager class.
59
+
60
+ Raises:
61
+ TypeError: If any group-by key is not a string.
62
+ ValueError: If any group-by key is not a valid attribute of the manager class.
63
+ """
64
+ if not all(isinstance(arg, str) for arg in group_by_keys):
65
+ raise TypeError("groupBy() arguments must be a strings")
66
+ if not all(
67
+ arg in self._manager_class.Interface.getAttributes()
68
+ for arg in group_by_keys
69
+ ):
70
+ raise ValueError(
71
+ f"groupBy() argument must be a valid attribute of {self._manager_class.__name__}"
72
+ )
73
+
74
+ def __buildGroupedManager(
75
+ self,
76
+ data: Bucket[GeneralManagerType],
77
+ ) -> list[GroupManager[GeneralManagerType]]:
78
+ """
79
+ Constructs a list of GroupManager instances, each representing a unique group of entries from the provided data bucket based on the current group-by keys.
80
+
81
+ Args:
82
+ data: The bucket of manager instances to be grouped.
83
+
84
+ Returns:
85
+ A list of GroupManager objects, each corresponding to a unique combination of group-by attribute values found in the data.
86
+ """
87
+ group_by_values: set[tuple[tuple[str, Any], ...]] = set()
88
+ for entry in data:
89
+ key = tuple((arg, getattr(entry, arg)) for arg in self._group_by_keys)
90
+ group_by_values.add(key)
91
+
92
+ groups = []
93
+ for group_by_value in sorted(group_by_values, key=str):
94
+ group_by_dict = {key: value for key, value in group_by_value}
95
+ grouped_manager_objects = data.filter(**group_by_dict)
96
+ groups.append(
97
+ GroupManager(
98
+ self._manager_class, group_by_dict, grouped_manager_objects
99
+ )
100
+ )
101
+ return groups
102
+
103
+ def __or__(self, other: object) -> GroupBucket[GeneralManagerType]:
104
+ """
105
+ Returns a new GroupBucket representing the union of this bucket and another, combining their underlying data.
106
+
107
+ Raises:
108
+ ValueError: If the other object is not a GroupBucket of the same type or uses a different manager class.
109
+ """
110
+ if not isinstance(other, self.__class__):
111
+ raise ValueError("Cannot combine different bucket types")
112
+ if self._manager_class != other._manager_class:
113
+ raise ValueError("Cannot combine different manager classes")
114
+ return GroupBucket(
115
+ self._manager_class,
116
+ self._group_by_keys,
117
+ self._basis_data | other._basis_data,
118
+ )
119
+
120
+ def __iter__(self) -> Generator[GroupManager[GeneralManagerType]]:
121
+ """
122
+ Yields each grouped manager in the current GroupBucket.
123
+
124
+ Returns:
125
+ A generator yielding GroupManager instances representing each group.
126
+ """
127
+ yield from self._data
128
+
129
+ def filter(self, **kwargs: Any) -> GroupBucket[GeneralManagerType]:
130
+ """
131
+ Returns a new GroupBucket containing only the entries from the underlying data that match the specified filter criteria.
132
+
133
+ Keyword arguments correspond to attribute-value pairs used for filtering.
134
+ """
135
+ new_basis_data = self._basis_data.filter(**kwargs)
136
+ return GroupBucket(
137
+ self._manager_class,
138
+ self._group_by_keys,
139
+ new_basis_data,
140
+ )
141
+
142
+ def exclude(self, **kwargs: Any) -> GroupBucket[GeneralManagerType]:
143
+ """
144
+ Returns a new GroupBucket excluding entries from the underlying data that match the given criteria.
145
+
146
+ Keyword arguments specify attribute-value pairs to exclude from the basis data. The resulting GroupBucket retains the same grouping keys and manager class.
147
+ """
148
+ new_basis_data = self._basis_data.exclude(**kwargs)
149
+ return GroupBucket(
150
+ self._manager_class,
151
+ self._group_by_keys,
152
+ new_basis_data,
153
+ )
154
+
155
+ def first(self) -> GroupManager[GeneralManagerType] | None:
156
+ """
157
+ Returns the first grouped manager in the collection, or None if the collection is empty.
158
+ """
159
+ try:
160
+ return next(iter(self))
161
+ except StopIteration:
162
+ return None
163
+
164
+ def last(self) -> GroupManager[GeneralManagerType] | None:
165
+ """
166
+ Returns the last grouped manager in the collection, or None if the collection is empty.
167
+ """
168
+ items = list(self)
169
+ if items:
170
+ return items[-1]
171
+ return None
172
+
173
+ def count(self) -> int:
174
+ """
175
+ Returns the number of grouped managers in the bucket.
176
+ """
177
+ return sum(1 for _ in self)
178
+
179
+ def all(self) -> Bucket[GeneralManagerType]:
180
+ """
181
+ Returns the current GroupBucket instance.
182
+
183
+ This method provides compatibility with interfaces expecting an `all()` method to retrieve the full collection.
184
+ """
185
+ return self
186
+
187
+ def get(self, **kwargs: Any) -> GroupManager[GeneralManagerType]:
188
+ """
189
+ Returns the first grouped manager matching the specified filter criteria.
190
+
191
+ Args:
192
+ **kwargs: Attribute-value pairs to filter grouped managers.
193
+
194
+ Returns:
195
+ The first GroupManager instance matching the filter criteria.
196
+
197
+ Raises:
198
+ ValueError: If no grouped manager matches the provided criteria.
199
+ """
200
+ first_value = self.filter(**kwargs).first()
201
+ if first_value is None:
202
+ raise ValueError(
203
+ f"Cannot find {self._manager_class.__name__} with {kwargs}"
204
+ )
205
+ return first_value
206
+
207
+ def __getitem__(
208
+ self, item: int | slice
209
+ ) -> GroupManager[GeneralManagerType] | GroupBucket[GeneralManagerType]:
210
+ """
211
+ Returns a grouped manager by index or a new GroupBucket by slice.
212
+
213
+ If an integer index is provided, returns the corresponding GroupManager. If a slice is provided, returns a new GroupBucket containing the union of the basis data from the selected groups.
214
+
215
+ Raises:
216
+ ValueError: If slicing results in no groups.
217
+ TypeError: If the argument is not an int or slice.
218
+ """
219
+ if isinstance(item, int):
220
+ return self._data[item]
221
+ elif isinstance(item, slice):
222
+ new_data = self._data[item]
223
+ new_base_data = None
224
+ for manager in new_data:
225
+ if new_base_data is None:
226
+ new_base_data = manager._data
227
+ else:
228
+ new_base_data = new_base_data | manager._data
229
+ if new_base_data is None:
230
+ raise ValueError("Cannot slice an empty GroupBucket")
231
+ return GroupBucket(self._manager_class, self._group_by_keys, new_base_data)
232
+ raise TypeError(f"Invalid argument type: {type(item)}. Expected int or slice.")
233
+
234
+ def __len__(self) -> int:
235
+ """
236
+ Returns the number of grouped managers in the GroupBucket.
237
+ """
238
+ return self.count()
239
+
240
+ def __contains__(self, item: GeneralManagerType) -> bool:
241
+ """
242
+ Checks if the given manager instance is present in the underlying basis data.
243
+
244
+ Args:
245
+ item: The manager instance to check for membership.
246
+
247
+ Returns:
248
+ True if the item exists in the basis data; otherwise, False.
249
+ """
250
+ return item in self._basis_data
251
+
252
+ def sort(
253
+ self,
254
+ key: tuple[str, ...] | str,
255
+ reverse: bool = False,
256
+ ) -> Bucket[GeneralManagerType]:
257
+ """
258
+ Returns a new GroupBucket with grouped managers sorted by the specified attribute keys.
259
+
260
+ Args:
261
+ key: A string or tuple of strings specifying the attribute(s) to sort by.
262
+ reverse: If True, sorts in descending order. Defaults to False.
263
+
264
+ Returns:
265
+ A new GroupBucket instance with grouped managers sorted by the given keys.
266
+ """
267
+ if isinstance(key, str):
268
+ key = (key,)
269
+ if reverse:
270
+ sorted_data = sorted(
271
+ self._data,
272
+ key=lambda x: tuple(getattr(x, k) for k in key),
273
+ reverse=True,
274
+ )
275
+ else:
276
+ sorted_data = sorted(
277
+ self._data, key=lambda x: tuple(getattr(x, k) for k in key)
278
+ )
279
+
280
+ new_bucket = GroupBucket(
281
+ self._manager_class, self._group_by_keys, self._basis_data
282
+ )
283
+ new_bucket._data = sorted_data
284
+ return new_bucket
285
+
286
+ def group_by(self, *group_by_keys: str) -> GroupBucket[GeneralManagerType]:
287
+ """
288
+ Returns a new GroupBucket grouped by the current and additional specified attribute keys.
289
+
290
+ Additional group-by keys are appended to the existing grouping, and the new GroupBucket is constructed from the same underlying data.
291
+ """
292
+ return GroupBucket(
293
+ self._manager_class,
294
+ tuple([*self._group_by_keys, *group_by_keys]),
295
+ self._basis_data,
296
+ )
@@ -1,5 +1,6 @@
1
1
  from typing import Generator
2
- from general_manager.manager.generalManager import GeneralManager, Bucket
2
+ from general_manager.manager.generalManager import GeneralManager
3
+ from general_manager.bucket.baseBucket import Bucket
3
4
  from general_manager.cache.dependencyIndex import (
4
5
  general_manager_name,
5
6
  Dependency,
@@ -12,19 +13,15 @@ class ModelDependencyCollector:
12
13
  @staticmethod
13
14
  def collect(obj) -> Generator[tuple[general_manager_name, filter_type, str]]:
14
15
  """
15
- Recursively yields dependency information from objects related to Django models.
16
+ Recursively extracts dependency information from Django model-related objects.
16
17
 
17
- Traverses the input object and its nested structures to identify instances of
18
- GeneralManager and Bucket, yielding tuples that describe their dependencies.
19
- Yields a tuple of (manager class name, dependency type, dependency value) for
20
- each dependency found.
18
+ Inspects the input object and its nested structures to identify instances of GeneralManager and Bucket, yielding a tuple for each dependency found. Each tuple contains the manager class name, the dependency type ("identification", "filter", or "exclude"), and the string representation of the dependency value.
21
19
 
22
20
  Args:
23
21
  obj: The object or collection to inspect for model dependencies.
24
22
 
25
23
  Yields:
26
- Tuples containing the manager class name, dependency type ("identification",
27
- "filter", or "exclude"), and the string representation of the dependency value.
24
+ Tuples of (manager class name, dependency type, dependency value) for each dependency discovered.
28
25
  """
29
26
  if isinstance(obj, GeneralManager):
30
27
  yield (
@@ -1,3 +0,0 @@
1
- # from .baseInterface import InterfaceBase, Bucket
2
- # from .databaseInterface import DatabaseInterface, ReadOnlyInterface, DatabaseBucket
3
- # from .calculationInterface import CalculationInterface, CalculationBucket