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.
Files changed (62) hide show
  1. general_manager/__init__.py +49 -0
  2. general_manager/api/__init__.py +36 -0
  3. general_manager/api/graphql.py +92 -43
  4. general_manager/api/mutation.py +35 -10
  5. general_manager/api/property.py +26 -3
  6. general_manager/apps.py +23 -16
  7. general_manager/bucket/__init__.py +32 -0
  8. general_manager/bucket/baseBucket.py +76 -64
  9. general_manager/bucket/calculationBucket.py +188 -108
  10. general_manager/bucket/databaseBucket.py +130 -49
  11. general_manager/bucket/groupBucket.py +113 -60
  12. general_manager/cache/__init__.py +38 -0
  13. general_manager/cache/cacheDecorator.py +29 -17
  14. general_manager/cache/cacheTracker.py +34 -15
  15. general_manager/cache/dependencyIndex.py +117 -33
  16. general_manager/cache/modelDependencyCollector.py +17 -8
  17. general_manager/cache/signals.py +17 -6
  18. general_manager/factory/__init__.py +34 -5
  19. general_manager/factory/autoFactory.py +57 -60
  20. general_manager/factory/factories.py +39 -14
  21. general_manager/factory/factoryMethods.py +38 -1
  22. general_manager/interface/__init__.py +36 -0
  23. general_manager/interface/baseInterface.py +71 -27
  24. general_manager/interface/calculationInterface.py +18 -10
  25. general_manager/interface/databaseBasedInterface.py +102 -71
  26. general_manager/interface/databaseInterface.py +66 -20
  27. general_manager/interface/models.py +10 -4
  28. general_manager/interface/readOnlyInterface.py +44 -30
  29. general_manager/manager/__init__.py +36 -3
  30. general_manager/manager/generalManager.py +73 -47
  31. general_manager/manager/groupManager.py +72 -17
  32. general_manager/manager/input.py +23 -15
  33. general_manager/manager/meta.py +53 -53
  34. general_manager/measurement/__init__.py +37 -2
  35. general_manager/measurement/measurement.py +135 -58
  36. general_manager/measurement/measurementField.py +161 -61
  37. general_manager/permission/__init__.py +32 -1
  38. general_manager/permission/basePermission.py +29 -12
  39. general_manager/permission/managerBasedPermission.py +32 -26
  40. general_manager/permission/mutationPermission.py +32 -3
  41. general_manager/permission/permissionChecks.py +9 -1
  42. general_manager/permission/permissionDataManager.py +49 -15
  43. general_manager/permission/utils.py +14 -3
  44. general_manager/rule/__init__.py +27 -1
  45. general_manager/rule/handler.py +90 -5
  46. general_manager/rule/rule.py +40 -27
  47. general_manager/utils/__init__.py +44 -2
  48. general_manager/utils/argsToKwargs.py +17 -9
  49. general_manager/utils/filterParser.py +29 -30
  50. general_manager/utils/formatString.py +2 -0
  51. general_manager/utils/jsonEncoder.py +14 -1
  52. general_manager/utils/makeCacheKey.py +18 -12
  53. general_manager/utils/noneToZero.py +8 -6
  54. general_manager/utils/pathMapping.py +92 -29
  55. general_manager/utils/public_api.py +49 -0
  56. general_manager/utils/testing.py +135 -69
  57. {generalmanager-0.14.1.dist-info → generalmanager-0.15.0.dist-info}/METADATA +10 -2
  58. generalmanager-0.15.0.dist-info/RECORD +62 -0
  59. generalmanager-0.14.1.dist-info/RECORD +0 -58
  60. {generalmanager-0.14.1.dist-info → generalmanager-0.15.0.dist-info}/WHEEL +0 -0
  61. {generalmanager-0.14.1.dist-info → generalmanager-0.15.0.dist-info}/licenses/LICENSE +0 -0
  62. {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
- Initialize a DatabaseBucket with a Django queryset, a manager class, and optional filter and exclude definitions.
31
+ Instantiate a database-backed bucket with optional filter state.
29
32
 
30
33
  Parameters:
31
- data (QuerySet): The Django queryset containing model instances to be managed.
32
- manager_class (Type[GeneralManagerType]): The manager class used to wrap model instances.
33
- filter_definitions (dict[str, list[Any]], optional): Initial filter criteria for the queryset.
34
- exclude_definitions (dict[str, list[Any]], optional): Initial exclude criteria for the queryset.
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
- Yields manager instances for each item in the underlying queryset.
49
+ Iterate over manager instances corresponding to the queryset rows.
44
50
 
45
- Iterates over the queryset, returning a new instance of the manager class for each item's primary key.
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
- Return a new bucket containing the union of this bucket and another bucket or manager instance.
62
+ Merge two database buckets (or bucket and instance) into a single result.
56
63
 
57
- If `other` is a manager instance of the same class, it is converted to a bucket before combining. If `other` is a compatible bucket, the resulting bucket contains all unique items from both. Raises a `ValueError` if the types or manager classes are incompatible.
64
+ Parameters:
65
+ other (Bucket[GeneralManagerType] | GeneralManagerType): Bucket or manager instance to merge.
58
66
 
59
67
  Returns:
60
- DatabaseBucket[GeneralManagerType]: A new bucket with the combined items.
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
- Combines existing filter definitions with additional criteria by appending new values to each key's list.
91
+ Merge stored filter definitions with additional lookup values.
81
92
 
82
- Args:
83
- basis: Existing filter definitions as a dictionary mapping keys to lists of values.
84
- **kwargs: Additional filter criteria to merge, with each value appended to the corresponding key.
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
- A dictionary containing all filter keys with lists of values from both the original and new criteria.
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(self, **kwargs: Any):
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
- Returns a new bucket with manager instances matching the combined filter criteria.
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
- Additional filter arguments are merged with any existing filters to further restrict the queryset, producing a new DatabaseBucket instance.
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
- Returns a new DatabaseBucket excluding items that match the specified criteria.
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
- Keyword arguments specify field lookups to exclude from the queryset. The resulting bucket contains only items that do not satisfy these exclusion filters.
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
- Returns the first item in the queryset wrapped in the manager class, or None if the queryset is empty.
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
- Returns the last item in the queryset wrapped in the manager class, or None if the queryset is empty.
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
- Returns the number of items in the bucket.
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
- Returns a new DatabaseBucket containing all items from the current queryset.
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
- Retrieves a single item matching the given criteria and returns it as a manager instance.
293
+ Retrieve a single manager instance matching the provided lookups.
233
294
 
234
- Args:
235
- **kwargs: Field lookups to identify the item to retrieve.
295
+ Parameters:
296
+ **kwargs (Any): Field lookups resolved via `QuerySet.get`.
236
297
 
237
298
  Returns:
238
- A manager instance wrapping the uniquely matched model object.
299
+ GeneralManagerType: Manager instance wrapping the matched model.
239
300
 
240
301
  Raises:
241
- Does not handle exceptions; any exceptions raised by the underlying queryset's `get()` method will propagate.
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
- Enables indexing and slicing of the bucket.
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
- 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.
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
- Returns the number of items in the bucket.
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
- Returns a string representation of the bucket, showing the manager class name and the underlying queryset.
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
- Returns a string representation of the bucket, showing the manager class name and the underlying queryset.
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 a manager instance or Django model instance is present in the bucket.
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 if the item's primary key exists in the underlying queryset; otherwise, False.
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 DatabaseBucket with items sorted by the specified field or fields.
296
-
373
+ Return a new bucket ordered by the specified fields.
374
+
297
375
  Parameters:
298
- key (str or tuple of str): Field name or tuple of field names to sort by.
299
- reverse (bool): If True, sort in descending order.
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: A new bucket containing the sorted items.
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 a new DatabaseBucket instance of the same type containing no items.
365
-
366
- This method creates an empty bucket while preserving the current manager class and bucket type.
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
-