GeneralManager 0.19.1__py3-none-any.whl → 0.20.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.
- 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 +11 -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 +207 -98
- general_manager/api/mutation.py +9 -9
- general_manager/api/property.py +4 -4
- general_manager/apps.py +120 -65
- 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} +27 -6
- general_manager/cache/{cacheTracker.py → cache_tracker.py} +1 -1
- general_manager/cache/{dependencyIndex.py → dependency_index.py} +24 -8
- 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} +30 -22
- 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} +21 -21
- general_manager/interface/models.py +3 -3
- general_manager/interface/{readOnlyInterface.py → read_only_interface.py} +34 -25
- general_manager/logging.py +133 -0
- general_manager/manager/{generalManager.py → general_manager.py} +75 -17
- general_manager/manager/{groupManager.py → group_manager.py} +6 -6
- general_manager/manager/input.py +1 -1
- general_manager/manager/meta.py +63 -17
- general_manager/measurement/measurement.py +3 -3
- general_manager/permission/{basePermission.py → base_permission.py} +55 -32
- 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 +76 -66
- general_manager/rule/handler.py +2 -2
- general_manager/rule/rule.py +102 -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/public_api.py +19 -0
- general_manager/utils/testing.py +14 -14
- {generalmanager-0.19.1.dist-info → generalmanager-0.20.0.dist-info}/METADATA +1 -1
- generalmanager-0.20.0.dist-info/RECORD +78 -0
- generalmanager-0.19.1.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.1.dist-info → generalmanager-0.20.0.dist-info}/WHEEL +0 -0
- {generalmanager-0.19.1.dist-info → generalmanager-0.20.0.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.19.1.dist-info → generalmanager-0.20.0.dist-info}/top_level.txt +0 -0
|
@@ -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(
|
|
@@ -294,7 +302,7 @@ class InterfaceBase(ABC):
|
|
|
294
302
|
raise InvalidInputValueError(name, value, allowed_values)
|
|
295
303
|
|
|
296
304
|
@classmethod
|
|
297
|
-
def create(cls, *args: Any, **kwargs: Any) -> Any:
|
|
305
|
+
def create(cls, *args: Any, **kwargs: Any) -> dict[str, Any]:
|
|
298
306
|
"""
|
|
299
307
|
Create a new managed record in the underlying data store using the interface's inputs.
|
|
300
308
|
|
|
@@ -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
|
|
|
@@ -7,10 +7,10 @@ from django.db import models
|
|
|
7
7
|
from datetime import datetime, date, time, timedelta
|
|
8
8
|
from django.utils import timezone
|
|
9
9
|
from general_manager.measurement.measurement import Measurement
|
|
10
|
-
from general_manager.measurement.
|
|
10
|
+
from general_manager.measurement.measurement_field import MeasurementField
|
|
11
11
|
from decimal import Decimal
|
|
12
|
-
from general_manager.factory.
|
|
13
|
-
from general_manager.interface.
|
|
12
|
+
from general_manager.factory.auto_factory import AutoFactory
|
|
13
|
+
from general_manager.interface.base_interface import (
|
|
14
14
|
InterfaceBase,
|
|
15
15
|
classPostCreationMethod,
|
|
16
16
|
classPreCreationMethod,
|
|
@@ -23,11 +23,11 @@ from general_manager.interface.baseInterface import (
|
|
|
23
23
|
AttributeTypedDict,
|
|
24
24
|
)
|
|
25
25
|
from general_manager.manager.input import Input
|
|
26
|
-
from general_manager.bucket.
|
|
26
|
+
from general_manager.bucket.database_bucket import DatabaseBucket
|
|
27
27
|
from general_manager.interface.models import (
|
|
28
28
|
GeneralManagerBasisModel,
|
|
29
29
|
GeneralManagerModel,
|
|
30
|
-
|
|
30
|
+
get_full_clean_methode,
|
|
31
31
|
)
|
|
32
32
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
33
33
|
|
|
@@ -76,9 +76,9 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
76
76
|
"""
|
|
77
77
|
super().__init__(*args, **kwargs)
|
|
78
78
|
self.pk = self.identification["id"]
|
|
79
|
-
self._instance: MODEL_TYPE = self.
|
|
79
|
+
self._instance: MODEL_TYPE = self.get_data(search_date)
|
|
80
80
|
|
|
81
|
-
def
|
|
81
|
+
def get_data(self, search_date: datetime | None = None) -> MODEL_TYPE:
|
|
82
82
|
"""
|
|
83
83
|
Fetch the underlying model instance, optionally as of a historical date.
|
|
84
84
|
|
|
@@ -95,13 +95,13 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
95
95
|
if timezone.is_naive(search_date):
|
|
96
96
|
search_date = timezone.make_aware(search_date)
|
|
97
97
|
if search_date <= timezone.now() - timedelta(seconds=5):
|
|
98
|
-
historical = self.
|
|
98
|
+
historical = self.get_historical_record(instance, search_date)
|
|
99
99
|
if historical is not None:
|
|
100
100
|
instance = historical
|
|
101
101
|
return instance
|
|
102
102
|
|
|
103
103
|
@staticmethod
|
|
104
|
-
def
|
|
104
|
+
def __parse_kwargs(**kwargs: Any) -> dict[str, Any]:
|
|
105
105
|
"""
|
|
106
106
|
Convert keyword arguments into ORM-friendly values.
|
|
107
107
|
|
|
@@ -111,7 +111,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
111
111
|
Returns:
|
|
112
112
|
dict[str, Any]: Arguments ready to be passed to Django ORM methods.
|
|
113
113
|
"""
|
|
114
|
-
from general_manager.manager.
|
|
114
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
115
115
|
|
|
116
116
|
parsed_kwargs: dict[str, Any] = {}
|
|
117
117
|
for key, value in kwargs.items():
|
|
@@ -135,12 +135,12 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
135
135
|
DatabaseBucket: Bucket wrapping the filtered queryset.
|
|
136
136
|
"""
|
|
137
137
|
|
|
138
|
-
kwargs = cls.
|
|
138
|
+
kwargs = cls.__parse_kwargs(**kwargs)
|
|
139
139
|
|
|
140
140
|
return DatabaseBucket(
|
|
141
141
|
cls._model.objects.filter(**kwargs),
|
|
142
142
|
cls._parent_class,
|
|
143
|
-
cls.
|
|
143
|
+
cls.__create_filter_definitions(**kwargs),
|
|
144
144
|
)
|
|
145
145
|
|
|
146
146
|
@classmethod
|
|
@@ -154,16 +154,16 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
154
154
|
Returns:
|
|
155
155
|
DatabaseBucket: Bucket wrapping the excluded queryset.
|
|
156
156
|
"""
|
|
157
|
-
kwargs = cls.
|
|
157
|
+
kwargs = cls.__parse_kwargs(**kwargs)
|
|
158
158
|
|
|
159
159
|
return DatabaseBucket(
|
|
160
160
|
cls._model.objects.exclude(**kwargs),
|
|
161
161
|
cls._parent_class,
|
|
162
|
-
cls.
|
|
162
|
+
cls.__create_filter_definitions(**kwargs),
|
|
163
163
|
)
|
|
164
164
|
|
|
165
165
|
@staticmethod
|
|
166
|
-
def
|
|
166
|
+
def __create_filter_definitions(**kwargs: Any) -> dict[str, Any]:
|
|
167
167
|
"""
|
|
168
168
|
Build a filter-definition mapping from Django-style kwargs.
|
|
169
169
|
|
|
@@ -179,7 +179,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
179
179
|
return filter_definitions
|
|
180
180
|
|
|
181
181
|
@classmethod
|
|
182
|
-
def
|
|
182
|
+
def get_historical_record(
|
|
183
183
|
cls, instance: MODEL_TYPE, search_date: datetime | None = None
|
|
184
184
|
) -> MODEL_TYPE | None:
|
|
185
185
|
"""
|
|
@@ -196,7 +196,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
196
196
|
return cast(MODEL_TYPE | None, historical)
|
|
197
197
|
|
|
198
198
|
@classmethod
|
|
199
|
-
def
|
|
199
|
+
def get_attribute_types(cls) -> dict[str, AttributeTypedDict]:
|
|
200
200
|
"""
|
|
201
201
|
Builds a mapping of model attribute names to their type metadata for the interface.
|
|
202
202
|
|
|
@@ -232,7 +232,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
232
232
|
models.TimeField: datetime,
|
|
233
233
|
}
|
|
234
234
|
fields: dict[str, AttributeTypedDict] = {}
|
|
235
|
-
field_name_list, to_ignore_list = cls.
|
|
235
|
+
field_name_list, to_ignore_list = cls.handle_custom_fields(cls._model)
|
|
236
236
|
for field_name in field_name_list:
|
|
237
237
|
field = cast(models.Field, getattr(cls._model, field_name))
|
|
238
238
|
fields[field_name] = {
|
|
@@ -243,7 +243,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
243
243
|
"default": field.default,
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
for field_name in cls.
|
|
246
|
+
for field_name in cls.__get_model_fields():
|
|
247
247
|
if field_name not in to_ignore_list:
|
|
248
248
|
field = cast(models.Field, getattr(cls._model, field_name).field)
|
|
249
249
|
fields[field_name] = {
|
|
@@ -255,7 +255,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
255
255
|
"default": field.default,
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
for field_name in cls.
|
|
258
|
+
for field_name in cls.__get_foreign_key_fields():
|
|
259
259
|
field = cls._model._meta.get_field(field_name)
|
|
260
260
|
if isinstance(field, GenericForeignKey):
|
|
261
261
|
continue
|
|
@@ -281,8 +281,8 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
for field_name, field_call in [
|
|
284
|
-
*cls.
|
|
285
|
-
*cls.
|
|
284
|
+
*cls.__get_many_to_many_fields(),
|
|
285
|
+
*cls.__get_reverse_relations(),
|
|
286
286
|
]:
|
|
287
287
|
if field_name in fields:
|
|
288
288
|
if field_call not in fields:
|
|
@@ -317,7 +317,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
@classmethod
|
|
320
|
-
def
|
|
320
|
+
def get_attributes(cls) -> dict[str, Callable[[DBBasedInterface], Any]]:
|
|
321
321
|
"""
|
|
322
322
|
Builds a mapping of attribute names to accessor callables for a DBBasedInterface instance.
|
|
323
323
|
|
|
@@ -329,23 +329,23 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
329
329
|
Raises:
|
|
330
330
|
DuplicateFieldNameError: If a generated attribute name conflicts with an existing attribute name.
|
|
331
331
|
"""
|
|
332
|
-
from general_manager.manager.
|
|
332
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
333
333
|
|
|
334
334
|
field_values: dict[str, Any] = {}
|
|
335
335
|
|
|
336
|
-
field_name_list, to_ignore_list = cls.
|
|
336
|
+
field_name_list, to_ignore_list = cls.handle_custom_fields(cls._model)
|
|
337
337
|
for field_name in field_name_list:
|
|
338
338
|
field_values[field_name] = lambda self, field_name=field_name: getattr(
|
|
339
339
|
self._instance, field_name
|
|
340
340
|
)
|
|
341
341
|
|
|
342
|
-
for field_name in cls.
|
|
342
|
+
for field_name in cls.__get_model_fields():
|
|
343
343
|
if field_name not in to_ignore_list:
|
|
344
344
|
field_values[field_name] = lambda self, field_name=field_name: getattr(
|
|
345
345
|
self._instance, field_name
|
|
346
346
|
)
|
|
347
347
|
|
|
348
|
-
for field_name in cls.
|
|
348
|
+
for field_name in cls.__get_foreign_key_fields():
|
|
349
349
|
related_model = cls._model._meta.get_field(field_name).related_model
|
|
350
350
|
if related_model and hasattr(
|
|
351
351
|
related_model,
|
|
@@ -371,8 +371,8 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
371
371
|
)
|
|
372
372
|
|
|
373
373
|
for field_name, field_call in [
|
|
374
|
-
*cls.
|
|
375
|
-
*cls.
|
|
374
|
+
*cls.__get_many_to_many_fields(),
|
|
375
|
+
*cls.__get_reverse_relations(),
|
|
376
376
|
]:
|
|
377
377
|
if field_name in field_values:
|
|
378
378
|
if field_call not in field_values:
|
|
@@ -415,7 +415,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
415
415
|
return field_values
|
|
416
416
|
|
|
417
417
|
@staticmethod
|
|
418
|
-
def
|
|
418
|
+
def handle_custom_fields(
|
|
419
419
|
model: Type[models.Model] | models.Model,
|
|
420
420
|
) -> tuple[list[str], list[str]]:
|
|
421
421
|
"""
|
|
@@ -429,7 +429,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
429
429
|
"""
|
|
430
430
|
field_name_list: list[str] = []
|
|
431
431
|
to_ignore_list: list[str] = []
|
|
432
|
-
for field_name in DBBasedInterface.
|
|
432
|
+
for field_name in DBBasedInterface._get_custom_fields(model):
|
|
433
433
|
to_ignore_list.append(f"{field_name}_value")
|
|
434
434
|
to_ignore_list.append(f"{field_name}_unit")
|
|
435
435
|
field_name_list.append(field_name)
|
|
@@ -437,7 +437,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
437
437
|
return field_name_list, to_ignore_list
|
|
438
438
|
|
|
439
439
|
@staticmethod
|
|
440
|
-
def
|
|
440
|
+
def _get_custom_fields(model: Type[models.Model] | models.Model) -> list[str]:
|
|
441
441
|
"""
|
|
442
442
|
Return names of fields declared directly on the model class.
|
|
443
443
|
|
|
@@ -454,7 +454,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
454
454
|
]
|
|
455
455
|
|
|
456
456
|
@classmethod
|
|
457
|
-
def
|
|
457
|
+
def __get_model_fields(cls) -> list[str]:
|
|
458
458
|
"""Return names of non-relational fields defined on the model."""
|
|
459
459
|
return [
|
|
460
460
|
field.name
|
|
@@ -463,7 +463,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
463
463
|
]
|
|
464
464
|
|
|
465
465
|
@classmethod
|
|
466
|
-
def
|
|
466
|
+
def __get_foreign_key_fields(cls) -> list[str]:
|
|
467
467
|
"""Return names of foreign-key and one-to-one relations on the model."""
|
|
468
468
|
return [
|
|
469
469
|
field.name
|
|
@@ -472,7 +472,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
472
472
|
]
|
|
473
473
|
|
|
474
474
|
@classmethod
|
|
475
|
-
def
|
|
475
|
+
def __get_many_to_many_fields(cls) -> list[tuple[str, str]]:
|
|
476
476
|
"""Return (field_name, accessor_name) tuples for many-to-many fields."""
|
|
477
477
|
return [
|
|
478
478
|
(field.name, field.name)
|
|
@@ -481,7 +481,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
481
481
|
]
|
|
482
482
|
|
|
483
483
|
@classmethod
|
|
484
|
-
def
|
|
484
|
+
def __get_reverse_relations(cls) -> list[tuple[str, str]]:
|
|
485
485
|
"""Return (field_name, accessor_name) tuples for reverse one-to-many relations."""
|
|
486
486
|
return [
|
|
487
487
|
(field.name, f"{field.name}_set")
|
|
@@ -490,7 +490,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
490
490
|
]
|
|
491
491
|
|
|
492
492
|
@staticmethod
|
|
493
|
-
def
|
|
493
|
+
def _pre_create(
|
|
494
494
|
name: generalManagerClassName,
|
|
495
495
|
attrs: attributes,
|
|
496
496
|
interface: interfaceBaseClass,
|
|
@@ -539,7 +539,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
539
539
|
if meta_class and rules:
|
|
540
540
|
model._meta.rules = rules # type: ignore[attr-defined]
|
|
541
541
|
# add full_clean method
|
|
542
|
-
model.full_clean =
|
|
542
|
+
model.full_clean = get_full_clean_methode(model) # type: ignore[assignment]
|
|
543
543
|
# Determine interface type
|
|
544
544
|
attrs["_interface_type"] = interface._interface_type
|
|
545
545
|
interface_cls = type(interface.__name__, (interface,), {})
|
|
@@ -562,7 +562,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
562
562
|
return attrs, interface_cls, model
|
|
563
563
|
|
|
564
564
|
@staticmethod
|
|
565
|
-
def
|
|
565
|
+
def _post_create(
|
|
566
566
|
new_class: newlyCreatedGeneralManagerClass,
|
|
567
567
|
interface_class: newlyCreatedInterfaceClass,
|
|
568
568
|
model: relatedClass,
|
|
@@ -581,7 +581,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
581
581
|
model._general_manager_class = new_class # type: ignore
|
|
582
582
|
|
|
583
583
|
@classmethod
|
|
584
|
-
def
|
|
584
|
+
def handle_interface(
|
|
585
585
|
cls,
|
|
586
586
|
) -> tuple[classPreCreationMethod, classPostCreationMethod]:
|
|
587
587
|
"""
|
|
@@ -590,10 +590,10 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
590
590
|
Returns:
|
|
591
591
|
tuple[classPreCreationMethod, classPostCreationMethod]: A pair (pre_create, post_create) where `pre_create` is invoked before the manager class is created to allow customization, and `post_create` is invoked after creation to finalize setup.
|
|
592
592
|
"""
|
|
593
|
-
return cls.
|
|
593
|
+
return cls._pre_create, cls._post_create
|
|
594
594
|
|
|
595
595
|
@classmethod
|
|
596
|
-
def
|
|
596
|
+
def get_field_type(cls, field_name: str) -> type:
|
|
597
597
|
"""
|
|
598
598
|
Return the type associated with a given model field name.
|
|
599
599
|
|