GeneralManager 0.17.0__py3-none-any.whl → 0.19.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 (68) hide show
  1. general_manager/__init__.py +11 -1
  2. general_manager/_types/api.py +0 -1
  3. general_manager/_types/bucket.py +0 -1
  4. general_manager/_types/cache.py +0 -1
  5. general_manager/_types/factory.py +0 -1
  6. general_manager/_types/general_manager.py +0 -1
  7. general_manager/_types/interface.py +0 -1
  8. general_manager/_types/manager.py +0 -1
  9. general_manager/_types/measurement.py +0 -1
  10. general_manager/_types/permission.py +0 -1
  11. general_manager/_types/rule.py +0 -1
  12. general_manager/_types/utils.py +0 -1
  13. general_manager/api/__init__.py +13 -1
  14. general_manager/api/graphql.py +356 -221
  15. general_manager/api/graphql_subscription_consumer.py +81 -78
  16. general_manager/api/mutation.py +85 -23
  17. general_manager/api/property.py +39 -13
  18. general_manager/apps.py +188 -47
  19. general_manager/bucket/__init__.py +10 -1
  20. general_manager/bucket/calculationBucket.py +155 -53
  21. general_manager/bucket/databaseBucket.py +157 -45
  22. general_manager/bucket/groupBucket.py +133 -44
  23. general_manager/cache/__init__.py +10 -1
  24. general_manager/cache/cacheDecorator.py +3 -0
  25. general_manager/cache/dependencyIndex.py +143 -45
  26. general_manager/cache/signals.py +9 -2
  27. general_manager/factory/__init__.py +10 -1
  28. general_manager/factory/autoFactory.py +55 -13
  29. general_manager/factory/factories.py +110 -40
  30. general_manager/factory/factoryMethods.py +122 -34
  31. general_manager/interface/__init__.py +10 -1
  32. general_manager/interface/baseInterface.py +129 -36
  33. general_manager/interface/calculationInterface.py +35 -18
  34. general_manager/interface/databaseBasedInterface.py +71 -45
  35. general_manager/interface/databaseInterface.py +96 -38
  36. general_manager/interface/models.py +5 -5
  37. general_manager/interface/readOnlyInterface.py +94 -20
  38. general_manager/manager/__init__.py +10 -1
  39. general_manager/manager/generalManager.py +25 -16
  40. general_manager/manager/groupManager.py +20 -6
  41. general_manager/manager/meta.py +84 -16
  42. general_manager/measurement/__init__.py +10 -1
  43. general_manager/measurement/measurement.py +289 -95
  44. general_manager/measurement/measurementField.py +42 -31
  45. general_manager/permission/__init__.py +10 -1
  46. general_manager/permission/basePermission.py +120 -38
  47. general_manager/permission/managerBasedPermission.py +72 -21
  48. general_manager/permission/mutationPermission.py +14 -9
  49. general_manager/permission/permissionChecks.py +14 -12
  50. general_manager/permission/permissionDataManager.py +24 -11
  51. general_manager/permission/utils.py +34 -6
  52. general_manager/public_api_registry.py +36 -10
  53. general_manager/rule/__init__.py +10 -1
  54. general_manager/rule/handler.py +133 -44
  55. general_manager/rule/rule.py +178 -39
  56. general_manager/utils/__init__.py +10 -1
  57. general_manager/utils/argsToKwargs.py +34 -9
  58. general_manager/utils/filterParser.py +22 -7
  59. general_manager/utils/formatString.py +1 -0
  60. general_manager/utils/pathMapping.py +23 -15
  61. general_manager/utils/public_api.py +33 -2
  62. general_manager/utils/testing.py +31 -33
  63. {generalmanager-0.17.0.dist-info → generalmanager-0.19.0.dist-info}/METADATA +3 -1
  64. generalmanager-0.19.0.dist-info/RECORD +77 -0
  65. {generalmanager-0.17.0.dist-info → generalmanager-0.19.0.dist-info}/licenses/LICENSE +1 -1
  66. generalmanager-0.17.0.dist-info/RECORD +0 -77
  67. {generalmanager-0.17.0.dist-info → generalmanager-0.19.0.dist-info}/WHEEL +0 -0
  68. {generalmanager-0.17.0.dist-info → generalmanager-0.19.0.dist-info}/top_level.txt +0 -0
