GeneralManager 0.17.0__py3-none-any.whl → 0.18.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.

Potentially problematic release.


This version of GeneralManager might be problematic. Click here for more details.

Files changed (67) 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/dependencyIndex.py +143 -45
  25. general_manager/cache/signals.py +9 -2
  26. general_manager/factory/__init__.py +10 -1
  27. general_manager/factory/autoFactory.py +55 -13
  28. general_manager/factory/factories.py +110 -40
  29. general_manager/factory/factoryMethods.py +122 -34
  30. general_manager/interface/__init__.py +10 -1
  31. general_manager/interface/baseInterface.py +129 -36
  32. general_manager/interface/calculationInterface.py +35 -18
  33. general_manager/interface/databaseBasedInterface.py +71 -45
  34. general_manager/interface/databaseInterface.py +96 -38
  35. general_manager/interface/models.py +5 -5
  36. general_manager/interface/readOnlyInterface.py +94 -20
  37. general_manager/manager/__init__.py +10 -1
  38. general_manager/manager/generalManager.py +25 -16
  39. general_manager/manager/groupManager.py +20 -6
  40. general_manager/manager/meta.py +84 -16
  41. general_manager/measurement/__init__.py +10 -1
  42. general_manager/measurement/measurement.py +289 -95
  43. general_manager/measurement/measurementField.py +42 -31
  44. general_manager/permission/__init__.py +10 -1
  45. general_manager/permission/basePermission.py +120 -38
  46. general_manager/permission/managerBasedPermission.py +72 -21
  47. general_manager/permission/mutationPermission.py +14 -9
  48. general_manager/permission/permissionChecks.py +14 -12
  49. general_manager/permission/permissionDataManager.py +24 -11
  50. general_manager/permission/utils.py +34 -6
  51. general_manager/public_api_registry.py +36 -10
  52. general_manager/rule/__init__.py +10 -1
  53. general_manager/rule/handler.py +133 -44
  54. general_manager/rule/rule.py +178 -39
  55. general_manager/utils/__init__.py +10 -1
  56. general_manager/utils/argsToKwargs.py +34 -9
  57. general_manager/utils/filterParser.py +22 -7
  58. general_manager/utils/formatString.py +1 -0
  59. general_manager/utils/pathMapping.py +23 -15
  60. general_manager/utils/public_api.py +33 -2
  61. general_manager/utils/testing.py +31 -33
  62. {generalmanager-0.17.0.dist-info → generalmanager-0.18.0.dist-info}/METADATA +2 -1
  63. generalmanager-0.18.0.dist-info/RECORD +77 -0
  64. {generalmanager-0.17.0.dist-info → generalmanager-0.18.0.dist-info}/licenses/LICENSE +1 -1
  65. generalmanager-0.17.0.dist-info/RECORD +0 -77
  66. {generalmanager-0.17.0.dist-info → generalmanager-0.18.0.dist-info}/WHEEL +0 -0
  67. {generalmanager-0.17.0.dist-info → generalmanager-0.18.0.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,49 @@ if TYPE_CHECKING:
13
13
  GeneralManagerType = TypeVar("GeneralManagerType", bound="GeneralManager")
14
14
 
15
15
 
16
+ class InvalidInterfaceTypeError(TypeError):
17
+ """Raised when a GeneralManager is configured with an incompatible Interface class."""
18
+
19
+ def __init__(self, interface_name: str) -> None:
20
+ """
21
+ Initialize an InvalidInterfaceTypeError indicating a configured interface is not a subclass of InterfaceBase.
22
+
23
+ Parameters:
24
+ interface_name (str): Name of the configured interface class that is invalid; included in the exception message.
25
+ """
26
+ super().__init__(f"{interface_name} must be a subclass of InterfaceBase.")
27
+
28
+
29
+ class MissingAttributeError(AttributeError):
30
+ """Raised when a dynamically generated descriptor cannot locate the attribute."""
31
+
32
+ def __init__(self, attribute_name: str, class_name: str) -> None:
33
+ """
34
+ Initialize the MissingAttributeError with the missing attribute and its owning class.
35
+
36
+ Parameters:
37
+ attribute_name (str): Name of the attribute that was not found.
38
+ class_name (str): Name of the class where the attribute lookup occurred.
39
+
40
+ The exception message is set to "`{attribute_name} not found in {class_name}.`".
41
+ """
42
+ super().__init__(f"{attribute_name} not found in {class_name}.")
43
+
44
+
45
+ class AttributeEvaluationError(AttributeError):
46
+ """Raised when evaluating a callable attribute raises an exception."""
47
+
48
+ def __init__(self, attribute_name: str, error: Exception) -> None:
49
+ """
50
+ Initialize an AttributeEvaluationError that wraps an exception raised while evaluating a descriptor attribute.
51
+
52
+ Parameters:
53
+ attribute_name (str): Name of the attribute whose evaluation failed.
54
+ error (Exception): The original exception that was raised; retained for inspection.
55
+ """
56
+ super().__init__(f"Error calling attribute {attribute_name}: {error}.")
57
+
58
+
16
59
  class _nonExistent:
17
60
  pass
18
61
 
@@ -33,15 +76,18 @@ class GeneralManagerMeta(type):
33
76
  attrs: dict[str, Any],
34
77
  ) -> type:
