GeneralManager 0.14.0__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 (63) 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.0.dist-info → generalmanager-0.15.0.dist-info}/METADATA +38 -4
  58. generalmanager-0.15.0.dist-info/RECORD +62 -0
  59. generalmanager-0.15.0.dist-info/licenses/LICENSE +21 -0
  60. generalmanager-0.14.0.dist-info/RECORD +0 -58
  61. generalmanager-0.14.0.dist-info/licenses/LICENSE +0 -29
  62. {generalmanager-0.14.0.dist-info → generalmanager-0.15.0.dist-info}/WHEEL +0 -0
  63. {generalmanager-0.14.0.dist-info → generalmanager-0.15.0.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,10 @@
1
+ """Helpers for generating realistic factory values for Django models."""
2
+
1
3
  from __future__ import annotations
2
4
  from typing import Any, cast, TYPE_CHECKING
3
5
  from factory.declarations import LazyFunction
4
6
  from factory.faker import Faker
5
- import exrex
7
+ import exrex # type: ignore[import-untyped]
6
8
  from django.db import models
7
9
  from django.core.validators import RegexValidator
8
10
  import random
@@ -19,9 +21,16 @@ def getFieldValue(
19
21
  field: models.Field[Any, Any] | models.ForeignObjectRel,
20
22
  ) -> object:
21
23
  """
22
- Generate a suitable fake or factory value for a given Django model field, for use in tests or data factories.
23
-
24
- Returns a value appropriate for the field type, including support for measurement, text, numeric, date/time, boolean, email, URL, IP address, UUID, duration, and character fields (with regex support). For relational fields (OneToOneField and ForeignKey), attempts to use a related model factory or select a random existing instance; raises ValueError if neither is available. Returns None for unsupported field types or with a 10% chance if the field allows null values.
24
+ Generate a realistic value for a Django model field.
25
+
26
+ Parameters:
27
+ field (models.Field | models.ForeignObjectRel): Field definition to generate a value for.
28
+
29
+ Returns:
30
+ object: Value appropriate for the field type.
31
+
32
+ Raises:
33
+ ValueError: If a related model lacks both a factory and existing instances.
25
34
  """
26
35
  if field.null:
27
36
  if random.choice([True] + 9 * [False]):
@@ -29,7 +38,7 @@ def getFieldValue(
29
38
 
30
39
  if isinstance(field, MeasurementField):
31
40
 
32
- def _measurement():
41
+ def _measurement() -> Measurement:
33
42
  value = Decimal(random.randrange(0, 10_000_000)) / Decimal("100") # two dp
34
43
  return Measurement(value, field.base_unit)
35
44
 
@@ -135,29 +144,45 @@ def getRelatedModel(
135
144
  field: models.ForeignObjectRel | models.Field[Any, Any],
136
145
  ) -> type[models.Model]:
137
146
  """
138
- Return the related model class for a given Django relational field.
139
-
147
+ Return the related model class for the given relational field.
148
+
149
+ Parameters:
150
+ field (models.Field | models.ForeignObjectRel): Relational field to inspect.
151
+
152
+ Returns:
153
+ type[models.Model]: Related model class.
154
+
140
155
  Raises:
141
- ValueError: If the field does not have a related model defined.
156
+ ValueError: If the field does not declare a related model.
142
157
  """
143
158
  related_model = field.related_model
144
159
  if related_model is None:
145
160
  raise ValueError(f"Field {field.name} does not have a related model defined.")
146
161
  if related_model == "self":
147
162
  related_model = field.model
148
- return related_model # For unsupported field types
163
+ if not isinstance(related_model, type) or not issubclass(
164
+ related_model, models.Model
165
+ ):
166
+ raise TypeError(
167
+ f"Related model for {field.name} must be a Django model class, got {related_model!r}"
168
+ )
169
+ return cast(type[models.Model], related_model)
149
170
 
150
171
 
151
172
  def getManyToManyFieldValue(
152
173
  field: models.ManyToManyField,
153
174
  ) -> list[models.Model]:
154
175
  """
155
- Generate a list of model instances to associate with a ManyToMany field for testing or factory purposes.
156
-
157
- If a related model factory is available, creates new instances as needed. Otherwise, selects from existing instances. Raises a ValueError if neither a factory nor existing instances are available.
158
-
176
+ Produce sample related instances for a ManyToMany field.
177
+
178
+ Parameters:
179
+ field (models.ManyToManyField): Field definition whose values should be generated.
180
+
159
181
  Returns:
160
- list[models.Model]: A list of related model instances for the ManyToMany field.
182
+ list[models.Model]: List of related instances to assign to the field.
183
+
184
+ Raises:
185
+ ValueError: If neither factories nor existing instances are available.
161
186
  """
162
187
  related_factory = None
163
188
  related_model = getRelatedModel(field)
@@ -1,3 +1,5 @@
1
+ """Convenience helpers for defining factory_boy lazy attributes."""
2
+
1
3
  from typing import Any, Optional
2
4
  from factory.declarations import LazyFunction, LazyAttribute, LazyAttributeSequence
3
5
  import random
@@ -13,12 +15,26 @@ fake = Faker()
13
15
  def LazyMeasurement(
14
16
  min_value: int | float, max_value: int | float, unit: str
15
17
  ) -> LazyFunction:
18
+ """
19
+ Return a lazy factory producing ``Measurement`` values in the given range.
20
+
21
+ Parameters:
22
+ min_value (int | float): Minimum magnitude.
23
+ max_value (int | float): Maximum magnitude.
24
+ unit (str): Measurement unit.
25
+ """
16
26
  return LazyFunction(
17
- lambda: Measurement(str(random.uniform(min_value, max_value))[:10], unit)
27
+ lambda: Measurement(f"{random.uniform(min_value, max_value):.6f}", unit)
18
28
  )
19
29
 
20
30
 
21
31
  def LazyDeltaDate(avg_delta_days: int, base_attribute: str) -> LazyAttribute:
32
+ """Return a lazy attribute that offsets a base date by a random delta.
33
+
34
+ Parameters:
35
+ avg_delta_days (int): Average number of days to offset.
36
+ base_attribute (str): Name of the attribute providing the base date.
37
+ """
22
38
  return LazyAttribute(
23
39
  lambda obj: (getattr(obj, base_attribute) or date.today())
24
40
  + timedelta(days=random.randint(avg_delta_days // 2, avg_delta_days * 3 // 2))
@@ -26,6 +42,7 @@ def LazyDeltaDate(avg_delta_days: int, base_attribute: str) -> LazyAttribute:
26
42
 
27
43
 
28
44
  def LazyProjectName() -> LazyFunction:
45
+ """Return a lazy factory producing a pseudo-random project-style name."""
29
46
  return LazyFunction(
30
47
  lambda: (
31
48
  f"{fake.word().capitalize()} "
@@ -37,28 +54,39 @@ def LazyProjectName() -> LazyFunction:
37
54
 
38
55
 
39
56
  def LazyDateToday() -> LazyFunction:
57
+ """Return a lazy factory that yields today's date."""
40
58
  return LazyFunction(lambda: date.today())
41
59
 
42
60
 
43
61
  def LazyDateBetween(start_date: date, end_date: date) -> LazyAttribute:
62
+ """Return a lazy attribute producing dates within the supplied range."""
44
63
  delta = (end_date - start_date).days
64
+ if delta < 0:
65
+ start_date, end_date = end_date, start_date
66
+ delta = -delta
45
67
  return LazyAttribute(
46
68
  lambda obj: start_date + timedelta(days=random.randint(0, delta))
47
69
  )
48
70
 
49
71
 
50
72
  def LazyDateTimeBetween(start: datetime, end: datetime) -> LazyAttribute:
73
+ """Return a lazy attribute producing datetimes within the supplied range."""
51
74
  span = (end - start).total_seconds()
75
+ if span < 0:
76
+ start, end = end, start
77
+ span = -span
52
78
  return LazyAttribute(
53
79
  lambda obj: start + timedelta(seconds=random.randint(0, int(span)))
54
80
  )
55
81
 
56
82
 
57
83
  def LazyInteger(min_value: int, max_value: int) -> LazyFunction:
84
+ """Return a lazy factory yielding random integers within the bounds."""
58
85
  return LazyFunction(lambda: random.randint(min_value, max_value))
59
86
 
60
87
 
61
88
  def LazyDecimal(min_value: float, max_value: float, precision: int = 2) -> LazyFunction:
89
+ """Return a lazy factory yielding Decimal values within the bounds."""
62
90
  fmt = f"{{:.{precision}f}}"
63
91
  return LazyFunction(
64
92
  lambda: Decimal(fmt.format(random.uniform(min_value, max_value)))
@@ -66,28 +94,34 @@ def LazyDecimal(min_value: float, max_value: float, precision: int = 2) -> LazyF
66
94
 
67
95
 
68
96
  def LazyChoice(options: list[Any]) -> LazyFunction:
97
+ """Return a lazy factory selecting a random element from the options."""
69
98
  return LazyFunction(lambda: random.choice(options))
70
99
 
71
100
 
72
101
  def LazySequence(start: int = 0, step: int = 1) -> LazyAttributeSequence:
102
+ """Return a lazy attribute sequence starting at ``start`` with ``step`` increments."""
73
103
  return LazyAttributeSequence(lambda obj, n: start + n * step)
74
104
 
75
105
 
76
106
  def LazyBoolean(trues_ratio: float = 0.5) -> LazyFunction:
107
+ """Return a lazy factory yielding booleans with the given true ratio."""
77
108
  return LazyFunction(lambda: random.random() < trues_ratio)
78
109
 
79
110
 
80
111
  def LazyUUID() -> LazyFunction:
112
+ """Return a lazy factory producing UUID4 strings."""
81
113
  return LazyFunction(lambda: str(uuid.uuid4()))
82
114
 
83
115
 
84
116
  def LazyFakerName() -> LazyFunction:
117
+ """Return a lazy factory producing names using Faker."""
85
118
  return LazyFunction(lambda: fake.name())
86
119
 
87
120
 
88
121
  def LazyFakerEmail(
89
122
  name: Optional[str] = None, domain: Optional[str] = None
90
123
  ) -> LazyFunction:
124
+ """Return a lazy factory producing email addresses with optional overrides."""
91
125
  if not name and not domain:
92
126
  return LazyFunction(lambda: fake.email(domain=domain))
93
127
  if not name:
@@ -98,12 +132,15 @@ def LazyFakerEmail(
98
132
 
99
133
 
100
134
  def LazyFakerSentence(number_of_words: int = 6) -> LazyFunction:
135
+ """Return a lazy factory producing fake sentences."""
101
136
  return LazyFunction(lambda: fake.sentence(nb_words=number_of_words))
102
137
 
103
138
 
104
139
  def LazyFakerAddress() -> LazyFunction:
140
+ """Return a lazy factory producing fake postal addresses."""
105
141
  return LazyFunction(lambda: fake.address())
106
142
 
107
143
 
108
144
  def LazyFakerUrl() -> LazyFunction:
145
+ """Return a lazy factory producing fake URLs."""
109
146
  return LazyFunction(lambda: fake.url())
@@ -0,0 +1,36 @@
1
+ """Public interface classes for GeneralManager implementations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from general_manager.utils.public_api import build_module_dir, resolve_export
8
+
9
+ __all__ = [
10
+ "InterfaceBase",
11
+ "DBBasedInterface",
12
+ "DatabaseInterface",
13
+ "ReadOnlyInterface",
14
+ "CalculationInterface",
15
+ ]
16
+
17
+ _MODULE_MAP = {
18
+ "InterfaceBase": "general_manager.interface.baseInterface",
19
+ "DBBasedInterface": "general_manager.interface.databaseBasedInterface",
20
+ "DatabaseInterface": "general_manager.interface.databaseInterface",
21
+ "ReadOnlyInterface": "general_manager.interface.readOnlyInterface",
22
+ "CalculationInterface": "general_manager.interface.calculationInterface",
23
+ }
24
+
25
+
26
+ def __getattr__(name: str) -> Any:
27
+ return resolve_export(
28
+ name,
29
+ module_all=__all__,
30
+ module_map=_MODULE_MAP,
31
+ module_globals=globals(),
32
+ )
33
+
34
+
35
+ def __dir__() -> list[str]:
36
+ return build_module_dir(module_all=__all__, module_globals=globals())
@@ -1,3 +1,5 @@
1
+ """Abstract interface layer shared by all GeneralManager implementations."""
2
+
1
3
  from __future__ import annotations
2
4
  from abc import ABC, abstractmethod
3
5
  from typing import (
@@ -9,6 +11,7 @@ from typing import (
9
11
  ClassVar,
10
12
  Callable,
11
13
  TypedDict,
14
+ cast,
12
15
  )
13
16
  from datetime import datetime
14
17
  from django.conf import settings
@@ -43,11 +46,7 @@ type classPostCreationMethod = Callable[
43
46
 
44
47
 
45
48
  class AttributeTypedDict(TypedDict):
46
- """
47
- This class is used to define the type of the attributes dictionary.
48
- It is used to define the type of the attributes dictionary in the
49
- GeneralManager class.
50
- """
49
+ """Describe metadata captured for each interface attribute."""
51
50
 
52
51
  type: type
53
52
  default: Any
@@ -57,29 +56,49 @@ class AttributeTypedDict(TypedDict):
57
56
 
58
57
 
59
58
  class InterfaceBase(ABC):
59
+ """Common base API for interfaces backing GeneralManager classes."""
60
+
60
61
  _parent_class: Type[GeneralManager]
61
62
  _interface_type: ClassVar[str]
62
63
  input_fields: dict[str, Input]
63
64
 
64
- def __init__(self, *args: Any, **kwargs: Any):
65
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
66
+ """
67
+ Construct the interface using the supplied identification arguments.
68
+
69
+ Parameters:
70
+ *args: Positional arguments passed to the interface constructor.
71
+ **kwargs: Keyword arguments passed to the interface constructor.
72
+
73
+ Returns:
74
+ None
75
+ """
65
76
  identification = self.parseInputFieldsToIdentification(*args, **kwargs)
66
77
  self.identification = self.formatIdentification(identification)
67
78
 
68
79
  def parseInputFieldsToIdentification(
69
80
  self,
70
81
  *args: Any,
71
- **kwargs: dict[str, Any],
82
+ **kwargs: Any,
72
83
  ) -> dict[str, Any]:
73
84
  """
74
- Parse and validate input arguments into a dictionary of input field values.
85
+ Parse raw arguments into a validated identification mapping.
75
86
 
76
- Positional and keyword arguments are mapped to input field names, with normalization of argument names (e.g., removing trailing `_id`). Ensures all required fields are present and no unexpected arguments are provided. Processes input fields in dependency order, casting and validating each value. Raises a `TypeError` for missing or unexpected arguments and a `ValueError` if circular dependencies are detected.
87
+ Parameters:
88
+ *args (Any): Positional arguments matched to the interface's input field order.
89
+ **kwargs (dict[str, Any]): Keyword arguments supplied by the caller.
77
90
 
78
91
  Returns:
79
- dict[str, Any]: A dictionary mapping input field names to their validated and cast values.
92
+ dict[str, Any]: Mapping of input field names to validated values.
93
+
94
+ Raises:
95
+ TypeError: If required inputs are missing, unexpected inputs are provided, or a value fails type checking.
96
+ ValueError: If circular dependencies prevent resolution of the inputs.
80
97
  """
81
- identification = {}
82
- kwargs = args_to_kwargs(args, self.input_fields.keys(), kwargs)
98
+ identification: dict[str, Any] = {}
99
+ kwargs = cast(
100
+ dict[str, Any], args_to_kwargs(args, self.input_fields.keys(), kwargs)
101
+ )
83
102
  # Check for extra arguments
84
103
  extra_args = set(kwargs.keys()) - set(self.input_fields.keys())
85
104
  if extra_args:
@@ -94,7 +113,7 @@ class InterfaceBase(ABC):
94
113
  raise TypeError(f"Missing required arguments: {', '.join(missing_args)}")
95
114
 
96
115
  # process input fields with dependencies
97
- processed = set()
116
+ processed: set[str] = set()
98
117
  while len(processed) < len(self.input_fields):
99
118
  progress_made = False
100
119
  for name, input_field in self.input_fields.items():
@@ -117,6 +136,15 @@ class InterfaceBase(ABC):
117
136
 
118
137
  @staticmethod
119
138
  def formatIdentification(identification: dict[str, Any]) -> dict[str, Any]:
139
+ """
140
+ Normalise identification data by replacing manager instances with their IDs.
141
+
142
+ Parameters:
143
+ identification (dict[str, Any]): Raw identification mapping possibly containing manager instances.
144
+
145
+ Returns:
146
+ dict[str, Any]: Identification mapping with nested managers replaced by their identifications.
147
+ """
120
148
  from general_manager.manager.generalManager import GeneralManager
121
149
 
122
150
  for key, value in identification.items():
@@ -141,9 +169,19 @@ class InterfaceBase(ABC):
141
169
  self, name: str, value: Any, identification: dict[str, Any]
142
170
  ) -> None:
143
171
  """
144
- Validates the type and allowed values of an input field.
172
+ Validate a single input value against its definition.
173
+
174
+ Parameters:
175
+ name (str): Input field name being processed.
176
+ value (Any): Value provided by the caller.
177
+ identification (dict[str, Any]): Partially resolved identification mapping used to evaluate dependencies.
145
178
 
146
- Ensures that the provided value matches the expected type for the specified input field. In debug mode, also checks that the value is among the allowed possible values if defined, supporting both callables and iterables. Raises a TypeError for invalid types or possible value definitions, and a ValueError if the value is not permitted.
179
+ Returns:
180
+ None
181
+
182
+ Raises:
183
+ TypeError: If the value has the wrong type or possible values are misconfigured.
184
+ ValueError: If the value is not permitted by the configured `possible_values`.
147
185
  """
148
186
  input_field = self.input_fields[name]
149
187
  if not isinstance(value, input_field.type):
@@ -173,31 +211,37 @@ class InterfaceBase(ABC):
173
211
 
174
212
  @classmethod
175
213
  def create(cls, *args: Any, **kwargs: Any) -> Any:
214
+ """Create a new record via the underlying data source."""
176
215
  raise NotImplementedError
177
216
 
178
217
  def update(self, *args: Any, **kwargs: Any) -> Any:
218
+ """Update the underlying record."""
179
219
  raise NotImplementedError
180
220
 
181
221
  def deactivate(self, *args: Any, **kwargs: Any) -> Any:
222
+ """Deactivate the underlying record."""
182
223
  raise NotImplementedError
183
224
 
184
225
  @abstractmethod
185
226
  def getData(self, search_date: datetime | None = None) -> Any:
227
+ """Return data materialised for the manager object."""
186
228
  raise NotImplementedError
187
229
 
188
230
  @classmethod
189
231
  @abstractmethod
190
232
  def getAttributeTypes(cls) -> dict[str, AttributeTypedDict]:
233
+ """Return metadata describing each attribute exposed on the manager."""
191
234
  raise NotImplementedError
192
235
 
193
236
  @classmethod
194
237
  @abstractmethod
195
238
  def getAttributes(cls) -> dict[str, Any]:
239
+ """Return attribute values exposed via the interface."""
196
240
  raise NotImplementedError
197
241
 
198
242
  @classmethod
199
243
  def getGraphQLProperties(cls) -> dict[str, GraphQLProperty]:
200
- """Return GraphQL properties defined on the parent manager."""
244
+ """Return GraphQLProperty descriptors defined on the parent manager class."""
201
245
  if not hasattr(cls, "_parent_class"):
202
246
  return {}
203
247
  return {
@@ -209,11 +253,13 @@ class InterfaceBase(ABC):
209
253
  @classmethod
210
254
  @abstractmethod
211
255
  def filter(cls, **kwargs: Any) -> Bucket[Any]:
256
+ """Return a bucket filtered by the provided lookup expressions."""
212
257
  raise NotImplementedError
213
258
 
214
259
  @classmethod
215
260
  @abstractmethod
216
261
  def exclude(cls, **kwargs: Any) -> Bucket[Any]:
262
+ """Return a bucket excluding records that match the provided lookup expressions."""
217
263
  raise NotImplementedError
218
264
 
219
265
  @classmethod
@@ -225,13 +271,11 @@ class InterfaceBase(ABC):
225
271
  classPostCreationMethod,
226
272
  ]:
227
273
  """
228
- This method returns a pre and a post GeneralManager creation method
229
- and is called inside the GeneralManagerMeta class to initialize the
230
- Interface.
231
- The pre creation method is called before the GeneralManager instance
232
- is created to modify the kwargs.
233
- The post creation method is called after the GeneralManager instance
234
- is created to modify the instance and add additional data.
274
+ Return hooks executed around GeneralManager class creation.
275
+
276
+ Returns:
277
+ tuple[classPreCreationMethod, classPostCreationMethod]:
278
+ Callables executed before and after the manager class is created.
235
279
  """
236
280
  raise NotImplementedError
237
281
 
@@ -239,13 +283,13 @@ class InterfaceBase(ABC):
239
283
  @abstractmethod
240
284
  def getFieldType(cls, field_name: str) -> type:
241
285
  """
242
- Returns the type of the specified input field.
286
+ Return the declared Python type for an input field.
243
287
 
244
- Args:
245
- field_name: The name of the input field.
288
+ Parameters:
289
+ field_name (str): Name of the input field.
246
290
 
247
291
  Returns:
248
- The Python type associated with the given field name.
292
+ type: Python type associated with the field.
249
293
 
250
294
  Raises:
251
295
  NotImplementedError: This method must be implemented by subclasses.
@@ -1,3 +1,5 @@
1
+ """Interface implementation for calculation-style GeneralManager classes."""
2
+
1
3
  from __future__ import annotations
2
4
  from datetime import datetime
3
5
  from typing import Any
@@ -18,6 +20,7 @@ from general_manager.bucket.calculationBucket import CalculationBucket
18
20
 
19
21
 
20
22
  class CalculationInterface(InterfaceBase):
23
+ """Interface exposing calculation inputs without persisting data."""
21
24
  _interface_type = "calculation"
22
25
  input_fields: dict[str, Input]
23
26
 
@@ -44,6 +47,7 @@ class CalculationInterface(InterfaceBase):
44
47
 
45
48
  @classmethod
46
49
  def getAttributes(cls) -> dict[str, Any]:
50
+ """Return attribute accessors that cast values using the configured inputs."""
47
51
  return {
48
52
  name: lambda self, name=name: cls.input_fields[name].cast(
49
53
  self.identification.get(name)
@@ -53,14 +57,17 @@ class CalculationInterface(InterfaceBase):
53
57
 
54
58
  @classmethod
55
59
  def filter(cls, **kwargs: Any) -> CalculationBucket:
60
+ """Return a calculation bucket filtered by the given parameters."""
56
61
  return CalculationBucket(cls._parent_class).filter(**kwargs)
57
62
 
58
63
  @classmethod
59
64
  def exclude(cls, **kwargs: Any) -> CalculationBucket:
65
+ """Return a calculation bucket excluding items matching the parameters."""
60
66
  return CalculationBucket(cls._parent_class).exclude(**kwargs)
61
67
 
62
68
  @classmethod
63
69
  def all(cls) -> CalculationBucket:
70
+ """Return a calculation bucket containing all combinations."""
64
71
  return CalculationBucket(cls._parent_class).all()
65
72
 
66
73
  @staticmethod
@@ -68,12 +75,15 @@ class CalculationInterface(InterfaceBase):
68
75
  name: generalManagerClassName, attrs: attributes, interface: interfaceBaseClass
69
76
  ) -> tuple[attributes, interfaceBaseClass, None]:
70
77
  """
71
- Prepare and return updated attributes and a new interface class for GeneralManager creation.
78
+ Prepare interface attributes prior to GeneralManager class creation.
72
79
 
73
- Collects all `Input` instances from the provided interface class, sets the interface type in the attributes, dynamically creates a new interface class with an `input_fields` attribute, and adds this class to the attributes dictionary.
80
+ Parameters:
81
+ name (generalManagerClassName): Name of the new manager class.
82
+ attrs (attributes): Attribute dictionary for the manager being created.
83
+ interface (interfaceBaseClass): Base interface definition.
74
84
 
75
85
  Returns:
76
- tuple: A tuple containing the updated attributes dictionary, the new interface class, and None.
86
+ tuple[attributes, interfaceBaseClass, None]: Updated attributes, interface class, and related model (None).
77
87
  """
78
88
  input_fields: dict[str, Input[Any]] = {}
79
89
  for key, value in vars(interface).items():
@@ -96,18 +106,16 @@ class CalculationInterface(InterfaceBase):
96
106
  interface_class: newlyCreatedInterfaceClass,
97
107
  model: relatedClass,
98
108
  ) -> None:
109
+ """Link the generated interface to the manager class after creation."""
99
110
  interface_class._parent_class = new_class
100
111
 
101
112
  @classmethod
102
113
  def handleInterface(cls) -> tuple[classPreCreationMethod, classPostCreationMethod]:
103
114
  """
104
- This method returns a pre and a post GeneralManager creation method
105
- and is called inside the GeneralManagerMeta class to initialize the
106
- Interface.
107
- The pre creation method is called before the GeneralManager instance
108
- is created to modify the kwargs.
109
- The post creation method is called after the GeneralManager instance
110
- is created to modify the instance and add additional data.
115
+ Return the pre- and post-creation hooks used by ``GeneralManagerMeta``.
116
+
117
+ Returns:
118
+ tuple[classPreCreationMethod, classPostCreationMethod]: Hook functions invoked around manager creation.
111
119
  """
112
120
  return cls._preCreate, cls._postCreate
113
121