@@ -36,6 +36,110 @@ class SortedFilters(TypedDict):
36
36
  input_excludes: dict[str, Any]
37
37
 
38
38
 
39
+ class InvalidCalculationInterfaceError(TypeError):
40
+ """Raised when a CalculationBucket is initialized with a non-CalculationInterface manager."""
41
+
42
+ def __init__(self) -> None:
43
+ """
44
+ Indicates a manager's interface does not inherit from CalculationInterface.
45
+
46
+ Initializes the exception with the message "CalculationBucket requires a manager whose interface inherits from CalculationInterface."
47
+ """
48
+ super().__init__(
49
+ "CalculationBucket requires a manager whose interface inherits from CalculationInterface."
50
+ )
51
+
52
+
53
+ class IncompatibleBucketTypeError(TypeError):
54
+ """Raised when attempting to combine buckets of different types."""
55
+
56
+ def __init__(self, bucket_type: type, other_type: type) -> None:
57
+ """
58
+ Initialize the error indicating two bucket types cannot be combined.
59
+
60
+ Parameters:
61
+ bucket_type (type): The first bucket class involved in the attempted combination.
62
+ other_type (type): The second bucket class involved in the attempted combination.
63
+
64
+ Notes:
65
+ The exception message is formatted as "Cannot combine {bucket_type.__name__} with {other_type.__name__}."
66
+ """
67
+ super().__init__(
68
+ f"Cannot combine {bucket_type.__name__} with {other_type.__name__}."
69
+ )
70
+
71
+
72
+ class IncompatibleBucketManagerError(TypeError):
73
+ """Raised when attempting to combine buckets with different manager classes."""
74
+
75
+ def __init__(self, first_manager: type, second_manager: type) -> None:
76
+ """
77
+ Indicate that two buckets for different manager classes cannot be combined.
78
+
79
+ Parameters:
80
+ first_manager (type): The first manager class involved in the attempted combination.
81
+ second_manager (type): The second manager class involved in the attempted combination.
82
+
83
+ Description:
84
+ The exception message will include the class names of both managers.
85
+ """
86
+ super().__init__(
87
+ f"Cannot combine buckets for {first_manager.__name__} and {second_manager.__name__}."
88
+ )
89
+
90
+
91
+ class CyclicDependencyError(ValueError):
92
+ """Raised when a cyclic dependency is detected in calculation sorting."""
93
+
94
+ def __init__(self, node: str) -> None:
95
+ """
96
+ Initialize the CyclicDependencyError for a specific node involved in a dependency cycle.
97
+
98
+ Parameters:
99
+ node (str): The identifier of the node where a cycle was detected. The exception message will include this node, e.g. "Cyclic dependency detected: {node}."
100
+ """
101
+ super().__init__(f"Cyclic dependency detected: {node}.")
102
+
103
+
104
+ class InvalidPossibleValuesError(TypeError):
105
+ """Raised when an input field provides invalid possible value definitions."""
106
+
107
+ def __init__(self, key_name: str) -> None:
108
+ """
109
+ Indicate that an input field defines an invalid `possible_values` configuration.
110
+
111
+ Parameters:
112
+ key_name (str): Name of the input field whose `possible_values` configuration is invalid.
113
+ """
114
+ super().__init__(
115
+ f"Invalid possible_values configuration for input '{key_name}'."
116
+ )
117
+
118
+
119
+ class MissingCalculationMatchError(ValueError):
120
+ """Raised when no calculation matches the provided filters."""
121
+
122
+ def __init__(self) -> None:
123
+ """
124
+ Exception raised when no calculation matches the provided filters.
125
+
126
+ Initializes the exception with the message "No matching calculation found."
127
+ """
128
+ super().__init__("No matching calculation found.")
129
+
130
+
131
+ class MultipleCalculationMatchError(ValueError):
132
+ """Raised when more than one calculation matches the provided filters."""
133
+
134
+ def __init__(self) -> None:
135
+ """
136
+ Error raised when more than one calculation matches the provided filters.
137
+
138
+ Initializes the exception with the message "Multiple matching calculations found."
139
+ """
140
+ super().__init__("Multiple matching calculations found.")
141
+
142
+
39
143
  class CalculationBucket(Bucket[GeneralManagerType]):
