GeneralManager 0.0.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 +0 -0
- general_manager/api/graphql.py +732 -0
- general_manager/api/mutation.py +143 -0
- general_manager/api/property.py +20 -0
- general_manager/apps.py +83 -0
- general_manager/auxiliary/__init__.py +2 -0
- general_manager/auxiliary/argsToKwargs.py +25 -0
- general_manager/auxiliary/filterParser.py +97 -0
- general_manager/auxiliary/noneToZero.py +12 -0
- general_manager/cache/cacheDecorator.py +72 -0
- general_manager/cache/cacheTracker.py +33 -0
- general_manager/cache/dependencyIndex.py +300 -0
- general_manager/cache/pathMapping.py +151 -0
- general_manager/cache/signals.py +48 -0
- general_manager/factory/__init__.py +5 -0
- general_manager/factory/factories.py +287 -0
- general_manager/factory/lazy_methods.py +38 -0
- general_manager/interface/__init__.py +3 -0
- general_manager/interface/baseInterface.py +308 -0
- general_manager/interface/calculationInterface.py +406 -0
- general_manager/interface/databaseInterface.py +726 -0
- general_manager/manager/__init__.py +3 -0
- general_manager/manager/generalManager.py +136 -0
- general_manager/manager/groupManager.py +288 -0
- general_manager/manager/input.py +48 -0
- general_manager/manager/meta.py +75 -0
- general_manager/measurement/__init__.py +2 -0
- general_manager/measurement/measurement.py +233 -0
- general_manager/measurement/measurementField.py +152 -0
- general_manager/permission/__init__.py +1 -0
- general_manager/permission/basePermission.py +178 -0
- general_manager/permission/fileBasedPermission.py +0 -0
- general_manager/permission/managerBasedPermission.py +171 -0
- general_manager/permission/permissionChecks.py +53 -0
- general_manager/permission/permissionDataManager.py +55 -0
- general_manager/rule/__init__.py +1 -0
- general_manager/rule/handler.py +122 -0
- general_manager/rule/rule.py +313 -0
- generalmanager-0.0.0.dist-info/METADATA +207 -0
- generalmanager-0.0.0.dist-info/RECORD +43 -0
- generalmanager-0.0.0.dist-info/WHEEL +5 -0
- generalmanager-0.0.0.dist-info/licenses/LICENSE +29 -0
- generalmanager-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,406 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from datetime import datetime
|
3
|
+
from typing import (
|
4
|
+
Any,
|
5
|
+
Type,
|
6
|
+
TYPE_CHECKING,
|
7
|
+
Callable,
|
8
|
+
Iterable,
|
9
|
+
Union,
|
10
|
+
Optional,
|
11
|
+
Generator,
|
12
|
+
List,
|
13
|
+
)
|
14
|
+
from general_manager.interface.baseInterface import (
|
15
|
+
InterfaceBase,
|
16
|
+
Bucket,
|
17
|
+
classPostCreationMethod,
|
18
|
+
classPreCreationMethod,
|
19
|
+
generalManagerClassName,
|
20
|
+
attributes,
|
21
|
+
interfaceBaseClass,
|
22
|
+
newlyCreatedGeneralManagerClass,
|
23
|
+
newlyCreatedInterfaceClass,
|
24
|
+
relatedClass,
|
25
|
+
GeneralManagerType,
|
26
|
+
AttributeTypedDict,
|
27
|
+
)
|
28
|
+
from general_manager.manager.input import Input
|
29
|
+
from general_manager.auxiliary.filterParser import parse_filters
|
30
|
+
|
31
|
+
if TYPE_CHECKING:
|
32
|
+
from general_manager.manager.generalManager import GeneralManager
|
33
|
+
from general_manager.manager.meta import GeneralManagerMeta
|
34
|
+
|
35
|
+
|
36
|
+
class CalculationInterface(InterfaceBase):
|
37
|
+
_interface_type = "calculation"
|
38
|
+
input_fields: dict[str, Input]
|
39
|
+
|
40
|
+
def getData(self, search_date: datetime | None = None) -> Any:
|
41
|
+
raise NotImplementedError("Calculations do not store data.")
|
42
|
+
|
43
|
+
@classmethod
|
44
|
+
def getAttributeTypes(cls) -> dict[str, AttributeTypedDict]:
|
45
|
+
return {
|
46
|
+
name: {
|
47
|
+
"type": field.type,
|
48
|
+
"default": None,
|
49
|
+
"is_editable": False,
|
50
|
+
"is_required": True,
|
51
|
+
}
|
52
|
+
for name, field in cls.input_fields.items()
|
53
|
+
}
|
54
|
+
|
55
|
+
@classmethod
|
56
|
+
def getAttributes(cls) -> dict[str, Any]:
|
57
|
+
return {
|
58
|
+
name: lambda self, name=name: cls.input_fields[name].cast(
|
59
|
+
self.identification.get(name)
|
60
|
+
)
|
61
|
+
for name in cls.input_fields.keys()
|
62
|
+
}
|
63
|
+
|
64
|
+
@classmethod
|
65
|
+
def filter(cls, **kwargs: Any) -> CalculationBucket:
|
66
|
+
return CalculationBucket(cls._parent_class).filter(**kwargs)
|
67
|
+
|
68
|
+
@classmethod
|
69
|
+
def exclude(cls, **kwargs: Any) -> CalculationBucket:
|
70
|
+
return CalculationBucket(cls._parent_class).exclude(**kwargs)
|
71
|
+
|
72
|
+
@classmethod
|
73
|
+
def all(cls) -> CalculationBucket:
|
74
|
+
return CalculationBucket(cls._parent_class).all()
|
75
|
+
|
76
|
+
@staticmethod
|
77
|
+
def _preCreate(
|
78
|
+
name: generalManagerClassName, attrs: attributes, interface: interfaceBaseClass
|
79
|
+
) -> tuple[attributes, interfaceBaseClass, None]:
|
80
|
+
# Felder aus der Interface-Klasse sammeln
|
81
|
+
input_fields: dict[str, Input[Any]] = {}
|
82
|
+
for key, value in vars(interface).items():
|
83
|
+
if key.startswith("__"):
|
84
|
+
continue
|
85
|
+
if isinstance(value, Input):
|
86
|
+
input_fields[key] = value
|
87
|
+
|
88
|
+
# Interface-Typ bestimmen
|
89
|
+
attrs["_interface_type"] = interface._interface_type
|
90
|
+
interface_cls = type(
|
91
|
+
interface.__name__, (interface,), {"input_fields": input_fields}
|
92
|
+
)
|
93
|
+
attrs["Interface"] = interface_cls
|
94
|
+
|
95
|
+
return attrs, interface_cls, None
|
96
|
+
|
97
|
+
@staticmethod
|
98
|
+
def _postCreate(
|
99
|
+
new_class: newlyCreatedGeneralManagerClass,
|
100
|
+
interface_class: newlyCreatedInterfaceClass,
|
101
|
+
model: relatedClass,
|
102
|
+
) -> None:
|
103
|
+
interface_class._parent_class = new_class
|
104
|
+
|
105
|
+
@classmethod
|
106
|
+
def handleInterface(cls) -> tuple[classPreCreationMethod, classPostCreationMethod]:
|
107
|
+
"""
|
108
|
+
This method returns a pre and a post GeneralManager creation method
|
109
|
+
and is called inside the GeneralManagerMeta class to initialize the
|
110
|
+
Interface.
|
111
|
+
The pre creation method is called before the GeneralManager instance
|
112
|
+
is created to modify the kwargs.
|
113
|
+
The post creation method is called after the GeneralManager instance
|
114
|
+
is created to modify the instance and add additional data.
|
115
|
+
"""
|
116
|
+
return cls._preCreate, cls._postCreate
|
117
|
+
|
118
|
+
@classmethod
|
119
|
+
def getFieldType(cls, field_name: str) -> type:
|
120
|
+
"""
|
121
|
+
This method returns the field type for the given field name.
|
122
|
+
"""
|
123
|
+
input = cls.input_fields.get(field_name)
|
124
|
+
if input is None:
|
125
|
+
raise ValueError(f"Field '{field_name}' not found in input fields.")
|
126
|
+
return input.type
|
127
|
+
|
128
|
+
|
129
|
+
class CalculationBucket(Bucket[GeneralManagerType]):
|
130
|
+
def __init__(
|
131
|
+
self,
|
132
|
+
manager_class: Type[GeneralManagerType],
|
133
|
+
filter_definitions: Optional[dict[str, dict]] = None,
|
134
|
+
exclude_definitions: Optional[dict[str, dict]] = None,
|
135
|
+
sort_key: Optional[Union[str, tuple[str]]] = None,
|
136
|
+
reverse: bool = False,
|
137
|
+
):
|
138
|
+
from general_manager.interface.calculationInterface import (
|
139
|
+
CalculationInterface,
|
140
|
+
)
|
141
|
+
|
142
|
+
super().__init__(manager_class)
|
143
|
+
|
144
|
+
interface_class = manager_class.Interface
|
145
|
+
if not issubclass(interface_class, CalculationInterface):
|
146
|
+
raise TypeError(
|
147
|
+
"CalculationBucket can only be used with CalculationInterface subclasses"
|
148
|
+
)
|
149
|
+
self.input_fields = interface_class.input_fields
|
150
|
+
self.filters = {} if filter_definitions is None else filter_definitions
|
151
|
+
self.excludes = {} if exclude_definitions is None else exclude_definitions
|
152
|
+
self.__current_combinations = None
|
153
|
+
self.sort_key = sort_key
|
154
|
+
self.reverse = reverse
|
155
|
+
|
156
|
+
def __reduce__(self) -> generalManagerClassName | tuple[Any, ...]:
|
157
|
+
return (
|
158
|
+
self.__class__,
|
159
|
+
(
|
160
|
+
self._manager_class,
|
161
|
+
self.filters,
|
162
|
+
self.excludes,
|
163
|
+
self.sort_key,
|
164
|
+
self.reverse,
|
165
|
+
),
|
166
|
+
)
|
167
|
+
|
168
|
+
def __or__(
|
169
|
+
self, other: Bucket[GeneralManagerType] | GeneralManager[GeneralManagerType]
|
170
|
+
) -> CalculationBucket[GeneralManagerType]:
|
171
|
+
from general_manager.manager.generalManager import GeneralManager
|
172
|
+
|
173
|
+
if isinstance(other, GeneralManager) and other.__class__ == self._manager_class:
|
174
|
+
return self.__or__(self.filter(id__in=[other.identification]))
|
175
|
+
if not isinstance(other, self.__class__):
|
176
|
+
raise ValueError("Cannot combine different bucket types")
|
177
|
+
if self._manager_class != other._manager_class:
|
178
|
+
raise ValueError("Cannot combine different manager classes")
|
179
|
+
|
180
|
+
combined_filters = {
|
181
|
+
key: value
|
182
|
+
for key, value in self.filters.items()
|
183
|
+
if key in other.filters and value == other.filters[key]
|
184
|
+
}
|
185
|
+
|
186
|
+
combined_excludes = {
|
187
|
+
key: value
|
188
|
+
for key, value in self.excludes.items()
|
189
|
+
if key in other.excludes and value == other.excludes[key]
|
190
|
+
}
|
191
|
+
|
192
|
+
return CalculationBucket(
|
193
|
+
self._manager_class,
|
194
|
+
combined_filters,
|
195
|
+
combined_excludes,
|
196
|
+
)
|
197
|
+
|
198
|
+
def __str__(self) -> str:
|
199
|
+
PRINT_MAX = 5
|
200
|
+
combinations = self.generate_combinations()
|
201
|
+
prefix = f"CalculationBucket ({len(combinations)})["
|
202
|
+
main = ",".join(
|
203
|
+
[
|
204
|
+
f"{self._manager_class.__name__}(**{comb})"
|
205
|
+
for comb in combinations[:PRINT_MAX]
|
206
|
+
]
|
207
|
+
)
|
208
|
+
sufix = f"]"
|
209
|
+
if len(combinations) > PRINT_MAX:
|
210
|
+
sufix = f", ...]"
|
211
|
+
|
212
|
+
return f"{prefix}{main}{sufix} "
|
213
|
+
|
214
|
+
def __repr__(self) -> str:
|
215
|
+
return self.__str__()
|
216
|
+
|
217
|
+
def filter(self, **kwargs: Any) -> CalculationBucket:
|
218
|
+
filters = self.filters.copy()
|
219
|
+
excludes = self.excludes.copy()
|
220
|
+
filters.update(parse_filters(kwargs, self.input_fields))
|
221
|
+
return CalculationBucket(self._manager_class, filters, excludes)
|
222
|
+
|
223
|
+
def exclude(self, **kwargs: Any) -> CalculationBucket:
|
224
|
+
filters = self.filters.copy()
|
225
|
+
excludes = self.excludes.copy()
|
226
|
+
excludes.update(parse_filters(kwargs, self.input_fields))
|
227
|
+
return CalculationBucket(self._manager_class, filters, excludes)
|
228
|
+
|
229
|
+
def all(self) -> CalculationBucket:
|
230
|
+
return self
|
231
|
+
|
232
|
+
def __iter__(self) -> Generator[GeneralManagerType]:
|
233
|
+
combinations = self.generate_combinations()
|
234
|
+
for combo in combinations:
|
235
|
+
yield self._manager_class(**combo)
|
236
|
+
|
237
|
+
def generate_combinations(self) -> List[dict[str, Any]]:
|
238
|
+
if self.__current_combinations is None:
|
239
|
+
# Implementierung ähnlich wie im InputManager
|
240
|
+
sorted_inputs = self.topological_sort_inputs()
|
241
|
+
current_combinations = self._generate_combinations(
|
242
|
+
sorted_inputs, self.filters, self.excludes
|
243
|
+
)
|
244
|
+
if self.sort_key is not None:
|
245
|
+
sort_key = self.sort_key
|
246
|
+
if isinstance(sort_key, str):
|
247
|
+
sort_key = (sort_key,)
|
248
|
+
key_func = lambda x: (tuple(x[key] for key in sort_key))
|
249
|
+
current_combinations = sorted(
|
250
|
+
current_combinations,
|
251
|
+
key=key_func,
|
252
|
+
)
|
253
|
+
if self.reverse:
|
254
|
+
current_combinations.reverse()
|
255
|
+
self.__current_combinations = current_combinations
|
256
|
+
|
257
|
+
return self.__current_combinations
|
258
|
+
|
259
|
+
def topological_sort_inputs(self) -> List[str]:
|
260
|
+
from collections import defaultdict
|
261
|
+
|
262
|
+
dependencies = {
|
263
|
+
name: field.depends_on for name, field in self.input_fields.items()
|
264
|
+
}
|
265
|
+
graph = defaultdict(set)
|
266
|
+
for key, deps in dependencies.items():
|
267
|
+
for dep in deps:
|
268
|
+
graph[dep].add(key)
|
269
|
+
|
270
|
+
visited = set()
|
271
|
+
sorted_inputs = []
|
272
|
+
|
273
|
+
def visit(node, temp_mark):
|
274
|
+
if node in visited:
|
275
|
+
return
|
276
|
+
if node in temp_mark:
|
277
|
+
raise ValueError(f"Cyclic dependency detected: {node}")
|
278
|
+
temp_mark.add(node)
|
279
|
+
for m in graph.get(node, []):
|
280
|
+
visit(m, temp_mark)
|
281
|
+
temp_mark.remove(node)
|
282
|
+
visited.add(node)
|
283
|
+
sorted_inputs.append(node)
|
284
|
+
|
285
|
+
for node in self.input_fields.keys():
|
286
|
+
if node not in visited:
|
287
|
+
visit(node, set())
|
288
|
+
|
289
|
+
sorted_inputs.reverse()
|
290
|
+
return sorted_inputs
|
291
|
+
|
292
|
+
def get_possible_values(
|
293
|
+
self, key_name: str, input_field: Input, current_combo: dict
|
294
|
+
) -> Union[Iterable[Any], Bucket[Any]]:
|
295
|
+
# Hole mögliche Werte
|
296
|
+
if callable(input_field.possible_values):
|
297
|
+
depends_on = input_field.depends_on
|
298
|
+
dep_values = {dep_name: current_combo[dep_name] for dep_name in depends_on}
|
299
|
+
possible_values = input_field.possible_values(**dep_values)
|
300
|
+
elif isinstance(input_field.possible_values, (Iterable, Bucket)):
|
301
|
+
possible_values = input_field.possible_values
|
302
|
+
else:
|
303
|
+
raise TypeError(f"Invalid possible_values for input '{key_name}'")
|
304
|
+
return possible_values
|
305
|
+
|
306
|
+
def _generate_combinations(
|
307
|
+
self,
|
308
|
+
sorted_inputs: List[str],
|
309
|
+
filters: dict[str, dict],
|
310
|
+
excludes: dict[str, dict],
|
311
|
+
) -> List[dict[str, Any]]:
|
312
|
+
def helper(index, current_combo):
|
313
|
+
if index == len(sorted_inputs):
|
314
|
+
yield current_combo.copy()
|
315
|
+
return
|
316
|
+
input_name: str = sorted_inputs[index]
|
317
|
+
input_field = self.input_fields[input_name]
|
318
|
+
|
319
|
+
# Hole mögliche Werte
|
320
|
+
possible_values = self.get_possible_values(
|
321
|
+
input_name, input_field, current_combo
|
322
|
+
)
|
323
|
+
|
324
|
+
# Wende die Filter an
|
325
|
+
field_filters = filters.get(input_name, {})
|
326
|
+
field_excludes = excludes.get(input_name, {})
|
327
|
+
|
328
|
+
if isinstance(possible_values, Bucket):
|
329
|
+
# Wende die Filter- und Exklusionsargumente direkt an
|
330
|
+
filter_kwargs = field_filters.get("filter_kwargs", {})
|
331
|
+
exclude_kwargs = field_excludes.get("filter_kwargs", {})
|
332
|
+
possible_values = possible_values.filter(**filter_kwargs).exclude(
|
333
|
+
**exclude_kwargs
|
334
|
+
)
|
335
|
+
else:
|
336
|
+
# Wende die Filterfunktionen an
|
337
|
+
filter_funcs = field_filters.get("filter_funcs", [])
|
338
|
+
for filter_func in filter_funcs:
|
339
|
+
possible_values = filter(filter_func, possible_values)
|
340
|
+
|
341
|
+
exclude_funcs = field_excludes.get("filter_funcs", [])
|
342
|
+
for exclude_func in exclude_funcs:
|
343
|
+
possible_values = filter(
|
344
|
+
lambda x: not exclude_func(x), possible_values
|
345
|
+
)
|
346
|
+
|
347
|
+
possible_values = list(possible_values)
|
348
|
+
|
349
|
+
for value in possible_values:
|
350
|
+
if not isinstance(value, input_field.type):
|
351
|
+
continue
|
352
|
+
current_combo[input_name] = value
|
353
|
+
yield from helper(index + 1, current_combo)
|
354
|
+
del current_combo[input_name]
|
355
|
+
|
356
|
+
return list(helper(0, {}))
|
357
|
+
|
358
|
+
def first(self) -> GeneralManagerType | None:
|
359
|
+
try:
|
360
|
+
return next(iter(self))
|
361
|
+
except StopIteration:
|
362
|
+
return None
|
363
|
+
|
364
|
+
def last(self) -> GeneralManagerType | None:
|
365
|
+
items = list(self)
|
366
|
+
if items:
|
367
|
+
return items[-1]
|
368
|
+
return None
|
369
|
+
|
370
|
+
def count(self) -> int:
|
371
|
+
return sum(1 for _ in self)
|
372
|
+
|
373
|
+
def __len__(self) -> int:
|
374
|
+
return self.count()
|
375
|
+
|
376
|
+
def __getitem__(
|
377
|
+
self, item: int | slice
|
378
|
+
) -> GeneralManagerType | CalculationBucket[GeneralManagerType]:
|
379
|
+
items = self.generate_combinations()
|
380
|
+
result = items[item]
|
381
|
+
if isinstance(result, list):
|
382
|
+
new_bucket = CalculationBucket(self._manager_class)
|
383
|
+
new_bucket.filters = self.filters.copy()
|
384
|
+
new_bucket.excludes = self.excludes.copy()
|
385
|
+
return new_bucket
|
386
|
+
return self._manager_class(**result)
|
387
|
+
|
388
|
+
def __contains__(self, item: GeneralManagerType) -> bool:
|
389
|
+
return item in list(self)
|
390
|
+
|
391
|
+
def get(self, **kwargs: Any) -> GeneralManagerType:
|
392
|
+
filtered_bucket = self.filter(**kwargs)
|
393
|
+
items = list(filtered_bucket)
|
394
|
+
if len(items) == 1:
|
395
|
+
return items[0]
|
396
|
+
elif len(items) == 0:
|
397
|
+
raise ValueError("No matching calculation found.")
|
398
|
+
else:
|
399
|
+
raise ValueError("Multiple matching calculations found.")
|
400
|
+
|
401
|
+
def sort(
|
402
|
+
self, key: str | tuple[str], reverse: bool = False
|
403
|
+
) -> CalculationBucket[GeneralManagerType]:
|
404
|
+
return CalculationBucket(
|
405
|
+
self._manager_class, self.filters, self.excludes, key, reverse
|
406
|
+
)
|