35
78
  """
36
- Create a new GeneralManager subclass and register its interface hooks.
79
+ Create a GeneralManager subclass, integrate any declared Interface hooks, and register the class for pending initialization and GraphQL processing.
80
+
81
+ If the class body defines an `Interface`, validates it is a subclass of `InterfaceBase`, invokes the interface's `handleInterface()` pre-creation hook to allow modification of the class namespace, creates the class, then invokes the post-creation hook and registers the class for attribute initialization and global tracking. If `Interface` is not defined, creates the class directly. If `settings.AUTOCREATE_GRAPHQL` is true, registers the created class for GraphQL interface processing.
37
82
 
38
83
  Parameters:
84
+ mcs (type): The metaclass creating the class.
39
85
  name (str): Name of the class being created.
40
- bases (tuple[type, ...]): Base classes inherited by the new class.
86
+ bases (tuple[type, ...]): Base classes for the new class.
41
87
  attrs (dict[str, Any]): Class namespace supplied during creation.
42
88
 
43
89
  Returns:
44
- type: Newly created class augmented with interface integration.
90
+ type: The newly created subclass, possibly modified by Interface hooks.
45
91
  """
46
92
 
47
93
  def createNewGeneralManagerClass(
@@ -56,9 +102,7 @@ class GeneralManagerMeta(type):
56
102
  if "Interface" in attrs:
57
103
  interface = attrs.pop("Interface")
58
104
  if not issubclass(interface, InterfaceBase):
59
- raise TypeError(
60
- f"{interface.__name__} must be a subclass of InterfaceBase"
61
- )
105
+ raise InvalidInterfaceTypeError(interface.__name__)
62
106
  preCreation, postCreation = interface.handleInterface()
63
107
  attrs, interface_cls, model = preCreation(name, attrs, interface)
64
108
  new_class = createNewGeneralManagerClass(mcs, name, bases, attrs)
@@ -79,18 +123,31 @@ class GeneralManagerMeta(type):
79
123
  attributes: Iterable[str], new_class: Type[GeneralManager]
80
124
  ) -> None:
81
125
  """
82
- Attach descriptor-based properties for each attribute declared on the interface.
126
+ Attach descriptor properties to new_class for each name in attributes.
127
+
128
+ Each generated descriptor returns the interface field type when accessed on the class and resolves the corresponding value from instance._attributes when accessed on an instance. If the stored value is callable it is invoked with instance._interface; a missing attribute raises MissingAttributeError and an exception raised while invoking a callable is wrapped in AttributeEvaluationError.
83
129
 
84
130
  Parameters:
85
- attributes (Iterable[str]): Names of attributes for which descriptors are created.
86
- new_class (Type[GeneralManager]): Class receiving the generated descriptors.
131
+ attributes (Iterable[str]): Names of attributes for which descriptors will be created.
132
+ new_class (Type[GeneralManager]): Class that will receive the generated descriptor attributes.
87
133
  """