40
144
  """Bucket that builds cartesian products of calculation input fields."""
41
145
 
@@ -48,20 +152,17 @@ class CalculationBucket(Bucket[GeneralManagerType]):
48
152
  reverse: bool = False,
49
153
  ) -> None:
50
154
  """
51
- Prepare a calculation bucket that enumerates all input combinations.
155
+ Initialize a CalculationBucket configured to enumerate all valid input combinations for a manager.
52
156
 
53
157
  Parameters:
54
- manager_class (type[GeneralManagerType]): Manager subclass whose interface derives from `CalculationInterface`.
55
- filter_definitions (dict[str, dict] | None): Optional filter constraints applied to generated combinations.
56
- exclude_definitions (dict[str, dict] | None): Optional exclude constraints removing combinations.
57
- sort_key (str | tuple[str, ...] | None): Key(s) used to order generated combinations.
58
- reverse (bool): When True, reverse the ordering defined by `sort_key`.
59
-
60
- Returns:
61
- None
158
+ manager_class (type[GeneralManagerType]): Manager subclass whose Interface must inherit from CalculationInterface.
159
+ filter_definitions (dict[str, dict] | None): Mapping of input/property filter constraints to apply to generated combinations.
160
+ exclude_definitions (dict[str, dict] | None): Mapping of input/property exclude constraints to remove generated combinations.
161
+ sort_key (str | tuple[str] | None): Key name or tuple of key names used to order generated manager combinations.
162
+ reverse (bool): If True, reverse the ordering defined by `sort_key`.
62
163
 
63
164
  Raises:
64
- TypeError: If the interface does not inherit from `CalculationInterface`.
165
+ InvalidCalculationInterfaceError: If the manager_class.Interface does not inherit from CalculationInterface.
65
166
  """
