GeneralManager 0.14.1__py3-none-any.whl → 0.15.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.
- general_manager/__init__.py +49 -0
- general_manager/api/__init__.py +36 -0
- general_manager/api/graphql.py +92 -43
- general_manager/api/mutation.py +35 -10
- general_manager/api/property.py +26 -3
- general_manager/apps.py +23 -16
- general_manager/bucket/__init__.py +32 -0
- general_manager/bucket/baseBucket.py +76 -64
- general_manager/bucket/calculationBucket.py +188 -108
- general_manager/bucket/databaseBucket.py +130 -49
- general_manager/bucket/groupBucket.py +113 -60
- general_manager/cache/__init__.py +38 -0
- general_manager/cache/cacheDecorator.py +29 -17
- general_manager/cache/cacheTracker.py +34 -15
- general_manager/cache/dependencyIndex.py +117 -33
- general_manager/cache/modelDependencyCollector.py +17 -8
- general_manager/cache/signals.py +17 -6
- general_manager/factory/__init__.py +34 -5
- general_manager/factory/autoFactory.py +57 -60
- general_manager/factory/factories.py +39 -14
- general_manager/factory/factoryMethods.py +38 -1
- general_manager/interface/__init__.py +36 -0
- general_manager/interface/baseInterface.py +71 -27
- general_manager/interface/calculationInterface.py +18 -10
- general_manager/interface/databaseBasedInterface.py +102 -71
- general_manager/interface/databaseInterface.py +66 -20
- general_manager/interface/models.py +10 -4
- general_manager/interface/readOnlyInterface.py +44 -30
- general_manager/manager/__init__.py +36 -3
- general_manager/manager/generalManager.py +73 -47
- general_manager/manager/groupManager.py +72 -17
- general_manager/manager/input.py +23 -15
- general_manager/manager/meta.py +53 -53
- general_manager/measurement/__init__.py +37 -2
- general_manager/measurement/measurement.py +135 -58
- general_manager/measurement/measurementField.py +161 -61
- general_manager/permission/__init__.py +32 -1
- general_manager/permission/basePermission.py +29 -12
- general_manager/permission/managerBasedPermission.py +32 -26
- general_manager/permission/mutationPermission.py +32 -3
- general_manager/permission/permissionChecks.py +9 -1
- general_manager/permission/permissionDataManager.py +49 -15
- general_manager/permission/utils.py +14 -3
- general_manager/rule/__init__.py +27 -1
- general_manager/rule/handler.py +90 -5
- general_manager/rule/rule.py +40 -27
- general_manager/utils/__init__.py +44 -2
- general_manager/utils/argsToKwargs.py +17 -9
- general_manager/utils/filterParser.py +29 -30
- general_manager/utils/formatString.py +2 -0
- general_manager/utils/jsonEncoder.py +14 -1
- general_manager/utils/makeCacheKey.py +18 -12
- general_manager/utils/noneToZero.py +8 -6
- general_manager/utils/pathMapping.py +92 -29
- general_manager/utils/public_api.py +49 -0
- general_manager/utils/testing.py +135 -69
- {generalmanager-0.14.1.dist-info → generalmanager-0.15.0.dist-info}/METADATA +10 -2
- generalmanager-0.15.0.dist-info/RECORD +62 -0
- generalmanager-0.14.1.dist-info/RECORD +0 -58
- {generalmanager-0.14.1.dist-info → generalmanager-0.15.0.dist-info}/WHEEL +0 -0
- {generalmanager-0.14.1.dist-info → generalmanager-0.15.0.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.14.1.dist-info → generalmanager-0.15.0.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,5 @@
|
|
1
|
+
"""Database-backed bucket implementation for GeneralManager collections."""
|
2
|
+
|
1
3
|
from __future__ import annotations
|
2
4
|
from typing import Type, Any, Generator, TypeVar, TYPE_CHECKING
|
3
5
|
from django.db import models
|
@@ -16,6 +18,7 @@ if TYPE_CHECKING:
|
|
16
18
|
|
17
19
|
|
18
20
|
class DatabaseBucket(Bucket[GeneralManagerType]):
|
21
|
+
"""Bucket implementation backed by Django ORM querysets."""
|
19
22
|
|
20
23
|
def __init__(
|
21
24
|
self,
|
@@ -23,15 +26,18 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
23
26
|
manager_class: Type[GeneralManagerType],
|
24
27
|
filter_definitions: dict[str, list[Any]] | None = None,
|
25
28
|
exclude_definitions: dict[str, list[Any]] | None = None,
|
26
|
-
):
|
29
|
+
) -> None:
|
27
30
|
"""
|
28
|
-
|
31
|
+
Instantiate a database-backed bucket with optional filter state.
|
29
32
|
|
30
33
|
Parameters:
|
31
|
-
data (QuerySet):
|
32
|
-
manager_class (
|
33
|
-
filter_definitions (dict[str, list[Any]]
|
34
|
-
exclude_definitions (dict[str, list[Any]]
|
34
|
+
data (models.QuerySet[modelsModel]): Queryset providing the underlying data.
|
35
|
+
manager_class (type[GeneralManagerType]): GeneralManager subclass used to wrap rows.
|
36
|
+
filter_definitions (dict[str, list[Any]] | None): Pre-existing filter expressions captured from parent buckets.
|
37
|
+
exclude_definitions (dict[str, list[Any]] | None): Pre-existing exclusion expressions captured from parent buckets.
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
None
|
35
41
|
"""
|
36
42
|
self._data = data
|
37
43
|
self._manager_class = manager_class
|
@@ -40,9 +46,10 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
40
46
|
|
41
47
|
def __iter__(self) -> Generator[GeneralManagerType, None, None]:
|
42
48
|
"""
|
43
|
-
|
49
|
+
Iterate over manager instances corresponding to the queryset rows.
|
44
50
|
|
45
|
-
|
51
|
+
Yields:
|
52
|
+
GeneralManagerType: Manager instance for each primary key in the queryset.
|
46
53
|
"""
|
47
54
|
for item in self._data:
|
48
55
|
yield self._manager_class(item.pk)
|
@@ -52,12 +59,16 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
52
59
|
other: Bucket[GeneralManagerType] | GeneralManagerType,
|
53
60
|
) -> DatabaseBucket[GeneralManagerType]:
|
54
61
|
"""
|
55
|
-
|
62
|
+
Merge two database buckets (or bucket and instance) into a single result.
|
56
63
|
|
57
|
-
|
64
|
+
Parameters:
|
65
|
+
other (Bucket[GeneralManagerType] | GeneralManagerType): Bucket or manager instance to merge.
|
58
66
|
|
59
67
|
Returns:
|
60
|
-
DatabaseBucket[GeneralManagerType]:
|
68
|
+
DatabaseBucket[GeneralManagerType]: New bucket containing the combined queryset.
|
69
|
+
|
70
|
+
Raises:
|
71
|
+
ValueError: If the operand is incompatible or uses a different manager class.
|
61
72
|
"""
|
62
73
|
if isinstance(other, GeneralManager) and other.__class__ == self._manager_class:
|
63
74
|
return self.__or__(
|
@@ -77,14 +88,14 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
77
88
|
self, basis: dict[str, list[Any]], **kwargs: Any
|
78
89
|
) -> dict[str, list[Any]]:
|
79
90
|
"""
|
80
|
-
|
91
|
+
Merge stored filter definitions with additional lookup values.
|
81
92
|
|
82
|
-
|
83
|
-
basis: Existing
|
84
|
-
**kwargs:
|
93
|
+
Parameters:
|
94
|
+
basis (dict[str, list[Any]]): Existing lookup definitions copied into the result.
|
95
|
+
**kwargs: New lookups whose values are appended to the result mapping.
|
85
96
|
|
86
97
|
Returns:
|
87
|
-
|
98
|
+
dict[str, list[Any]]: Combined mapping of lookups to value lists.
|
88
99
|
"""
|
89
100
|
kwarg_filter: dict[str, list[Any]] = {}
|
90
101
|
for key, value in basis.items():
|
@@ -95,7 +106,20 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
95
106
|
kwarg_filter[key].append(value)
|
96
107
|
return kwarg_filter
|
97
108
|
|
98
|
-
def __parseFilterDeifintions(
|
109
|
+
def __parseFilterDeifintions(
|
110
|
+
self,
|
111
|
+
**kwargs: Any,
|
112
|
+
) -> tuple[dict[str, Any], dict[str, list[Any]], list[tuple[str, Any, str]]]:
|
113
|
+
"""
|
114
|
+
Separate ORM-compatible filters from Python-side property filters.
|
115
|
+
|
116
|
+
Parameters:
|
117
|
+
**kwargs: Filter lookups supplied to `filter` or `exclude`.
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
tuple[dict[str, Any], dict[str, Any], list[tuple[str, Any, str]]]:
|
121
|
+
Query annotations, ORM-compatible lookups, and Python-evaluated filter specifications.
|
122
|
+
"""
|
99
123
|
annotations: dict[str, Any] = {}
|
100
124
|
orm_kwargs: dict[str, list[Any]] = {}
|
101
125
|
python_filters: list[tuple[str, Any, str]] = []
|
@@ -122,6 +146,16 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
122
146
|
def __parsePythonFilters(
|
123
147
|
self, query_set: models.QuerySet, python_filters: list[tuple[str, Any, str]]
|
124
148
|
) -> list[int]:
|
149
|
+
"""
|
150
|
+
Evaluate Python-only filters and return the primary keys that satisfy them.
|
151
|
+
|
152
|
+
Parameters:
|
153
|
+
query_set (models.QuerySet): Queryset to inspect.
|
154
|
+
python_filters (list[tuple[str, Any, str]]): Filters requiring Python evaluation, each containing the lookup, value, and property root.
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
list[int]: Primary keys of rows that meet all Python-evaluated filters.
|
158
|
+
"""
|
125
159
|
ids: list[int] = []
|
126
160
|
for obj in query_set:
|
127
161
|
inst = self._manager_class(obj.pk)
|
@@ -138,9 +172,17 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
138
172
|
|
139
173
|
def filter(self, **kwargs: Any) -> DatabaseBucket[GeneralManagerType]:
|
140
174
|
"""
|
141
|
-
|
175
|
+
Produce a bucket filtered by the supplied lookups in addition to existing state.
|
176
|
+
|
177
|
+
Parameters:
|
178
|
+
**kwargs (Any): Django-style lookup expressions applied to the underlying queryset.
|
142
179
|
|
143
|
-
|
180
|
+
Returns:
|
181
|
+
DatabaseBucket[GeneralManagerType]: New bucket representing the refined queryset.
|
182
|
+
|
183
|
+
Raises:
|
184
|
+
ValueError: If the ORM rejects the filter arguments.
|
185
|
+
TypeError: If a query annotation callback does not return a queryset.
|
144
186
|
"""
|
145
187
|
annotations, orm_kwargs, python_filters = self.__parseFilterDeifintions(
|
146
188
|
**kwargs
|
@@ -170,9 +212,16 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
170
212
|
|
171
213
|
def exclude(self, **kwargs: Any) -> DatabaseBucket[GeneralManagerType]:
|
172
214
|
"""
|
173
|
-
|
215
|
+
Produce a bucket that excludes rows matching the supplied lookups.
|
216
|
+
|
217
|
+
Parameters:
|
218
|
+
**kwargs (Any): Django-style lookup expressions identifying records to omit.
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
DatabaseBucket[GeneralManagerType]: New bucket representing the filtered queryset.
|
174
222
|
|
175
|
-
|
223
|
+
Raises:
|
224
|
+
TypeError: If a query annotation callback does not return a queryset.
|
176
225
|
"""
|
177
226
|
annotations, orm_kwargs, python_filters = self.__parseFilterDeifintions(
|
178
227
|
**kwargs
|
@@ -199,7 +248,10 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
199
248
|
|
200
249
|
def first(self) -> GeneralManagerType | None:
|
201
250
|
"""
|
202
|
-
|
251
|
+
Return the first row in the queryset as a manager instance.
|
252
|
+
|
253
|
+
Returns:
|
254
|
+
GeneralManagerType | None: First manager instance if available.
|
203
255
|
"""
|
204
256
|
first_element = self._data.first()
|
205
257
|
if first_element is None:
|
@@ -208,7 +260,10 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
208
260
|
|
209
261
|
def last(self) -> GeneralManagerType | None:
|
210
262
|
"""
|
211
|
-
|
263
|
+
Return the last row in the queryset as a manager instance.
|
264
|
+
|
265
|
+
Returns:
|
266
|
+
GeneralManagerType | None: Last manager instance if available.
|
212
267
|
"""
|
213
268
|
first_element = self._data.last()
|
214
269
|
if first_element is None:
|
@@ -217,37 +272,48 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
217
272
|
|
218
273
|
def count(self) -> int:
|
219
274
|
"""
|
220
|
-
|
275
|
+
Count the number of rows represented by the bucket.
|
276
|
+
|
277
|
+
Returns:
|
278
|
+
int: Number of queryset rows.
|
221
279
|
"""
|
222
280
|
return self._data.count()
|
223
281
|
|
224
282
|
def all(self) -> DatabaseBucket:
|
225
283
|
"""
|
226
|
-
|
284
|
+
Return a bucket materialising the queryset without further filtering.
|
285
|
+
|
286
|
+
Returns:
|
287
|
+
DatabaseBucket: Bucket encapsulating `self._data.all()`.
|
227
288
|
"""
|
228
289
|
return self.__class__(self._data.all(), self._manager_class)
|
229
290
|
|
230
291
|
def get(self, **kwargs: Any) -> GeneralManagerType:
|
231
292
|
"""
|
232
|
-
|
293
|
+
Retrieve a single manager instance matching the provided lookups.
|
233
294
|
|
234
|
-
|
235
|
-
|
295
|
+
Parameters:
|
296
|
+
**kwargs (Any): Field lookups resolved via `QuerySet.get`.
|
236
297
|
|
237
298
|
Returns:
|
238
|
-
|
299
|
+
GeneralManagerType: Manager instance wrapping the matched model.
|
239
300
|
|
240
301
|
Raises:
|
241
|
-
|
302
|
+
models.ObjectDoesNotExist: Propagated from the underlying queryset when no row matches.
|
303
|
+
models.MultipleObjectsReturned: Propagated when multiple rows satisfy the lookup.
|
242
304
|
"""
|
243
305
|
element = self._data.get(**kwargs)
|
244
306
|
return self._manager_class(element.pk)
|
245
307
|
|
246
308
|
def __getitem__(self, item: int | slice) -> GeneralManagerType | DatabaseBucket:
|
247
309
|
"""
|
248
|
-
|
310
|
+
Access manager instances by index or obtain a sliced bucket.
|
311
|
+
|
312
|
+
Parameters:
|
313
|
+
item (int | slice): Index of the desired row or slice object describing a range.
|
249
314
|
|
250
|
-
|
315
|
+
Returns:
|
316
|
+
GeneralManagerType | DatabaseBucket: Manager instance for single indices or bucket wrapping the sliced queryset.
|
251
317
|
"""
|
252
318
|
if isinstance(item, slice):
|
253
319
|
return self.__class__(self._data[item], self._manager_class)
|
@@ -255,28 +321,40 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
255
321
|
|
256
322
|
def __len__(self) -> int:
|
257
323
|
"""
|
258
|
-
|
324
|
+
Return the number of rows represented by the bucket.
|
325
|
+
|
326
|
+
Returns:
|
327
|
+
int: Size of the queryset.
|
259
328
|
"""
|
260
329
|
return self._data.count()
|
261
330
|
|
262
331
|
def __str__(self) -> str:
|
263
332
|
"""
|
264
|
-
|
333
|
+
Return a user-friendly representation of the bucket.
|
334
|
+
|
335
|
+
Returns:
|
336
|
+
str: Human-readable description of the queryset and manager class.
|
265
337
|
"""
|
266
338
|
return f"{self._manager_class.__name__}Bucket {self._data} ({len(self._data)} items)"
|
267
339
|
|
268
340
|
def __repr__(self) -> str:
|
269
341
|
"""
|
270
|
-
|
342
|
+
Return a debug representation of the bucket.
|
343
|
+
|
344
|
+
Returns:
|
345
|
+
str: Detailed description including queryset, manager class, filters, and excludes.
|
271
346
|
"""
|
272
347
|
return f"DatabaseBucket ({self._data}, manager_class={self._manager_class.__name__}, filters={self.filters}, excludes={self.excludes})"
|
273
348
|
|
274
349
|
def __contains__(self, item: GeneralManagerType | models.Model) -> bool:
|
275
350
|
"""
|
276
|
-
Determine whether
|
351
|
+
Determine whether the provided instance belongs to the bucket.
|
352
|
+
|
353
|
+
Parameters:
|
354
|
+
item (GeneralManagerType | models.Model): Manager or model instance whose primary key is checked.
|
277
355
|
|
278
356
|
Returns:
|
279
|
-
True
|
357
|
+
bool: True when the primary key exists in the queryset.
|
280
358
|
"""
|
281
359
|
from general_manager.manager.generalManager import GeneralManager
|
282
360
|
|
@@ -292,14 +370,18 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
292
370
|
reverse: bool = False,
|
293
371
|
) -> DatabaseBucket:
|
294
372
|
"""
|
295
|
-
Return a new
|
296
|
-
|
373
|
+
Return a new bucket ordered by the specified fields.
|
374
|
+
|
297
375
|
Parameters:
|
298
|
-
key (str
|
299
|
-
reverse (bool):
|
300
|
-
|
376
|
+
key (str | tuple[str, ...]): Field name(s) used for ordering.
|
377
|
+
reverse (bool): Whether to sort in descending order.
|
378
|
+
|
301
379
|
Returns:
|
302
|
-
DatabaseBucket:
|
380
|
+
DatabaseBucket: Bucket whose queryset is ordered accordingly.
|
381
|
+
|
382
|
+
Raises:
|
383
|
+
ValueError: If sorting by a non-sortable property or when the ORM rejects the ordering.
|
384
|
+
TypeError: If a property annotation callback does not return a queryset.
|
303
385
|
"""
|
304
386
|
if isinstance(key, str):
|
305
387
|
key = (key,)
|
@@ -329,7 +411,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
329
411
|
if python_keys:
|
330
412
|
objs = list(qs)
|
331
413
|
|
332
|
-
def key_func(obj):
|
414
|
+
def key_func(obj: models.Model) -> tuple[object, ...]:
|
333
415
|
inst = self._manager_class(obj.pk)
|
334
416
|
values = []
|
335
417
|
for k in key:
|
@@ -358,14 +440,13 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
358
440
|
|
359
441
|
return self.__class__(qs, self._manager_class)
|
360
442
|
|
361
|
-
|
362
443
|
def none(self) -> DatabaseBucket[GeneralManagerType]:
|
363
444
|
"""
|
364
|
-
Return
|
365
|
-
|
366
|
-
|
445
|
+
Return an empty bucket sharing the same manager class.
|
446
|
+
|
447
|
+
Returns:
|
448
|
+
DatabaseBucket[GeneralManagerType]: Empty bucket retaining filter and exclude state.
|
367
449
|
"""
|
368
450
|
own = self.all()
|
369
451
|
own._data = own._data.none()
|
370
452
|
return own
|
371
|
-
|