GeneralManager 0.5.2__py3-none-any.whl → 0.6.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/api/graphql.py +2 -1
- general_manager/bucket/baseBucket.py +240 -0
- general_manager/bucket/calculationBucket.py +477 -0
- general_manager/bucket/databaseBucket.py +235 -0
- general_manager/bucket/groupBucket.py +296 -0
- general_manager/cache/modelDependencyCollector.py +5 -8
- general_manager/interface/__init__.py +0 -3
- general_manager/interface/baseInterface.py +13 -103
- general_manager/interface/calculationInterface.py +12 -299
- general_manager/interface/databaseBasedInterface.py +538 -0
- general_manager/interface/databaseInterface.py +16 -655
- general_manager/interface/readOnlyInterface.py +107 -0
- general_manager/manager/generalManager.py +11 -10
- general_manager/manager/groupManager.py +12 -190
- general_manager/manager/meta.py +19 -5
- general_manager/permission/basePermission.py +4 -6
- {generalmanager-0.5.2.dist-info → generalmanager-0.6.0.dist-info}/METADATA +2 -2
- {generalmanager-0.5.2.dist-info → generalmanager-0.6.0.dist-info}/RECORD +21 -15
- {generalmanager-0.5.2.dist-info → generalmanager-0.6.0.dist-info}/WHEEL +0 -0
- {generalmanager-0.5.2.dist-info → generalmanager-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.5.2.dist-info → generalmanager-0.6.0.dist-info}/top_level.txt +0 -0
@@ -2,11 +2,9 @@ from __future__ import annotations
|
|
2
2
|
from abc import ABC, abstractmethod
|
3
3
|
from typing import (
|
4
4
|
Type,
|
5
|
-
Generator,
|
6
5
|
TYPE_CHECKING,
|
7
6
|
Any,
|
8
7
|
TypeVar,
|
9
|
-
Generic,
|
10
8
|
Iterable,
|
11
9
|
ClassVar,
|
12
10
|
Callable,
|
@@ -21,7 +19,7 @@ if TYPE_CHECKING:
|
|
21
19
|
from general_manager.manager.input import Input
|
22
20
|
from general_manager.manager.generalManager import GeneralManager
|
23
21
|
from general_manager.manager.meta import GeneralManagerMeta
|
24
|
-
from general_manager.
|
22
|
+
from general_manager.bucket.baseBucket import Bucket
|
25
23
|
|
26
24
|
|
27
25
|
GeneralManagerType = TypeVar("GeneralManagerType", bound="GeneralManager")
|
@@ -73,7 +71,7 @@ class InterfaceBase(ABC):
|
|
73
71
|
"""
|
74
72
|
Parses and validates input arguments into a structured identification dictionary.
|
75
73
|
|
76
|
-
Converts positional and keyword arguments into a dictionary keyed by input field names,
|
74
|
+
Converts positional and keyword arguments into a dictionary keyed by input field names, normalizing argument names and ensuring all required fields are present. Processes input fields in dependency order, casting and validating each value. Raises a `TypeError` for unexpected or missing arguments and a `ValueError` if circular dependencies among input fields are detected.
|
77
75
|
|
78
76
|
Returns:
|
79
77
|
A dictionary mapping input field names to their validated and cast values.
|
@@ -134,7 +132,7 @@ class InterfaceBase(ABC):
|
|
134
132
|
"""
|
135
133
|
Validates the type and allowed values of an input field.
|
136
134
|
|
137
|
-
|
135
|
+
Ensures that the provided value matches the expected type for the specified input field. In debug mode, also checks that the value is among the allowed possible values if defined, supporting both callables and iterables. Raises a TypeError for invalid types or possible value definitions, and a ValueError if the value is not permitted.
|
138
136
|
"""
|
139
137
|
input_field = self.input_fields[name]
|
140
138
|
if not isinstance(value, input_field.type):
|
@@ -219,103 +217,15 @@ class InterfaceBase(ABC):
|
|
219
217
|
@abstractmethod
|
220
218
|
def getFieldType(cls, field_name: str) -> type:
|
221
219
|
"""
|
222
|
-
Returns the type of the
|
220
|
+
Returns the type of the specified input field.
|
221
|
+
|
222
|
+
Args:
|
223
|
+
field_name: The name of the input field.
|
224
|
+
|
225
|
+
Returns:
|
226
|
+
The Python type associated with the given field name.
|
227
|
+
|
228
|
+
Raises:
|
229
|
+
NotImplementedError: This method must be implemented by subclasses.
|
223
230
|
"""
|
224
231
|
raise NotImplementedError
|
225
|
-
|
226
|
-
|
227
|
-
class Bucket(ABC, Generic[GeneralManagerType]):
|
228
|
-
|
229
|
-
def __init__(self, manager_class: Type[GeneralManagerType]):
|
230
|
-
self._manager_class = manager_class
|
231
|
-
self._data = None
|
232
|
-
self.excludes = {}
|
233
|
-
self.filters = {}
|
234
|
-
|
235
|
-
def __eq__(self, other: object) -> bool:
|
236
|
-
if not isinstance(other, self.__class__):
|
237
|
-
return False
|
238
|
-
return self._data == other._data and self._manager_class == other._manager_class
|
239
|
-
|
240
|
-
def __reduce__(self) -> str | tuple[Any, ...]:
|
241
|
-
return (
|
242
|
-
self.__class__,
|
243
|
-
(None, self._manager_class, self.filters, self.excludes),
|
244
|
-
)
|
245
|
-
|
246
|
-
@abstractmethod
|
247
|
-
def __or__(
|
248
|
-
self, other: Bucket[GeneralManagerType] | GeneralManager[GeneralManagerType]
|
249
|
-
) -> Bucket[GeneralManagerType]:
|
250
|
-
raise NotImplementedError
|
251
|
-
|
252
|
-
@abstractmethod
|
253
|
-
def __iter__(
|
254
|
-
self,
|
255
|
-
) -> Generator[GeneralManagerType | GroupManager[GeneralManagerType]]:
|
256
|
-
raise NotImplementedError
|
257
|
-
|
258
|
-
@abstractmethod
|
259
|
-
def filter(self, **kwargs: Any) -> Bucket[GeneralManagerType]:
|
260
|
-
raise NotImplementedError
|
261
|
-
|
262
|
-
@abstractmethod
|
263
|
-
def exclude(self, **kwargs: Any) -> Bucket[GeneralManagerType]:
|
264
|
-
raise NotImplementedError
|
265
|
-
|
266
|
-
@abstractmethod
|
267
|
-
def first(self) -> GeneralManagerType | GroupManager[GeneralManagerType] | None:
|
268
|
-
raise NotImplementedError
|
269
|
-
|
270
|
-
@abstractmethod
|
271
|
-
def last(self) -> GeneralManagerType | GroupManager[GeneralManagerType] | None:
|
272
|
-
raise NotImplementedError
|
273
|
-
|
274
|
-
@abstractmethod
|
275
|
-
def count(self) -> int:
|
276
|
-
raise NotImplementedError
|
277
|
-
|
278
|
-
@abstractmethod
|
279
|
-
def all(self) -> Bucket[GeneralManagerType]:
|
280
|
-
raise NotImplementedError
|
281
|
-
|
282
|
-
@abstractmethod
|
283
|
-
def get(
|
284
|
-
self, **kwargs: Any
|
285
|
-
) -> GeneralManagerType | GroupManager[GeneralManagerType]:
|
286
|
-
raise NotImplementedError
|
287
|
-
|
288
|
-
@abstractmethod
|
289
|
-
def __getitem__(
|
290
|
-
self, item: int | slice
|
291
|
-
) -> (
|
292
|
-
GeneralManagerType
|
293
|
-
| GroupManager[GeneralManagerType]
|
294
|
-
| Bucket[GeneralManagerType]
|
295
|
-
):
|
296
|
-
raise NotImplementedError
|
297
|
-
|
298
|
-
@abstractmethod
|
299
|
-
def __len__(self) -> int:
|
300
|
-
raise NotImplementedError
|
301
|
-
|
302
|
-
@abstractmethod
|
303
|
-
def __contains__(self, item: GeneralManagerType) -> bool:
|
304
|
-
raise NotImplementedError
|
305
|
-
|
306
|
-
@abstractmethod
|
307
|
-
def sort(
|
308
|
-
self,
|
309
|
-
key: tuple[str] | str,
|
310
|
-
reverse: bool = False,
|
311
|
-
) -> Bucket[GeneralManagerType]:
|
312
|
-
raise NotImplementedError
|
313
|
-
|
314
|
-
def group_by(self, *group_by_keys: str) -> GroupBucket[GeneralManagerType]:
|
315
|
-
"""
|
316
|
-
This method groups the data by the given arguments.
|
317
|
-
It returns a GroupBucket with the grouped data.
|
318
|
-
"""
|
319
|
-
from general_manager.manager.groupManager import GroupBucket
|
320
|
-
|
321
|
-
return GroupBucket(self._manager_class, group_by_keys, self)
|
@@ -1,19 +1,8 @@
|
|
1
1
|
from __future__ import annotations
|
2
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
|
-
)
|
3
|
+
from typing import Any
|
14
4
|
from general_manager.interface.baseInterface import (
|
15
5
|
InterfaceBase,
|
16
|
-
Bucket,
|
17
6
|
classPostCreationMethod,
|
18
7
|
classPreCreationMethod,
|
19
8
|
generalManagerClassName,
|
@@ -22,15 +11,10 @@ from general_manager.interface.baseInterface import (
|
|
22
11
|
newlyCreatedGeneralManagerClass,
|
23
12
|
newlyCreatedInterfaceClass,
|
24
13
|
relatedClass,
|
25
|
-
GeneralManagerType,
|
26
14
|
AttributeTypedDict,
|
27
15
|
)
|
28
16
|
from general_manager.manager.input import Input
|
29
|
-
from general_manager.
|
30
|
-
|
31
|
-
if TYPE_CHECKING:
|
32
|
-
from general_manager.manager.generalManager import GeneralManager
|
33
|
-
from general_manager.manager.meta import GeneralManagerMeta
|
17
|
+
from general_manager.bucket.calculationBucket import CalculationBucket
|
34
18
|
|
35
19
|
|
36
20
|
class CalculationInterface(InterfaceBase):
|
@@ -118,289 +102,18 @@ class CalculationInterface(InterfaceBase):
|
|
118
102
|
@classmethod
|
119
103
|
def getFieldType(cls, field_name: str) -> type:
|
120
104
|
"""
|
121
|
-
|
105
|
+
Returns the type of the specified input field.
|
106
|
+
|
107
|
+
Args:
|
108
|
+
field_name: The name of the input field.
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
The Python type associated with the input field.
|
112
|
+
|
113
|
+
Raises:
|
114
|
+
ValueError: If the specified field name does not exist in input_fields.
|
122
115
|
"""
|
123
116
|
input = cls.input_fields.get(field_name)
|
124
117
|
if input is None:
|
125
118
|
raise ValueError(f"Field '{field_name}' not found in input fields.")
|
126
119
|
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
|
-
)
|