66
167
  from general_manager.interface.calculationInterface import (
67
168
  CalculationInterface,
@@ -71,9 +172,7 @@ class CalculationBucket(Bucket[GeneralManagerType]):
71
172
 
72
173
  interface_class = manager_class.Interface
73
174
  if not issubclass(interface_class, CalculationInterface):
74
- raise TypeError(
75
- "CalculationBucket can only be used with CalculationInterface subclasses"
76
- )
175
+ raise InvalidCalculationInterfaceError()
77
176
  self.input_fields = interface_class.input_fields
78
177
  self.filter_definitions = (
79
178
  {} if filter_definitions is None else filter_definitions
@@ -148,25 +247,28 @@ class CalculationBucket(Bucket[GeneralManagerType]):
148
247
  other: Bucket[GeneralManagerType] | GeneralManagerType,
149
248
  ) -> CalculationBucket[GeneralManagerType]:
150
249
  """
151
- Merge two calculation buckets or intersect with a single manager instance.
250
+ Combine this bucket with another bucket or intersect it with a single manager instance.
152
251
 
153
252
  Parameters:
154
- other (Bucket[GeneralManagerType] | GeneralManagerType): Calculation bucket or manager instance to merge.
253
+ other: A CalculationBucket or a GeneralManager instance to merge. If a manager instance of the same manager class is given, it is treated as a filter on that manager's identification.
155
254
 
156
255
  Returns:
157
- CalculationBucket[GeneralManagerType]: Bucket reflecting the combined constraints.
256
+ A new CalculationBucket representing the constraints common to both operands.
158
257
 
159
258
  Raises:
160
- ValueError: If `other` is incompatible or uses a different manager class.
259
+ IncompatibleBucketTypeError: If `other` is neither a CalculationBucket nor a compatible manager instance.
260
+ IncompatibleBucketManagerError: If `other` is a CalculationBucket for a different manager class.
161
261
  """
162
262
  from general_manager.manager.generalManager import GeneralManager
163
263
 
164
264
  if isinstance(other, GeneralManager) and other.__class__ == self._manager_class:
165
265
  return self.__or__(self.filter(id__in=[other.identification]))
166
266
  if not isinstance(other, self.__class__):
167
- raise ValueError("Cannot combine different bucket types")
267
+ raise IncompatibleBucketTypeError(self.__class__, type(other))
168
268
  if self._manager_class != other._manager_class:
169
- raise ValueError("Cannot combine different manager classes")
269
+ raise IncompatibleBucketManagerError(
270
+ self._manager_class, other._manager_class
271
+ )
170
272
 
171
273
  combined_filters = {
172
274
  key: value
@@ -396,10 +498,10 @@ class CalculationBucket(Bucket[GeneralManagerType]):
396
498
  Produce a dependency-respecting order of input fields.
397
499
 
398
500
  Returns:
399
- list[str]: Input names ordered so each dependency appears before dependants.
501
+ list[str]: Input names ordered so each dependency appears before its dependents.
400
502
 
401
503
  Raises:
402
- ValueError: If the dependency graph contains a cycle.
504
+ CyclicDependencyError: If the dependency graph contains a cycle; the exception's `node` identifies a node involved in the cycle.
403
505
  """
404
506
  from collections import defaultdict
405
507
 
@@ -416,22 +518,19 @@ class CalculationBucket(Bucket[GeneralManagerType]):
416
518
 
417
519
  def visit(node: str, temp_mark: set[str]) -> None:
418
520
  """
419
- Perform DFS while detecting cycles in the dependency graph.
521
+ Depth-first search helper that orders dependency nodes and detects cycles.
420
522
 
421
523
  Parameters:
422
- node (str): Input field currently being processed.
423
- temp_mark (set[str]): Nodes visited along the current path.
424
-
425
- Returns:
426
- None
524
+ node (str): The input field being visited.
525
+ temp_mark (set[str]): Nodes on the current DFS path used to detect cycles.
427
526
 
428
527
  Raises:
429
- ValueError: If a cyclic dependency involves `node`.
528
+ CyclicDependencyError: If a cyclic dependency is detected involving `node`.
430
529
  """
431
530
  if node in visited:
432
531
  return
433
532
  if node in temp_mark:
434
- raise ValueError(f"Cyclic dependency detected: {node}")
533
+ raise CyclicDependencyError(node)
435
534
  temp_mark.add(node)
436
535
  for m in graph.get(node, []):
437
536
  visit(m, temp_mark)
@@ -451,18 +550,18 @@ class CalculationBucket(Bucket[GeneralManagerType]):
451
550
  ) -> Union[Iterable[Any], Bucket[Any]]:
452
551
  # Retrieve possible values
453
552
  """
454
- Resolve the potential values for an input field given the current combination.
553
+ Resolve potential values for an input field based on the current partial input combination.
455
554
 
456
555
  Parameters:
457
- key_name (str): Name of the input field.
458
- input_field (Input): Input definition describing type and dependencies.
459
- current_combo (dict): Current partial assignment of input values.
556
+ key_name (str): Name of the input field used for error context.
557
+ input_field (Input): Input definition that may include `possible_values` and `depends_on`.
558
+ current_combo (dict): Partial mapping of already-selected input values required to evaluate dependencies.
460
559
 
461
560
  Returns:
462
- Iterable[Any] | Bucket[Any]: Collection of permissible values.
561
+ Iterable[Any] | Bucket[Any]: An iterable of allowed values for the input or a Bucket supplying candidate values.
463
562
 
464
563
  Raises:
465
- TypeError: If the configured `possible_values` cannot be evaluated.
564
+ InvalidPossibleValuesError: If the input field's `possible_values` is neither callable nor an iterable/Bucket.
466
565
  """
467
566
  if callable(input_field.possible_values):
468
567
  depends_on = input_field.depends_on
@@ -471,7 +570,7 @@ class CalculationBucket(Bucket[GeneralManagerType]):
471
570
  elif isinstance(input_field.possible_values, (Iterable, Bucket)):
472
571
  possible_values = input_field.possible_values
473
572
  else:
474
- raise TypeError(f"Invalid possible_values for input '{key_name}'")
573
+ raise InvalidPossibleValuesError(key_name)
475
574
  return possible_values
476
575
 
477
576
  def _generate_input_combinations(
@@ -481,15 +580,15 @@ class CalculationBucket(Bucket[GeneralManagerType]):
481
580
  excludes: dict[str, dict],
482
581
  ) -> List[dict[str, Any]]:
483
582
  """
484
- Generate all valid input combinations while honouring filters and excludes.
583
+ Generate all valid assignments of input fields that satisfy the provided per-field filters and exclusions.
485
584
 
486
585
  Parameters:
487
- sorted_inputs (list[str]): Input names ordered by dependency.
488
- filters (dict[str, dict]): Filter definitions keyed by input name.
489
- excludes (dict[str, dict]): Exclusion definitions keyed by input name.
586
+ sorted_inputs (list[str]): Input names in dependency-respecting order.
587
+ filters (dict[str, dict]): Per-input filter definitions (may include `filter_funcs` or `filter_kwargs`).
588
+ excludes (dict[str, dict]): Per-input exclusion definitions (may include `filter_funcs` or `filter_kwargs`).
490
589
 
491
590
  Returns:
492
- list[dict[str, Any]]: Valid input combinations.
591
+ list[dict[str, Any]]: Completed input-to-value mappings that meet the filters and excludes.
493
592
  """
494
593
 
495
594
  def helper(
@@ -681,38 +780,39 @@ class CalculationBucket(Bucket[GeneralManagerType]):
681
780
 
682
781
  def get(self, **kwargs: Any) -> GeneralManagerType:
683
782
  """
684
- Retrieve a single manager instance that satisfies the given filters.
783
+ Return the single manager instance that matches the provided field filters.
685
784
 
686
785
  Parameters:
687
- **kwargs (Any): Filter expressions narrowing the calculation results.
786
+ **kwargs (Any): Field filters to apply when selecting a calculation (e.g., property or input names mapped to expected values).
688
787
 
689
788
  Returns:
690
- GeneralManagerType: Matching manager instance.
789
+ The single manager instance that satisfies the provided filters.
691
790
 
692
791
  Raises:
693
- ValueError: If zero or multiple calculations match the filters.
792
+ MissingCalculationMatchError: If no matching manager exists.
793
+ MultipleCalculationMatchError: If more than one matching manager exists.
694
794
  """
695
795
  filtered_bucket = self.filter(**kwargs)
696
796
  items = list(filtered_bucket)
697
797
  if len(items) == 1:
698
798
  return items[0]
699
799
  elif len(items) == 0:
700
- raise ValueError("No matching calculation found.")
800
+ raise MissingCalculationMatchError()
701
801
  else:
702
- raise ValueError("Multiple matching calculations found.")
802
+ raise MultipleCalculationMatchError()
703
803
 
704
804
  def sort(
705
805
  self, key: str | tuple[str], reverse: bool = False
706
806
  ) -> CalculationBucket[GeneralManagerType]:
707
807
  """
708
- Return a new bucket with updated sorting preferences.
808
+ Create a new CalculationBucket configured to order generated combinations by the given attribute key.
709
809
 
710
810
  Parameters:
711
- key (str | tuple[str, ...]): Attribute name(s) used for ordering combinations.
712
- reverse (bool): Whether to apply descending order.
811
+ key: Attribute name or tuple of attribute names to use for ordering generated manager combinations.
812
+ reverse: If True, sort in descending order.
713
813
 
714
814
  Returns:
715
- CalculationBucket[GeneralManagerType]: Bucket configured with the provided sorting options.
815
+ A new CalculationBucket configured to sort combinations by the provided key and direction.
716
816
  """
717
817
  return CalculationBucket(
718
818
  self._manager_class,
@@ -731,6 +831,8 @@ class CalculationBucket(Bucket[GeneralManagerType]):
731
831
  """
732
832
  own = self.all()
733
833
  own._data = []
734
- own.filters = {}
735
- own.excludes = {}
834
+ own.filter_definitions = {}
835
+ own.exclude_definitions = {}
836
+ own._filters = {}
837
+ own._excludes = {}
736
838
  return own