GeneralManager 0.19.0__py3-none-any.whl → 0.19.2__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.
- general_manager/_types/api.py +4 -4
- general_manager/_types/bucket.py +4 -4
- general_manager/_types/cache.py +6 -6
- general_manager/_types/factory.py +35 -35
- general_manager/_types/general_manager.py +9 -9
- general_manager/_types/interface.py +5 -5
- general_manager/_types/manager.py +4 -4
- general_manager/_types/measurement.py +1 -1
- general_manager/_types/permission.py +3 -3
- general_manager/_types/utils.py +12 -12
- general_manager/api/graphql.py +118 -95
- general_manager/api/mutation.py +9 -9
- general_manager/api/property.py +4 -4
- general_manager/apps.py +31 -31
- general_manager/bucket/{baseBucket.py → base_bucket.py} +5 -5
- general_manager/bucket/{calculationBucket.py → calculation_bucket.py} +10 -10
- general_manager/bucket/{databaseBucket.py → database_bucket.py} +16 -19
- general_manager/bucket/{groupBucket.py → group_bucket.py} +8 -8
- general_manager/cache/{cacheDecorator.py → cache_decorator.py} +5 -5
- general_manager/cache/{cacheTracker.py → cache_tracker.py} +1 -1
- general_manager/cache/{dependencyIndex.py → dependency_index.py} +1 -1
- general_manager/cache/{modelDependencyCollector.py → model_dependency_collector.py} +4 -4
- general_manager/cache/signals.py +1 -1
- general_manager/factory/{autoFactory.py → auto_factory.py} +24 -19
- general_manager/factory/factories.py +10 -13
- general_manager/factory/{factoryMethods.py → factory_methods.py} +19 -17
- general_manager/interface/{baseInterface.py → base_interface.py} +29 -21
- general_manager/interface/{calculationInterface.py → calculation_interface.py} +10 -10
- general_manager/interface/{databaseBasedInterface.py → database_based_interface.py} +42 -42
- general_manager/interface/{databaseInterface.py → database_interface.py} +15 -15
- general_manager/interface/models.py +3 -3
- general_manager/interface/{readOnlyInterface.py → read_only_interface.py} +12 -12
- general_manager/manager/{generalManager.py → general_manager.py} +11 -11
- general_manager/manager/{groupManager.py → group_manager.py} +6 -6
- general_manager/manager/input.py +1 -1
- general_manager/manager/meta.py +15 -15
- general_manager/measurement/measurement.py +3 -3
- general_manager/permission/{basePermission.py → base_permission.py} +21 -21
- general_manager/permission/{managerBasedPermission.py → manager_based_permission.py} +21 -21
- general_manager/permission/{mutationPermission.py → mutation_permission.py} +12 -12
- general_manager/permission/{permissionChecks.py → permission_checks.py} +2 -2
- general_manager/permission/{permissionDataManager.py → permission_data_manager.py} +6 -6
- general_manager/permission/utils.py +6 -6
- general_manager/public_api_registry.py +75 -66
- general_manager/rule/handler.py +2 -2
- general_manager/rule/rule.py +17 -11
- general_manager/utils/{filterParser.py → filter_parser.py} +3 -3
- general_manager/utils/{jsonEncoder.py → json_encoder.py} +1 -1
- general_manager/utils/{makeCacheKey.py → make_cache_key.py} +1 -1
- general_manager/utils/{noneToZero.py → none_to_zero.py} +1 -1
- general_manager/utils/{pathMapping.py → path_mapping.py} +14 -14
- general_manager/utils/testing.py +14 -14
- {generalmanager-0.19.0.dist-info → generalmanager-0.19.2.dist-info}/METADATA +1 -1
- generalmanager-0.19.2.dist-info/RECORD +77 -0
- generalmanager-0.19.0.dist-info/RECORD +0 -77
- /general_manager/measurement/{measurementField.py → measurement_field.py} +0 -0
- /general_manager/permission/{fileBasedPermission.py → file_based_permission.py} +0 -0
- /general_manager/utils/{argsToKwargs.py → args_to_kwargs.py} +0 -0
- /general_manager/utils/{formatString.py → format_string.py} +0 -0
- {generalmanager-0.19.0.dist-info → generalmanager-0.19.2.dist-info}/WHEEL +0 -0
- {generalmanager-0.19.0.dist-info → generalmanager-0.19.2.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.19.0.dist-info → generalmanager-0.19.2.dist-info}/top_level.txt +0 -0
|
@@ -4,11 +4,14 @@ from __future__ import annotations
|
|
|
4
4
|
from typing import TYPE_CHECKING, Type, Callable, Union, Any, TypeVar, Literal
|
|
5
5
|
from django.db import models
|
|
6
6
|
from factory.django import DjangoModelFactory
|
|
7
|
-
from general_manager.factory.factories import
|
|
7
|
+
from general_manager.factory.factories import (
|
|
8
|
+
get_field_value,
|
|
9
|
+
get_many_to_many_field_value,
|
|
10
|
+
)
|
|
8
11
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
9
12
|
|
|
10
13
|
if TYPE_CHECKING:
|
|
11
|
-
from general_manager.interface.
|
|
14
|
+
from general_manager.interface.database_interface import (
|
|
12
15
|
DBBasedInterface,
|
|
13
16
|
)
|
|
14
17
|
|
|
@@ -82,7 +85,7 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
|
|
|
82
85
|
is_model = False
|
|
83
86
|
if not is_model:
|
|
84
87
|
raise InvalidAutoFactoryModelError
|
|
85
|
-
field_name_list, to_ignore_list = cls.interface.
|
|
88
|
+
field_name_list, to_ignore_list = cls.interface.handle_custom_fields(model)
|
|
86
89
|
|
|
87
90
|
fields = [
|
|
88
91
|
field
|
|
@@ -107,20 +110,20 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
|
|
|
107
110
|
continue # Skip fields that are already set
|
|
108
111
|
if isinstance(field, models.AutoField) or field.auto_created:
|
|
109
112
|
continue # Skip auto fields
|
|
110
|
-
params[field.name] =
|
|
113
|
+
params[field.name] = get_field_value(field)
|
|
111
114
|
|
|
112
115
|
obj: list[models.Model] | models.Model = super()._generate(strategy, params)
|
|
113
116
|
if isinstance(obj, list):
|
|
114
117
|
for item in obj:
|
|
115
118
|
if not isinstance(item, models.Model):
|
|
116
119
|
raise InvalidGeneratedObjectError()
|
|
117
|
-
cls.
|
|
120
|
+
cls._handle_many_to_many_fields_after_creation(item, params)
|
|
118
121
|
else:
|
|
119
|
-
cls.
|
|
122
|
+
cls._handle_many_to_many_fields_after_creation(obj, params)
|
|
120
123
|
return obj
|
|
121
124
|
|
|
122
125
|
@classmethod
|
|
123
|
-
def
|
|
126
|
+
def _handle_many_to_many_fields_after_creation(
|
|
124
127
|
cls, obj: models.Model, attrs: dict[str, Any]
|
|
125
128
|
) -> None:
|
|
126
129
|
"""
|
|
@@ -134,7 +137,7 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
|
|
|
134
137
|
if field.name in attrs:
|
|
135
138
|
m2m_values = attrs[field.name]
|
|
136
139
|
else:
|
|
137
|
-
m2m_values =
|
|
140
|
+
m2m_values = get_many_to_many_field_value(field)
|
|
138
141
|
if m2m_values:
|
|
139
142
|
getattr(obj, field.name).set(m2m_values)
|
|
140
143
|
|
|
@@ -172,8 +175,10 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
|
|
|
172
175
|
"""
|
|
173
176
|
kwargs = cls._adjust_kwargs(**kwargs)
|
|
174
177
|
if cls._adjustmentMethod is not None:
|
|
175
|
-
return cls.
|
|
176
|
-
|
|
178
|
+
return cls.__create_with_generate_func(
|
|
179
|
+
use_creation_method=True, params=kwargs
|
|
180
|
+
)
|
|
181
|
+
return cls._model_creation(model_class, **kwargs)
|
|
177
182
|
|
|
178
183
|
@classmethod
|
|
179
184
|
def _build(
|
|
@@ -192,13 +197,13 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
|
|
|
192
197
|
"""
|
|
193
198
|
kwargs = cls._adjust_kwargs(**kwargs)
|
|
194
199
|
if cls._adjustmentMethod is not None:
|
|
195
|
-
return cls.
|
|
200
|
+
return cls.__create_with_generate_func(
|
|
196
201
|
use_creation_method=False, params=kwargs
|
|
197
202
|
)
|
|
198
|
-
return cls.
|
|
203
|
+
return cls._model_building(model_class, **kwargs)
|
|
199
204
|
|
|
200
205
|
@classmethod
|
|
201
|
-
def
|
|
206
|
+
def _model_creation(
|
|
202
207
|
cls, model_class: Type[models.Model], **kwargs: Any
|
|
203
208
|
) -> models.Model:
|
|
204
209
|
"""
|
|
@@ -219,7 +224,7 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
|
|
|
219
224
|
return obj
|
|
220
225
|
|
|
221
226
|
@classmethod
|
|
222
|
-
def
|
|
227
|
+
def _model_building(
|
|
223
228
|
cls, model_class: Type[models.Model], **kwargs: Any
|
|
224
229
|
) -> models.Model:
|
|
225
230
|
"""Construct an unsaved model instance with the provided field values."""
|
|
@@ -229,7 +234,7 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
|
|
|
229
234
|
return obj
|
|
230
235
|
|
|
231
236
|
@classmethod
|
|
232
|
-
def
|
|
237
|
+
def __create_with_generate_func(
|
|
233
238
|
cls, use_creation_method: bool, params: dict[str, Any]
|
|
234
239
|
) -> models.Model | list[models.Model]:
|
|
235
240
|
"""
|
|
@@ -251,13 +256,13 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
|
|
|
251
256
|
records = cls._adjustmentMethod(**params)
|
|
252
257
|
if isinstance(records, dict):
|
|
253
258
|
if use_creation_method:
|
|
254
|
-
return cls.
|
|
255
|
-
return cls.
|
|
259
|
+
return cls._model_creation(model_cls, **records)
|
|
260
|
+
return cls._model_building(model_cls, **records)
|
|
256
261
|
|
|
257
262
|
created_objects: list[models.Model] = []
|
|
258
263
|
for record in records:
|
|
259
264
|
if use_creation_method:
|
|
260
|
-
created_objects.append(cls.
|
|
265
|
+
created_objects.append(cls._model_creation(model_cls, **record))
|
|
261
266
|
else:
|
|
262
|
-
created_objects.append(cls.
|
|
267
|
+
created_objects.append(cls._model_building(model_cls, **record))
|
|
263
268
|
return created_objects
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
"""Helpers for generating realistic factory values for Django models."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Any, cast
|
|
5
5
|
|
|
6
6
|
from factory.declarations import LazyFunction
|
|
7
7
|
from factory.faker import Faker
|
|
8
8
|
import exrex # type: ignore[import-untyped]
|
|
9
9
|
from django.core.validators import RegexValidator
|
|
10
10
|
from django.db import models
|
|
11
|
-
from datetime import date, datetime, time, timezone
|
|
11
|
+
from datetime import date, datetime, time, timezone, timedelta
|
|
12
12
|
from decimal import Decimal
|
|
13
13
|
from random import SystemRandom
|
|
14
14
|
from general_manager.measurement.measurement import Measurement
|
|
15
|
-
from general_manager.measurement.
|
|
16
|
-
|
|
17
|
-
if TYPE_CHECKING:
|
|
18
|
-
from general_manager.factory.autoFactory import AutoFactory
|
|
15
|
+
from general_manager.measurement.measurement_field import MeasurementField
|
|
19
16
|
|
|
20
17
|
|
|
21
18
|
_RNG = SystemRandom()
|
|
@@ -65,7 +62,7 @@ class InvalidRelatedModelTypeError(TypeError):
|
|
|
65
62
|
)
|
|
66
63
|
|
|
67
64
|
|
|
68
|
-
def
|
|
65
|
+
def get_field_value(
|
|
69
66
|
field: models.Field[Any, Any] | models.ForeignObjectRel,
|
|
70
67
|
) -> Any:
|
|
71
68
|
"""
|
|
@@ -158,7 +155,7 @@ def getFieldValue(
|
|
|
158
155
|
elif isinstance(field, models.UUIDField):
|
|
159
156
|
return cast(str, Faker("uuid4"))
|
|
160
157
|
elif isinstance(field, models.DurationField):
|
|
161
|
-
return cast(
|
|
158
|
+
return cast(timedelta, Faker("time_delta"))
|
|
162
159
|
elif isinstance(field, models.CharField):
|
|
163
160
|
max_length = field.max_length or 100
|
|
164
161
|
# Check for RegexValidator
|
|
@@ -173,7 +170,7 @@ def getFieldValue(
|
|
|
173
170
|
else:
|
|
174
171
|
return cast(str, Faker("text", max_nb_chars=max_length))
|
|
175
172
|
elif isinstance(field, models.OneToOneField):
|
|
176
|
-
related_model =
|
|
173
|
+
related_model = get_related_model(field)
|
|
177
174
|
if hasattr(related_model, "_general_manager_class"):
|
|
178
175
|
related_factory = related_model._general_manager_class.Factory # type: ignore
|
|
179
176
|
return related_factory()
|
|
@@ -185,7 +182,7 @@ def getFieldValue(
|
|
|
185
182
|
else:
|
|
186
183
|
raise MissingFactoryOrInstancesError(related_model)
|
|
187
184
|
elif isinstance(field, models.ForeignKey):
|
|
188
|
-
related_model =
|
|
185
|
+
related_model = get_related_model(field)
|
|
189
186
|
# Create or get an instance of the related model
|
|
190
187
|
if hasattr(related_model, "_general_manager_class"):
|
|
191
188
|
create_a_new_instance = _RNG.choice([True, True, False])
|
|
@@ -209,7 +206,7 @@ def getFieldValue(
|
|
|
209
206
|
return None
|
|
210
207
|
|
|
211
208
|
|
|
212
|
-
def
|
|
209
|
+
def get_related_model(
|
|
213
210
|
field: models.ForeignObjectRel | models.Field[Any, Any],
|
|
214
211
|
) -> type[models.Model]:
|
|
215
212
|
"""
|
|
@@ -239,7 +236,7 @@ def getRelatedModel(
|
|
|
239
236
|
return cast(type[models.Model], related_model)
|
|
240
237
|
|
|
241
238
|
|
|
242
|
-
def
|
|
239
|
+
def get_many_to_many_field_value(
|
|
243
240
|
field: models.ManyToManyField,
|
|
244
241
|
) -> list[models.Model]:
|
|
245
242
|
"""
|
|
@@ -257,7 +254,7 @@ def getManyToManyFieldValue(
|
|
|
257
254
|
MissingFactoryOrInstancesError: If the related model provides neither a factory nor any existing instances.
|
|
258
255
|
"""
|
|
259
256
|
related_factory = None
|
|
260
|
-
related_model =
|
|
257
|
+
related_model = get_related_model(field)
|
|
261
258
|
related_instances = list(related_model.objects.all())
|
|
262
259
|
if hasattr(related_model, "_general_manager_class"):
|
|
263
260
|
related_factory = related_model._general_manager_class.Factory # type: ignore
|
|
@@ -17,7 +17,7 @@ _AVG_DELTA_DAYS_ERROR = "avg_delta_days must be >= 0"
|
|
|
17
17
|
_EMPTY_OPTIONS_ERROR = "options must be a non-empty list"
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def
|
|
20
|
+
def lazy_measurement(
|
|
21
21
|
min_value: int | float, max_value: int | float, unit: str
|
|
22
22
|
) -> LazyFunction:
|
|
23
23
|
"""
|
|
@@ -36,7 +36,7 @@ def LazyMeasurement(
|
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def
|
|
39
|
+
def lazy_delta_date(avg_delta_days: int, base_attribute: str) -> LazyAttribute:
|
|
40
40
|
"""
|
|
41
41
|
Compute a date by offsetting an instance's base date attribute by a randomized number of days.
|
|
42
42
|
|
|
@@ -60,7 +60,7 @@ def LazyDeltaDate(avg_delta_days: int, base_attribute: str) -> LazyAttribute:
|
|
|
60
60
|
)
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
def
|
|
63
|
+
def lazy_project_name() -> LazyFunction:
|
|
64
64
|
"""Return a lazy factory producing a pseudo-random project-style name."""
|
|
65
65
|
return LazyFunction(
|
|
66
66
|
lambda: (
|
|
@@ -72,12 +72,12 @@ def LazyProjectName() -> LazyFunction:
|
|
|
72
72
|
)
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
def
|
|
75
|
+
def lazy_date_today() -> LazyFunction:
|
|
76
76
|
"""Return a lazy factory that yields today's date."""
|
|
77
77
|
return LazyFunction(lambda: date.today())
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
def
|
|
80
|
+
def lazy_date_between(start_date: date, end_date: date) -> LazyAttribute:
|
|
81
81
|
"""
|
|
82
82
|
Produce a lazy attribute that yields a date between two given dates (inclusive).
|
|
83
83
|
|
|
@@ -95,7 +95,7 @@ def LazyDateBetween(start_date: date, end_date: date) -> LazyAttribute:
|
|
|
95
95
|
return LazyAttribute(lambda _: start_date + timedelta(days=_RNG.randint(0, delta)))
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
def
|
|
98
|
+
def lazy_date_time_between(start: datetime, end: datetime) -> LazyAttribute:
|
|
99
99
|
"""
|
|
100
100
|
Produce a lazy attribute that yields a datetime within the inclusive range defined by `start` and `end`.
|
|
101
101
|
|
|
@@ -117,7 +117,7 @@ def LazyDateTimeBetween(start: datetime, end: datetime) -> LazyAttribute:
|
|
|
117
117
|
)
|
|
118
118
|
|
|
119
119
|
|
|
120
|
-
def
|
|
120
|
+
def lazy_integer(min_value: int, max_value: int) -> LazyFunction:
|
|
121
121
|
"""
|
|
122
122
|
Return a lazy factory that produces an integer within the provided bounds.
|
|
123
123
|
|
|
@@ -131,7 +131,9 @@ def LazyInteger(min_value: int, max_value: int) -> LazyFunction:
|
|
|
131
131
|
return LazyFunction(lambda: _RNG.randint(min_value, max_value))
|
|
132
132
|
|
|
133
133
|
|
|
134
|
-
def
|
|
134
|
+
def lazy_decimal(
|
|
135
|
+
min_value: float, max_value: float, precision: int = 2
|
|
136
|
+
) -> LazyFunction:
|
|
135
137
|
"""
|
|
136
138
|
Create a lazy factory that produces Decimal values between min_value and max_value, rounded to the specified precision.
|
|
137
139
|
|
|
@@ -147,7 +149,7 @@ def LazyDecimal(min_value: float, max_value: float, precision: int = 2) -> LazyF
|
|
|
147
149
|
return LazyFunction(lambda: Decimal(fmt.format(_RNG.uniform(min_value, max_value))))
|
|
148
150
|
|
|
149
151
|
|
|
150
|
-
def
|
|
152
|
+
def lazy_choice(options: list[Any]) -> LazyFunction:
|
|
151
153
|
"""
|
|
152
154
|
Create a lazy factory that selects a random element from the provided options.
|
|
153
155
|
|
|
@@ -162,7 +164,7 @@ def LazyChoice(options: list[Any]) -> LazyFunction:
|
|
|
162
164
|
return LazyFunction(lambda: _RNG.choice(options))
|
|
163
165
|
|
|
164
166
|
|
|
165
|
-
def
|
|
167
|
+
def lazy_sequence(start: int = 0, step: int = 1) -> LazyAttributeSequence:
|
|
166
168
|
"""
|
|
167
169
|
Produce a sequence attribute that yields successive integer values.
|
|
168
170
|
|
|
@@ -178,7 +180,7 @@ def LazySequence(start: int = 0, step: int = 1) -> LazyAttributeSequence:
|
|
|
178
180
|
return LazyAttributeSequence(lambda _instance, index: start + index * step)
|
|
179
181
|
|
|
180
182
|
|
|
181
|
-
def
|
|
183
|
+
def lazy_boolean(trues_ratio: float = 0.5) -> LazyFunction:
|
|
182
184
|
"""
|
|
183
185
|
Return booleans where each value is True with the specified probability.
|
|
184
186
|
|
|
@@ -191,7 +193,7 @@ def LazyBoolean(trues_ratio: float = 0.5) -> LazyFunction:
|
|
|
191
193
|
return LazyFunction(lambda: _RNG.random() < trues_ratio)
|
|
192
194
|
|
|
193
195
|
|
|
194
|
-
def
|
|
196
|
+
def lazy_uuid() -> LazyFunction:
|
|
195
197
|
"""
|
|
196
198
|
Create a lazy factory that yields RFC 4122 version 4 UUID strings.
|
|
197
199
|
|
|
@@ -201,12 +203,12 @@ def LazyUUID() -> LazyFunction:
|
|
|
201
203
|
return LazyFunction(lambda: str(uuid.uuid4()))
|
|
202
204
|
|
|
203
205
|
|
|
204
|
-
def
|
|
206
|
+
def lazy_faker_name() -> LazyFunction:
|
|
205
207
|
"""Return a lazy factory producing names using Faker."""
|
|
206
208
|
return LazyFunction(lambda: fake.name())
|
|
207
209
|
|
|
208
210
|
|
|
209
|
-
def
|
|
211
|
+
def lazy_faker_email(
|
|
210
212
|
name: Optional[str] = None, domain: Optional[str] = None
|
|
211
213
|
) -> LazyFunction:
|
|
212
214
|
"""Return a lazy factory producing email addresses with optional overrides."""
|
|
@@ -219,16 +221,16 @@ def LazyFakerEmail(
|
|
|
219
221
|
return LazyFunction(lambda: name.replace(" ", "_") + "@" + domain)
|
|
220
222
|
|
|
221
223
|
|
|
222
|
-
def
|
|
224
|
+
def lazy_faker_sentence(number_of_words: int = 6) -> LazyFunction:
|
|
223
225
|
"""Return a lazy factory producing fake sentences."""
|
|
224
226
|
return LazyFunction(lambda: fake.sentence(nb_words=number_of_words))
|
|
225
227
|
|
|
226
228
|
|
|
227
|
-
def
|
|
229
|
+
def lazy_faker_address() -> LazyFunction:
|
|
228
230
|
"""Return a lazy factory producing fake postal addresses."""
|
|
229
231
|
return LazyFunction(lambda: fake.address())
|
|
230
232
|
|
|
231
233
|
|
|
232
|
-
def
|
|
234
|
+
def lazy_faker_url() -> LazyFunction:
|
|
233
235
|
"""Return a lazy factory producing fake URLs."""
|
|
234
236
|
return LazyFunction(lambda: fake.url())
|
|
@@ -17,13 +17,13 @@ from datetime import datetime
|
|
|
17
17
|
from django.conf import settings
|
|
18
18
|
from django.db.models import Model
|
|
19
19
|
|
|
20
|
-
from general_manager.utils import args_to_kwargs
|
|
20
|
+
from general_manager.utils.args_to_kwargs import args_to_kwargs
|
|
21
21
|
from general_manager.api.property import GraphQLProperty
|
|
22
22
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
24
24
|
from general_manager.manager.input import Input
|
|
25
|
-
from general_manager.manager.
|
|
26
|
-
from general_manager.bucket.
|
|
25
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
26
|
+
from general_manager.bucket.base_bucket import Bucket
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
GeneralManagerType = TypeVar("GeneralManagerType", bound="GeneralManager")
|
|
@@ -159,10 +159,10 @@ class InterfaceBase(ABC):
|
|
|
159
159
|
*args: Positional identification values corresponding to the interface's input field order.
|
|
160
160
|
**kwargs: Named identification values matching the interface's input field names.
|
|
161
161
|
"""
|
|
162
|
-
identification = self.
|
|
163
|
-
self.identification = self.
|
|
162
|
+
identification = self.parse_input_fields_to_identification(*args, **kwargs)
|
|
163
|
+
self.identification = self.format_identification(identification)
|
|
164
164
|
|
|
165
|
-
def
|
|
165
|
+
def parse_input_fields_to_identification(
|
|
166
166
|
self,
|
|
167
167
|
*args: Any,
|
|
168
168
|
**kwargs: Any,
|
|
@@ -192,11 +192,19 @@ class InterfaceBase(ABC):
|
|
|
192
192
|
# Check for extra arguments
|
|
193
193
|
extra_args = set(kwargs.keys()) - set(self.input_fields.keys())
|
|
194
194
|
if extra_args:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
195
|
+
handled: set[str] = set()
|
|
196
|
+
for extra_arg in list(extra_args):
|
|
197
|
+
if extra_arg.endswith("_id"):
|
|
198
|
+
base = extra_arg[:-3]
|
|
199
|
+
if base in self.input_fields:
|
|
200
|
+
kwargs[base] = kwargs.pop(extra_arg)
|
|
201
|
+
handled.add(extra_arg)
|
|
202
|
+
# recompute remaining unknown keys after handling known *_id aliases
|
|
203
|
+
remaining = (extra_args - handled) | (
|
|
204
|
+
set(kwargs.keys()) - set(self.input_fields.keys())
|
|
205
|
+
)
|
|
206
|
+
if remaining:
|
|
207
|
+
raise UnexpectedInputArgumentsError(remaining)
|
|
200
208
|
|
|
201
209
|
missing_args = set(self.input_fields.keys()) - set(kwargs.keys())
|
|
202
210
|
if missing_args:
|
|
@@ -223,7 +231,7 @@ class InterfaceBase(ABC):
|
|
|
223
231
|
return identification
|
|
224
232
|
|
|
225
233
|
@staticmethod
|
|
226
|
-
def
|
|
234
|
+
def format_identification(identification: dict[str, Any]) -> dict[str, Any]:
|
|
227
235
|
"""
|
|
228
236
|
Normalise identification data by replacing manager instances with their IDs.
|
|
229
237
|
|
|
@@ -233,7 +241,7 @@ class InterfaceBase(ABC):
|
|
|
233
241
|
Returns:
|
|
234
242
|
dict[str, Any]: Identification mapping with nested managers replaced by their identifications.
|
|
235
243
|
"""
|
|
236
|
-
from general_manager.manager.
|
|
244
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
237
245
|
|
|
238
246
|
for key, value in identification.items():
|
|
239
247
|
if isinstance(value, GeneralManager):
|
|
@@ -245,12 +253,12 @@ class InterfaceBase(ABC):
|
|
|
245
253
|
identification[key].append(v.identification)
|
|
246
254
|
elif isinstance(v, dict):
|
|
247
255
|
identification[key].append(
|
|
248
|
-
InterfaceBase.
|
|
256
|
+
InterfaceBase.format_identification(v)
|
|
249
257
|
)
|
|
250
258
|
else:
|
|
251
259
|
identification[key].append(v)
|
|
252
260
|
elif isinstance(value, dict):
|
|
253
|
-
identification[key] = InterfaceBase.
|
|
261
|
+
identification[key] = InterfaceBase.format_identification(value)
|
|
254
262
|
return identification
|
|
255
263
|
|
|
256
264
|
def _process_input(
|
|
@@ -316,24 +324,24 @@ class InterfaceBase(ABC):
|
|
|
316
324
|
raise NotImplementedError
|
|
317
325
|
|
|
318
326
|
@abstractmethod
|
|
319
|
-
def
|
|
327
|
+
def get_data(self, search_date: datetime | None = None) -> Any:
|
|
320
328
|
"""Return data materialised for the manager object."""
|
|
321
329
|
raise NotImplementedError
|
|
322
330
|
|
|
323
331
|
@classmethod
|
|
324
332
|
@abstractmethod
|
|
325
|
-
def
|
|
333
|
+
def get_attribute_types(cls) -> dict[str, AttributeTypedDict]:
|
|
326
334
|
"""Return metadata describing each attribute exposed on the manager."""
|
|
327
335
|
raise NotImplementedError
|
|
328
336
|
|
|
329
337
|
@classmethod
|
|
330
338
|
@abstractmethod
|
|
331
|
-
def
|
|
339
|
+
def get_attributes(cls) -> dict[str, Any]:
|
|
332
340
|
"""Return attribute values exposed via the interface."""
|
|
333
341
|
raise NotImplementedError
|
|
334
342
|
|
|
335
343
|
@classmethod
|
|
336
|
-
def
|
|
344
|
+
def get_graph_ql_properties(cls) -> dict[str, GraphQLProperty]:
|
|
337
345
|
"""Return GraphQLProperty descriptors defined on the parent manager class."""
|
|
338
346
|
if not hasattr(cls, "_parent_class"):
|
|
339
347
|
return {}
|
|
@@ -357,7 +365,7 @@ class InterfaceBase(ABC):
|
|
|
357
365
|
|
|
358
366
|
@classmethod
|
|
359
367
|
@abstractmethod
|
|
360
|
-
def
|
|
368
|
+
def handle_interface(
|
|
361
369
|
cls,
|
|
362
370
|
) -> tuple[
|
|
363
371
|
classPreCreationMethod,
|
|
@@ -374,7 +382,7 @@ class InterfaceBase(ABC):
|
|
|
374
382
|
|
|
375
383
|
@classmethod
|
|
376
384
|
@abstractmethod
|
|
377
|
-
def
|
|
385
|
+
def get_field_type(cls, field_name: str) -> type:
|
|
378
386
|
"""
|
|
379
387
|
Return the declared Python type for an input field.
|
|
380
388
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from typing import Any, ClassVar
|
|
6
|
-
from general_manager.interface.
|
|
6
|
+
from general_manager.interface.base_interface import (
|
|
7
7
|
InterfaceBase,
|
|
8
8
|
classPostCreationMethod,
|
|
9
9
|
classPreCreationMethod,
|
|
@@ -16,7 +16,7 @@ from general_manager.interface.baseInterface import (
|
|
|
16
16
|
AttributeTypedDict,
|
|
17
17
|
)
|
|
18
18
|
from general_manager.manager.input import Input
|
|
19
|
-
from general_manager.bucket.
|
|
19
|
+
from general_manager.bucket.calculation_bucket import CalculationBucket
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class CalculationInterface(InterfaceBase):
|
|
@@ -25,7 +25,7 @@ class CalculationInterface(InterfaceBase):
|
|
|
25
25
|
_interface_type: ClassVar[str] = "calculation"
|
|
26
26
|
input_fields: ClassVar[dict[str, Input]]
|
|
27
27
|
|
|
28
|
-
def
|
|
28
|
+
def get_data(self, search_date: datetime | None = None) -> Any:
|
|
29
29
|
"""
|
|
30
30
|
Indicates that calculation interfaces do not provide stored data.
|
|
31
31
|
|
|
@@ -38,7 +38,7 @@ class CalculationInterface(InterfaceBase):
|
|
|
38
38
|
raise NotImplementedError("Calculations do not store data.")
|
|
39
39
|
|
|
40
40
|
@classmethod
|
|
41
|
-
def
|
|
41
|
+
def get_attribute_types(cls) -> dict[str, AttributeTypedDict]:
|
|
42
42
|
"""
|
|
43
43
|
Return a dictionary describing the type and metadata for each input field in the calculation interface.
|
|
44
44
|
|
|
@@ -56,7 +56,7 @@ class CalculationInterface(InterfaceBase):
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
@classmethod
|
|
59
|
-
def
|
|
59
|
+
def get_attributes(cls) -> dict[str, Any]:
|
|
60
60
|
"""Return attribute accessors that cast values using the configured inputs."""
|
|
61
61
|
return {
|
|
62
62
|
name: lambda self, name=name: cls.input_fields[name].cast(
|
|
@@ -81,7 +81,7 @@ class CalculationInterface(InterfaceBase):
|
|
|
81
81
|
return CalculationBucket(cls._parent_class).all()
|
|
82
82
|
|
|
83
83
|
@staticmethod
|
|
84
|
-
def
|
|
84
|
+
def _pre_create(
|
|
85
85
|
_name: generalManagerClassName, attrs: attributes, interface: interfaceBaseClass
|
|
86
86
|
) -> tuple[attributes, interfaceBaseClass, None]:
|
|
87
87
|
"""
|
|
@@ -111,7 +111,7 @@ class CalculationInterface(InterfaceBase):
|
|
|
111
111
|
return attrs, interface_cls, None
|
|
112
112
|
|
|
113
113
|
@staticmethod
|
|
114
|
-
def
|
|
114
|
+
def _post_create(
|
|
115
115
|
new_class: newlyCreatedGeneralManagerClass,
|
|
116
116
|
interface_class: newlyCreatedInterfaceClass,
|
|
117
117
|
_model: relatedClass,
|
|
@@ -130,17 +130,17 @@ class CalculationInterface(InterfaceBase):
|
|
|
130
130
|
interface_class._parent_class = new_class
|
|
131
131
|
|
|
132
132
|
@classmethod
|
|
133
|
-
def
|
|
133
|
+
def handle_interface(cls) -> tuple[classPreCreationMethod, classPostCreationMethod]:
|
|
134
134
|
"""
|
|
135
135
|
Return the pre- and post-creation hooks used by ``GeneralManagerMeta``.
|
|
136
136
|
|
|
137
137
|
Returns:
|
|
138
138
|
tuple[classPreCreationMethod, classPostCreationMethod]: Hook functions invoked around manager creation.
|
|
139
139
|
"""
|
|
140
|
-
return cls.
|
|
140
|
+
return cls._pre_create, cls._post_create
|
|
141
141
|
|
|
142
142
|
@classmethod
|
|
143
|
-
def
|
|
143
|
+
def get_field_type(cls, field_name: str) -> type:
|
|
144
144
|
"""
|
|
145
145
|
Get the Python type for an input field.
|
|
146
146
|
|