88
134
 
89
135
  def descriptorMethod(
90
136
  attr_name: str,
91
137
  new_class: type,
92
138
  ) -> object:
93
- """Create a descriptor that resolves attribute values from the interface at runtime."""
139
+ """
140
+ Create a descriptor that provides attribute access backed by an instance's interface attributes.
141
+
142
+ When accessed on the class, the descriptor returns the field type by delegating to the class's `Interface.getFieldType` for the configured attribute name. When accessed on an instance, it returns the value stored in `instance._attributes[attr_name]`. If the stored value is callable, it is invoked with `instance._interface` and the resulting value is returned. If the attribute is not present on the instance, a `MissingAttributeError` is raised. If invoking a callable attribute raises an exception, that error is wrapped in `AttributeEvaluationError`.
143
+
144
+ Parameters:
145
+ attr_name (str): The name of the attribute the descriptor resolves.
146
+ new_class (type): The class that will receive the descriptor; used to access its `Interface`.
147
+
148
+ Returns:
149
+ descriptor (object): A descriptor object suitable for assigning as a class attribute.
150
+ """
94
151
 
95
152
  class Descriptor:
96
153
  def __init__(
@@ -104,21 +161,32 @@ class GeneralManagerMeta(type):
104
161
  instance: Any | None,
105
162
  owner: type | None = None,
106
163
  ) -> Any:
107
- """Return the field type on the class or the stored value on an instance."""
164
+ """
165
+ Provide the class field type when accessed on the class, or resolve and return the stored attribute value for an instance.
166
+
167
+ When accessed on a class, returns the field type from the class's Interface via Interface.getFieldType.
168
+ When accessed on an instance, retrieves the value stored in instance._attributes for this descriptor's attribute name;
169
+ if the stored value is callable, it is invoked with instance._interface and the result is returned.
170
+
171
+ Returns:
172
+ The field type (when accessed on the class) or the resolved attribute value from the instance.
173
+
174
+ Raises:
175
+ MissingAttributeError: If the attribute is not present in instance._attributes.
176
+ AttributeEvaluationError: If calling a callable attribute raises an exception; the original exception is wrapped.
177
+ """
108
178
  if instance is None:
109
179
  return self._class.Interface.getFieldType(self._attr_name)
110
180
  attribute = instance._attributes.get(self._attr_name, _nonExistent)
111
181
  if attribute is _nonExistent:
112
- raise AttributeError(
113
- f"{self._attr_name} not found in {instance.__class__.__name__}"
182
+ raise MissingAttributeError(
183
+ self._attr_name, instance.__class__.__name__
114
184
  )
115
185
  if callable(attribute):
116
186
  try:
117
187
  attribute = attribute(instance._interface)
118
188
  except Exception as e:
119
- raise AttributeError(
120
- f"Error calling attribute {self._attr_name}: {e}"
121
- ) from e
189
+ raise AttributeEvaluationError(self._attr_name, e) from e
122
190
  return attribute
123
191
 
124
192
  return Descriptor(attr_name, cast(Type[Any], new_class))
@@ -12,10 +12,19 @@ __all__ = list(MEASUREMENT_EXPORTS)
12
12
  _MODULE_MAP = MEASUREMENT_EXPORTS
13
13
 
14
14
  if TYPE_CHECKING:
15
- from general_manager._types.measurement import * # noqa: F401,F403
15
+ from general_manager._types.measurement import * # noqa: F403
16
16
 
17
17
 
18
18
  def __getattr__(name: str) -> Any:
19
+ """
20
+ Dynamically resolve and return a public API attribute by name.
21
+
22
+ Parameters:
23
+ name (str): The attribute name requested from the module's public API.
24
+
25
+ Returns:
26
+ Any: The object or submodule bound to `name` as defined by the module export mapping.
27
+ """
19
28
  return resolve_export(
20
29
  name,
21
30
  module_all=__all__,