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.
- 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.0.dist-info → generalmanager-0.15.0.dist-info}/METADATA +38 -4
- generalmanager-0.15.0.dist-info/RECORD +62 -0
- generalmanager-0.15.0.dist-info/licenses/LICENSE +21 -0
- generalmanager-0.14.0.dist-info/RECORD +0 -58
- generalmanager-0.14.0.dist-info/licenses/LICENSE +0 -29
- {generalmanager-0.14.0.dist-info → generalmanager-0.15.0.dist-info}/WHEEL +0 -0
- {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
|
23
|
-
|
24
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
156
|
-
|
157
|
-
|
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]:
|
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(
|
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:
|
82
|
+
**kwargs: Any,
|
72
83
|
) -> dict[str, Any]:
|
73
84
|
"""
|
74
|
-
Parse
|
85
|
+
Parse raw arguments into a validated identification mapping.
|
75
86
|
|
76
|
-
|
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]:
|
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 =
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|
-
|
286
|
+
Return the declared Python type for an input field.
|
243
287
|
|
244
|
-
|
245
|
-
field_name:
|
288
|
+
Parameters:
|
289
|
+
field_name (str): Name of the input field.
|
246
290
|
|
247
291
|
Returns:
|
248
|
-
|
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
|
78
|
+
Prepare interface attributes prior to GeneralManager class creation.
|
72
79
|
|
73
|
-
|
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
|
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
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
|