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.
Files changed (43) hide show
  1. general_manager/__init__.py +0 -0
  2. general_manager/api/graphql.py +732 -0
  3. general_manager/api/mutation.py +143 -0
  4. general_manager/api/property.py +20 -0
  5. general_manager/apps.py +83 -0
  6. general_manager/auxiliary/__init__.py +2 -0
  7. general_manager/auxiliary/argsToKwargs.py +25 -0
  8. general_manager/auxiliary/filterParser.py +97 -0
  9. general_manager/auxiliary/noneToZero.py +12 -0
  10. general_manager/cache/cacheDecorator.py +72 -0
  11. general_manager/cache/cacheTracker.py +33 -0
  12. general_manager/cache/dependencyIndex.py +300 -0
  13. general_manager/cache/pathMapping.py +151 -0
  14. general_manager/cache/signals.py +48 -0
  15. general_manager/factory/__init__.py +5 -0
  16. general_manager/factory/factories.py +287 -0
  17. general_manager/factory/lazy_methods.py +38 -0
  18. general_manager/interface/__init__.py +3 -0
  19. general_manager/interface/baseInterface.py +308 -0
  20. general_manager/interface/calculationInterface.py +406 -0
  21. general_manager/interface/databaseInterface.py +726 -0
  22. general_manager/manager/__init__.py +3 -0
  23. general_manager/manager/generalManager.py +136 -0
  24. general_manager/manager/groupManager.py +288 -0
  25. general_manager/manager/input.py +48 -0
  26. general_manager/manager/meta.py +75 -0
  27. general_manager/measurement/__init__.py +2 -0
  28. general_manager/measurement/measurement.py +233 -0
  29. general_manager/measurement/measurementField.py +152 -0
  30. general_manager/permission/__init__.py +1 -0
  31. general_manager/permission/basePermission.py +178 -0
  32. general_manager/permission/fileBasedPermission.py +0 -0
  33. general_manager/permission/managerBasedPermission.py +171 -0
  34. general_manager/permission/permissionChecks.py +53 -0
  35. general_manager/permission/permissionDataManager.py +55 -0
  36. general_manager/rule/__init__.py +1 -0
  37. general_manager/rule/handler.py +122 -0
  38. general_manager/rule/rule.py +313 -0
  39. generalmanager-0.0.0.dist-info/METADATA +207 -0
  40. generalmanager-0.0.0.dist-info/RECORD +43 -0
  41. generalmanager-0.0.0.dist-info/WHEEL +5 -0
  42. generalmanager-0.0.0.dist-info/licenses/LICENSE +29 -0
  43. 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
+ )