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,732 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import graphene
|
3
|
+
from typing import Any, Callable, get_args, TYPE_CHECKING, cast, Type
|
4
|
+
from decimal import Decimal
|
5
|
+
from datetime import date, datetime
|
6
|
+
import json
|
7
|
+
|
8
|
+
# Eigene Module
|
9
|
+
from general_manager.measurement.measurement import Measurement
|
10
|
+
from general_manager.manager.generalManager import GeneralManagerMeta, GeneralManager
|
11
|
+
from general_manager.api.property import GraphQLProperty
|
12
|
+
from general_manager.interface.baseInterface import InterfaceBase, Bucket
|
13
|
+
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from general_manager.permission.basePermission import BasePermission
|
16
|
+
from graphene import ResolveInfo as GraphQLResolveInfo
|
17
|
+
|
18
|
+
|
19
|
+
class MeasurementType(graphene.ObjectType): # type: ignore
|
20
|
+
value = graphene.Float()
|
21
|
+
unit = graphene.String()
|
22
|
+
required = graphene.Boolean()
|
23
|
+
editable = graphene.Boolean()
|
24
|
+
default_value = graphene.String()
|
25
|
+
|
26
|
+
|
27
|
+
def getReadPermissionFilter(
|
28
|
+
generalManagerClass: GeneralManagerMeta,
|
29
|
+
info: GraphQLResolveInfo,
|
30
|
+
) -> list[tuple[dict[str, Any], dict[str, Any]]]:
|
31
|
+
"""
|
32
|
+
Ermittelt die Filter, die auf Basis der read-Permission für den angegebenen
|
33
|
+
Manager angewendet werden müssen.
|
34
|
+
"""
|
35
|
+
filters = []
|
36
|
+
PermissionClass: type[BasePermission] | None = getattr(
|
37
|
+
generalManagerClass, "Permission", None
|
38
|
+
)
|
39
|
+
if PermissionClass:
|
40
|
+
permission_filters = PermissionClass(
|
41
|
+
generalManagerClass, info.context.user
|
42
|
+
).getPermissionFilter()
|
43
|
+
for permission_filter in permission_filters:
|
44
|
+
filter_dict = permission_filter.get("filter", {})
|
45
|
+
exclude_dict = permission_filter.get("exclude", {})
|
46
|
+
filters.append((filter_dict, exclude_dict))
|
47
|
+
return filters
|
48
|
+
|
49
|
+
|
50
|
+
class GraphQL:
|
51
|
+
"""
|
52
|
+
Baut die GraphQL-Oberfläche auf und erstellt Resolver-Funktionen
|
53
|
+
dynamisch für die angegebene GeneralManager-Klasse.
|
54
|
+
"""
|
55
|
+
|
56
|
+
_query_class: type[graphene.ObjectType] | None = None
|
57
|
+
_mutation_class: type[graphene.ObjectType] | None = None
|
58
|
+
_mutations: dict[str, Any] = {}
|
59
|
+
_query_fields: dict[str, Any] = {}
|
60
|
+
graphql_type_registry: dict[str, type] = {}
|
61
|
+
graphql_filter_type_registry: dict[str, type] = {}
|
62
|
+
|
63
|
+
@classmethod
|
64
|
+
def createGraphqlMutation(cls, generalManagerClass: type[GeneralManager]) -> None:
|
65
|
+
"""
|
66
|
+
Erzeugt ein GraphQL-Mutation-Interface für die übergebene Manager-Klasse.
|
67
|
+
Dabei werden:
|
68
|
+
- Attribute aus dem Interface in Graphene-Felder abgebildet
|
69
|
+
- Zu jedem Feld ein Resolver generiert und hinzugefügt
|
70
|
+
- Der neue Type in das Registry eingetragen und Mutationen angehängt.
|
71
|
+
"""
|
72
|
+
|
73
|
+
interface_cls: InterfaceBase | None = getattr(
|
74
|
+
generalManagerClass, "Interface", None
|
75
|
+
)
|
76
|
+
if not interface_cls:
|
77
|
+
return
|
78
|
+
|
79
|
+
default_return_values = {
|
80
|
+
"success": graphene.Boolean(),
|
81
|
+
"errors": graphene.List(graphene.String),
|
82
|
+
generalManagerClass.__name__: graphene.Field(
|
83
|
+
lambda: GraphQL.graphql_type_registry[generalManagerClass.__name__]
|
84
|
+
),
|
85
|
+
}
|
86
|
+
if InterfaceBase.create.__code__ != interface_cls.create.__code__:
|
87
|
+
create_name = f"create{generalManagerClass.__name__}"
|
88
|
+
cls._mutations[create_name] = cls.generateCreateMutationClass(
|
89
|
+
generalManagerClass, default_return_values
|
90
|
+
)
|
91
|
+
|
92
|
+
if InterfaceBase.update.__code__ != interface_cls.update.__code__:
|
93
|
+
update_name = f"update{generalManagerClass.__name__}"
|
94
|
+
cls._mutations[update_name] = cls.generateUpdateMutationClass(
|
95
|
+
generalManagerClass, default_return_values
|
96
|
+
)
|
97
|
+
|
98
|
+
if InterfaceBase.deactivate.__code__ != interface_cls.deactivate.__code__:
|
99
|
+
delete_name = f"delete{generalManagerClass.__name__}"
|
100
|
+
cls._mutations[delete_name] = cls.generateDeleteMutationClass(
|
101
|
+
generalManagerClass, default_return_values
|
102
|
+
)
|
103
|
+
|
104
|
+
@classmethod
|
105
|
+
def createGraphqlInterface(cls, generalManagerClass: GeneralManagerMeta) -> None:
|
106
|
+
"""
|
107
|
+
Erzeugt ein GraphQL-Interface für die übergebene Manager-Klasse.
|
108
|
+
Dabei werden:
|
109
|
+
- Attribute aus dem Interface in Graphene-Felder abgebildet
|
110
|
+
- Zu jedem Feld ein Resolver generiert und hinzugefügt
|
111
|
+
- Der neue Type in das Registry eingetragen und Queries angehängt.
|
112
|
+
"""
|
113
|
+
interface_cls: InterfaceBase | None = getattr(
|
114
|
+
generalManagerClass, "Interface", None
|
115
|
+
)
|
116
|
+
if not interface_cls:
|
117
|
+
return
|
118
|
+
|
119
|
+
graphene_type_name = f"{generalManagerClass.__name__}Type"
|
120
|
+
fields: dict[str, Any] = {}
|
121
|
+
|
122
|
+
# Felder aus dem Interface mappen
|
123
|
+
for field_name, field_info in interface_cls.getAttributeTypes().items():
|
124
|
+
field_type = field_info["type"]
|
125
|
+
fields[field_name] = cls._mapFieldToGrapheneRead(field_type, field_name)
|
126
|
+
resolver_name = f"resolve_{field_name}"
|
127
|
+
fields[resolver_name] = cls._createResolver(field_name, field_type)
|
128
|
+
|
129
|
+
# Zusätzliche GraphQLPropertys verarbeiten
|
130
|
+
for attr_name, attr_value in generalManagerClass.__dict__.items():
|
131
|
+
if isinstance(attr_value, GraphQLProperty):
|
132
|
+
type_hints = get_args(attr_value.graphql_type_hint)
|
133
|
+
field_type = (
|
134
|
+
type_hints[0]
|
135
|
+
if type_hints
|
136
|
+
else cast(type, attr_value.graphql_type_hint)
|
137
|
+
)
|
138
|
+
fields[attr_name] = cls._mapFieldToGrapheneRead(field_type, attr_name)
|
139
|
+
fields[f"resolve_{attr_name}"] = cls._createResolver(
|
140
|
+
attr_name, field_type
|
141
|
+
)
|
142
|
+
|
143
|
+
graphene_type = type(graphene_type_name, (graphene.ObjectType,), fields)
|
144
|
+
cls.graphql_type_registry[generalManagerClass.__name__] = graphene_type
|
145
|
+
cls._addQueriesToSchema(graphene_type, generalManagerClass)
|
146
|
+
|
147
|
+
@staticmethod
|
148
|
+
def _sortByOptions(
|
149
|
+
generalManagerClass: GeneralManagerMeta,
|
150
|
+
) -> type[graphene.Enum]:
|
151
|
+
"""
|
152
|
+
Erzeugt ein Enum für Sortieroptionen basierend auf den Attributstypen der
|
153
|
+
Manager-Klasse.
|
154
|
+
"""
|
155
|
+
sort_options = []
|
156
|
+
for (
|
157
|
+
field_name,
|
158
|
+
field_info,
|
159
|
+
) in generalManagerClass.Interface.getAttributeTypes().items():
|
160
|
+
field_type = field_info["type"]
|
161
|
+
if issubclass(field_type, GeneralManager):
|
162
|
+
continue
|
163
|
+
elif issubclass(field_type, Measurement):
|
164
|
+
sort_options.append(f"{field_name}_value")
|
165
|
+
sort_options.append(f"{field_name}_unit")
|
166
|
+
else:
|
167
|
+
sort_options.append(field_name)
|
168
|
+
|
169
|
+
return type(
|
170
|
+
f"{generalManagerClass.__name__}SortByOptions",
|
171
|
+
(graphene.Enum,),
|
172
|
+
{option: option for option in sort_options},
|
173
|
+
)
|
174
|
+
|
175
|
+
@staticmethod
|
176
|
+
def _createFilterOptions(field_name: str, field_type: GeneralManagerMeta) -> type:
|
177
|
+
"""
|
178
|
+
Baut dynamisch ein InputObjectType für Filteroptionen auf.
|
179
|
+
"""
|
180
|
+
number_options = ["exact", "gt", "gte", "lt", "lte"]
|
181
|
+
string_options = [
|
182
|
+
"exact",
|
183
|
+
"icontains",
|
184
|
+
"contains",
|
185
|
+
"in",
|
186
|
+
"startswith",
|
187
|
+
"endswith",
|
188
|
+
]
|
189
|
+
|
190
|
+
graphene_filter_type_name = f"{field_type.__name__}FilterType"
|
191
|
+
if graphene_filter_type_name in GraphQL.graphql_filter_type_registry:
|
192
|
+
return GraphQL.graphql_filter_type_registry[graphene_filter_type_name]
|
193
|
+
|
194
|
+
filter_fields = {}
|
195
|
+
for attr_name, attr_info in field_type.Interface.getAttributeTypes().items():
|
196
|
+
attr_type = attr_info["type"]
|
197
|
+
if issubclass(attr_type, GeneralManager):
|
198
|
+
continue
|
199
|
+
elif issubclass(attr_type, Measurement):
|
200
|
+
filter_fields[f"{attr_name}_value"] = graphene.Float()
|
201
|
+
filter_fields[f"{attr_name}_unit"] = graphene.String()
|
202
|
+
for option in number_options:
|
203
|
+
filter_fields[f"{attr_name}_value__{option}"] = graphene.Float()
|
204
|
+
filter_fields[f"{attr_name}_unit__{option}"] = graphene.String()
|
205
|
+
else:
|
206
|
+
filter_fields[attr_name] = GraphQL._mapFieldToGrapheneRead(
|
207
|
+
attr_type, attr_name
|
208
|
+
)
|
209
|
+
if issubclass(attr_type, (int, float, Decimal, date, datetime)):
|
210
|
+
for option in number_options:
|
211
|
+
filter_fields[f"{attr_name}__{option}"] = (
|
212
|
+
GraphQL._mapFieldToGrapheneRead(attr_type, attr_name)
|
213
|
+
)
|
214
|
+
elif issubclass(attr_type, str):
|
215
|
+
for option in string_options:
|
216
|
+
filter_fields[f"{attr_name}__{option}"] = (
|
217
|
+
GraphQL._mapFieldToGrapheneRead(attr_type, attr_name)
|
218
|
+
)
|
219
|
+
|
220
|
+
filter_class = type(
|
221
|
+
graphene_filter_type_name,
|
222
|
+
(graphene.InputObjectType,),
|
223
|
+
filter_fields,
|
224
|
+
)
|
225
|
+
GraphQL.graphql_filter_type_registry[graphene_filter_type_name] = filter_class
|
226
|
+
return filter_class
|
227
|
+
|
228
|
+
@staticmethod
|
229
|
+
def _mapFieldToGrapheneRead(field_type: type, field_name: str) -> Any:
|
230
|
+
if issubclass(field_type, Measurement):
|
231
|
+
return graphene.Field(MeasurementType, target_unit=graphene.String())
|
232
|
+
elif issubclass(field_type, GeneralManager):
|
233
|
+
if field_name.endswith("_list"):
|
234
|
+
filter_options = GraphQL._createFilterOptions(field_name, field_type)
|
235
|
+
sort_by_options = GraphQL._sortByOptions(field_type)
|
236
|
+
return graphene.List(
|
237
|
+
lambda: GraphQL.graphql_type_registry[field_type.__name__],
|
238
|
+
filter=filter_options(),
|
239
|
+
exclude=filter_options(),
|
240
|
+
sort_by=sort_by_options(),
|
241
|
+
reverse=graphene.Boolean(),
|
242
|
+
page=graphene.Int(),
|
243
|
+
page_size=graphene.Int(),
|
244
|
+
group_by=graphene.List(graphene.String),
|
245
|
+
)
|
246
|
+
return graphene.Field(
|
247
|
+
lambda: GraphQL.graphql_type_registry[field_type.__name__]
|
248
|
+
)
|
249
|
+
else:
|
250
|
+
return GraphQL._mapFieldToGrapheneBaseType(field_type)()
|
251
|
+
|
252
|
+
@staticmethod
|
253
|
+
def _mapFieldToGrapheneBaseType(field_type: type) -> Type[Any]:
|
254
|
+
"""
|
255
|
+
Ordnet einen Python-Typ einem entsprechenden Graphene-Feld zu.
|
256
|
+
"""
|
257
|
+
if issubclass(field_type, str):
|
258
|
+
return graphene.String
|
259
|
+
elif issubclass(field_type, bool):
|
260
|
+
return graphene.Boolean
|
261
|
+
elif issubclass(field_type, int):
|
262
|
+
return graphene.Int
|
263
|
+
elif issubclass(field_type, (float, Decimal)):
|
264
|
+
return graphene.Float
|
265
|
+
elif issubclass(field_type, (date, datetime)):
|
266
|
+
return graphene.Date
|
267
|
+
else:
|
268
|
+
return graphene.String
|
269
|
+
|
270
|
+
@staticmethod
|
271
|
+
def _parseInput(input_val: dict[str, Any] | str | None) -> dict[str, Any]:
|
272
|
+
"""
|
273
|
+
Wandelt einen als JSON-String oder Dict gelieferten Filter/Exclude-Parameter in ein Dict um.
|
274
|
+
"""
|
275
|
+
if input_val is None:
|
276
|
+
return {}
|
277
|
+
if isinstance(input_val, str):
|
278
|
+
try:
|
279
|
+
return json.loads(input_val)
|
280
|
+
except Exception:
|
281
|
+
return {}
|
282
|
+
return input_val
|
283
|
+
|
284
|
+
@staticmethod
|
285
|
+
def _applyQueryParameters(
|
286
|
+
queryset: Bucket[GeneralManager],
|
287
|
+
filter_input: dict[str, Any] | str | None,
|
288
|
+
exclude_input: dict[str, Any] | str | None,
|
289
|
+
sort_by: graphene.Enum | None,
|
290
|
+
reverse: bool,
|
291
|
+
page: int | None,
|
292
|
+
page_size: int | None,
|
293
|
+
) -> Bucket[GeneralManager]:
|
294
|
+
"""
|
295
|
+
Wendet Filter, Excludes, Sortierung und Paginierung auf das Queryset an.
|
296
|
+
"""
|
297
|
+
filters = GraphQL._parseInput(filter_input)
|
298
|
+
if filters:
|
299
|
+
queryset = queryset.filter(**filters)
|
300
|
+
|
301
|
+
excludes = GraphQL._parseInput(exclude_input)
|
302
|
+
if excludes:
|
303
|
+
queryset = queryset.exclude(**excludes)
|
304
|
+
|
305
|
+
if sort_by:
|
306
|
+
sort_by_str = cast(str, getattr(sort_by, "value", sort_by))
|
307
|
+
queryset = queryset.sort(sort_by_str, reverse=reverse)
|
308
|
+
|
309
|
+
if page is not None or page_size is not None:
|
310
|
+
page = page or 1
|
311
|
+
page_size = page_size or 10
|
312
|
+
offset = (page - 1) * page_size
|
313
|
+
queryset = cast(Bucket, queryset[offset : offset + page_size])
|
314
|
+
|
315
|
+
return queryset
|
316
|
+
|
317
|
+
@staticmethod
|
318
|
+
def _applyPermissionFilters(
|
319
|
+
queryset: Bucket,
|
320
|
+
general_manager_class: type[GeneralManager],
|
321
|
+
info: GraphQLResolveInfo,
|
322
|
+
) -> Bucket:
|
323
|
+
"""
|
324
|
+
Wendet die vom Permission-Interface vorgegebenen Filter auf das Queryset an.
|
325
|
+
"""
|
326
|
+
permission_filters = getReadPermissionFilter(general_manager_class, info)
|
327
|
+
filtered_queryset = queryset
|
328
|
+
for perm_filter, perm_exclude in permission_filters:
|
329
|
+
qs_perm = queryset.exclude(**perm_exclude).filter(**perm_filter)
|
330
|
+
filtered_queryset = filtered_queryset | qs_perm
|
331
|
+
|
332
|
+
return filtered_queryset
|
333
|
+
|
334
|
+
@staticmethod
|
335
|
+
def _checkReadPermission(
|
336
|
+
instance: GeneralManager, info: GraphQLResolveInfo, field_name: str
|
337
|
+
) -> bool:
|
338
|
+
"""
|
339
|
+
Überprüft, ob der Benutzer Lesezugriff auf das jeweilige Feld hat.
|
340
|
+
"""
|
341
|
+
PermissionClass: type[BasePermission] | None = getattr(
|
342
|
+
instance, "Permission", None
|
343
|
+
)
|
344
|
+
if PermissionClass:
|
345
|
+
return PermissionClass(instance, info.context.user).checkPermission(
|
346
|
+
"read", field_name
|
347
|
+
)
|
348
|
+
return True
|
349
|
+
|
350
|
+
@staticmethod
|
351
|
+
def _createListResolver(
|
352
|
+
base_getter: Callable[[Any], Any], fallback_manager_class: type[GeneralManager]
|
353
|
+
) -> Callable[..., Any]:
|
354
|
+
"""
|
355
|
+
Erzeugt einen Resolver für List-Felder, der:
|
356
|
+
- Eine Basisabfrage (base_queryset) über den base_getter ermittelt
|
357
|
+
- Zuerst die permission-basierten Filter anwendet
|
358
|
+
- Anschließend Filter, Excludes, Sortierung und Paginierung übernimmt
|
359
|
+
"""
|
360
|
+
|
361
|
+
def resolver(
|
362
|
+
self: GeneralManager,
|
363
|
+
info: GraphQLResolveInfo,
|
364
|
+
filter: dict[str, Any] | str | None = None,
|
365
|
+
exclude: dict[str, Any] | str | None = None,
|
366
|
+
sort_by: graphene.Enum | None = None,
|
367
|
+
reverse: bool = False,
|
368
|
+
page: int | None = None,
|
369
|
+
page_size: int | None = None,
|
370
|
+
group_by: list[str] | None = None,
|
371
|
+
) -> Any:
|
372
|
+
base_queryset = base_getter(self)
|
373
|
+
# Verwende _manager_class aus dem Attribut falls vorhanden, ansonsten das Fallback
|
374
|
+
manager_class = getattr(
|
375
|
+
base_queryset, "_manager_class", fallback_manager_class
|
376
|
+
)
|
377
|
+
qs = GraphQL._applyPermissionFilters(base_queryset, manager_class, info)
|
378
|
+
qs = GraphQL._applyQueryParameters(
|
379
|
+
qs, filter, exclude, sort_by, reverse, page, page_size
|
380
|
+
)
|
381
|
+
if group_by is not None:
|
382
|
+
if group_by == [""]:
|
383
|
+
qs = qs.group_by()
|
384
|
+
else:
|
385
|
+
qs = qs.group_by(*group_by)
|
386
|
+
return qs
|
387
|
+
|
388
|
+
return resolver
|
389
|
+
|
390
|
+
@staticmethod
|
391
|
+
def _createMeasurementResolver(field_name: str) -> Callable[..., Any]:
|
392
|
+
"""
|
393
|
+
Erzeugt einen Resolver für Felder vom Typ Measurement.
|
394
|
+
"""
|
395
|
+
|
396
|
+
def resolver(
|
397
|
+
self: GeneralManager,
|
398
|
+
info: GraphQLResolveInfo,
|
399
|
+
target_unit: str | None = None,
|
400
|
+
) -> dict[str, Any] | None:
|
401
|
+
if not GraphQL._checkReadPermission(self, info, field_name):
|
402
|
+
return None
|
403
|
+
result = getattr(self, field_name)
|
404
|
+
if not isinstance(result, Measurement):
|
405
|
+
return None
|
406
|
+
if target_unit:
|
407
|
+
result = result.to(target_unit)
|
408
|
+
return {
|
409
|
+
"value": result.quantity.magnitude,
|
410
|
+
"unit": result.quantity.units,
|
411
|
+
}
|
412
|
+
|
413
|
+
return resolver
|
414
|
+
|
415
|
+
@staticmethod
|
416
|
+
def _createNormalResolver(field_name: str) -> Callable[..., Any]:
|
417
|
+
"""
|
418
|
+
Erzeugt einen Resolver für Standardfelder (keine Listen, keine Measurements).
|
419
|
+
"""
|
420
|
+
|
421
|
+
def resolver(self: GeneralManager, info: GraphQLResolveInfo) -> Any:
|
422
|
+
if not GraphQL._checkReadPermission(self, info, field_name):
|
423
|
+
return None
|
424
|
+
return getattr(self, field_name)
|
425
|
+
|
426
|
+
return resolver
|
427
|
+
|
428
|
+
@classmethod
|
429
|
+
def _createResolver(cls, field_name: str, field_type: type) -> Callable[..., Any]:
|
430
|
+
"""
|
431
|
+
Wählt anhand des Feldtyps den passenden Resolver aus.
|
432
|
+
"""
|
433
|
+
if field_name.endswith("_list") and issubclass(field_type, GeneralManager):
|
434
|
+
return cls._createListResolver(
|
435
|
+
lambda self: getattr(self, field_name), field_type
|
436
|
+
)
|
437
|
+
if issubclass(field_type, Measurement):
|
438
|
+
return cls._createMeasurementResolver(field_name)
|
439
|
+
return cls._createNormalResolver(field_name)
|
440
|
+
|
441
|
+
@classmethod
|
442
|
+
def _addQueriesToSchema(
|
443
|
+
cls, graphene_type: type, generalManagerClass: GeneralManagerMeta
|
444
|
+
) -> None:
|
445
|
+
"""
|
446
|
+
Fügt dem Schema Abfragen hinzu (Liste und Einzelobjekt) basierend auf der
|
447
|
+
GeneralManager-Klasse.
|
448
|
+
"""
|
449
|
+
if not issubclass(generalManagerClass, GeneralManager):
|
450
|
+
raise TypeError(
|
451
|
+
"generalManagerClass must be a subclass of GeneralManager to create a GraphQL interface"
|
452
|
+
)
|
453
|
+
|
454
|
+
if not hasattr(cls, "_query_fields"):
|
455
|
+
cls._query_fields: dict[str, Any] = {}
|
456
|
+
|
457
|
+
# Resolver und Feld für die Listenabfrage
|
458
|
+
list_field_name = f"{generalManagerClass.__name__.lower()}_list"
|
459
|
+
filter_options = cls._createFilterOptions(
|
460
|
+
generalManagerClass.__name__.lower(), generalManagerClass
|
461
|
+
)
|
462
|
+
sort_by_options = cls._sortByOptions(generalManagerClass)
|
463
|
+
list_field = graphene.List(
|
464
|
+
graphene_type,
|
465
|
+
filter=filter_options(),
|
466
|
+
exclude=filter_options(),
|
467
|
+
sort_by=sort_by_options(),
|
468
|
+
reverse=graphene.Boolean(),
|
469
|
+
page=graphene.Int(),
|
470
|
+
page_size=graphene.Int(),
|
471
|
+
group_by=graphene.List(graphene.String),
|
472
|
+
)
|
473
|
+
|
474
|
+
list_resolver = cls._createListResolver(
|
475
|
+
lambda self: generalManagerClass.all(), generalManagerClass
|
476
|
+
)
|
477
|
+
cls._query_fields[list_field_name] = list_field
|
478
|
+
cls._query_fields[f"resolve_{list_field_name}"] = list_resolver
|
479
|
+
|
480
|
+
# Resolver und Feld für die Einzelobjektabfrage
|
481
|
+
item_field_name = generalManagerClass.__name__.lower()
|
482
|
+
identification_fields = {}
|
483
|
+
for (
|
484
|
+
input_field_name,
|
485
|
+
input_field,
|
486
|
+
) in generalManagerClass.Interface.input_fields.items():
|
487
|
+
if issubclass(input_field.type, GeneralManager):
|
488
|
+
key = f"{input_field_name}_id"
|
489
|
+
identification_fields[key] = graphene.Int(required=True)
|
490
|
+
elif input_field_name == "id":
|
491
|
+
identification_fields[input_field_name] = graphene.ID(required=True)
|
492
|
+
else:
|
493
|
+
identification_fields[input_field_name] = cls._mapFieldToGrapheneRead(
|
494
|
+
input_field.type, input_field_name
|
495
|
+
)
|
496
|
+
identification_fields[input_field_name].required = True
|
497
|
+
|
498
|
+
item_field = graphene.Field(graphene_type, **identification_fields)
|
499
|
+
|
500
|
+
def resolver(
|
501
|
+
self: GeneralManager, info: GraphQLResolveInfo, **identification: dict
|
502
|
+
) -> GeneralManager:
|
503
|
+
return generalManagerClass(**identification)
|
504
|
+
|
505
|
+
cls._query_fields[item_field_name] = item_field
|
506
|
+
cls._query_fields[f"resolve_{item_field_name}"] = resolver
|
507
|
+
|
508
|
+
@classmethod
|
509
|
+
def createWriteFields(cls, interface_cls: InterfaceBase) -> dict[str, Any]:
|
510
|
+
fields: dict[str, Any] = {}
|
511
|
+
|
512
|
+
for name, info in interface_cls.getAttributeTypes().items():
|
513
|
+
if name in ["changed_by", "created_at", "updated_at"]:
|
514
|
+
continue
|
515
|
+
|
516
|
+
typ = info["type"]
|
517
|
+
req = info["is_required"]
|
518
|
+
default = info["default"]
|
519
|
+
|
520
|
+
if issubclass(typ, GeneralManager):
|
521
|
+
if name.endswith("_list"):
|
522
|
+
fld = graphene.List(
|
523
|
+
graphene.ID,
|
524
|
+
required=req,
|
525
|
+
default_value=default,
|
526
|
+
)
|
527
|
+
else:
|
528
|
+
fld = graphene.ID(
|
529
|
+
required=req,
|
530
|
+
default_value=default,
|
531
|
+
)
|
532
|
+
else:
|
533
|
+
base_cls = cls._mapFieldToGrapheneBaseType(typ)
|
534
|
+
fld = base_cls(
|
535
|
+
required=req,
|
536
|
+
default_value=default,
|
537
|
+
)
|
538
|
+
|
539
|
+
# Markierung, damit Dein generate*-Code weiß, was editable ist
|
540
|
+
setattr(fld, "editable", info["is_editable"])
|
541
|
+
fields[name] = fld
|
542
|
+
|
543
|
+
# history_comment bleibt optional ohne Default
|
544
|
+
fields["history_comment"] = graphene.String()
|
545
|
+
setattr(fields["history_comment"], "editable", True)
|
546
|
+
|
547
|
+
return fields
|
548
|
+
|
549
|
+
@classmethod
|
550
|
+
def generateCreateMutationClass(
|
551
|
+
cls,
|
552
|
+
generalManagerClass: type[GeneralManager],
|
553
|
+
default_return_values: dict[str, Any],
|
554
|
+
) -> type[graphene.Mutation] | None:
|
555
|
+
"""
|
556
|
+
Generiert eine Mutation-Klasse für die Erstellung eines Objekts.
|
557
|
+
"""
|
558
|
+
interface_cls: InterfaceBase | None = getattr(
|
559
|
+
generalManagerClass, "Interface", None
|
560
|
+
)
|
561
|
+
if not interface_cls:
|
562
|
+
return
|
563
|
+
|
564
|
+
def create_mutation(
|
565
|
+
self,
|
566
|
+
info: GraphQLResolveInfo,
|
567
|
+
**kwargs: dict[str, Any],
|
568
|
+
) -> GeneralManager:
|
569
|
+
try:
|
570
|
+
instance = generalManagerClass.create(
|
571
|
+
**kwargs, creator_id=info.context.user.id
|
572
|
+
)
|
573
|
+
except Exception as e:
|
574
|
+
return self.__class__(
|
575
|
+
**{
|
576
|
+
"success": False,
|
577
|
+
"errors": [str(e)],
|
578
|
+
generalManagerClass.__name__: None,
|
579
|
+
}
|
580
|
+
)
|
581
|
+
return self.__class__(
|
582
|
+
**{
|
583
|
+
"success": True,
|
584
|
+
"errors": [],
|
585
|
+
generalManagerClass.__name__: instance,
|
586
|
+
}
|
587
|
+
)
|
588
|
+
|
589
|
+
return type(
|
590
|
+
f"Create{generalManagerClass.__name__}",
|
591
|
+
(graphene.Mutation,),
|
592
|
+
{
|
593
|
+
**default_return_values,
|
594
|
+
"__doc__": f"Mutation to create {generalManagerClass.__name__}",
|
595
|
+
"Arguments": type(
|
596
|
+
"Arguments",
|
597
|
+
(),
|
598
|
+
{
|
599
|
+
field_name: field
|
600
|
+
for field_name, field in cls.createWriteFields(
|
601
|
+
interface_cls
|
602
|
+
).items()
|
603
|
+
if field_name not in generalManagerClass.Interface.input_fields
|
604
|
+
},
|
605
|
+
),
|
606
|
+
"mutate": create_mutation,
|
607
|
+
},
|
608
|
+
)
|
609
|
+
|
610
|
+
@classmethod
|
611
|
+
def generateUpdateMutationClass(
|
612
|
+
cls,
|
613
|
+
generalManagerClass: type[GeneralManager],
|
614
|
+
default_return_values: dict[str, Any],
|
615
|
+
) -> type[graphene.Mutation] | None:
|
616
|
+
"""
|
617
|
+
Generiert eine Mutation-Klasse für die Aktualisierung eines Objekts.
|
618
|
+
"""
|
619
|
+
interface_cls: InterfaceBase | None = getattr(
|
620
|
+
generalManagerClass, "Interface", None
|
621
|
+
)
|
622
|
+
if not interface_cls:
|
623
|
+
return
|
624
|
+
|
625
|
+
def update_mutation(
|
626
|
+
self,
|
627
|
+
info: GraphQLResolveInfo,
|
628
|
+
**kwargs: dict[str, Any],
|
629
|
+
) -> GeneralManager:
|
630
|
+
try:
|
631
|
+
manager_id = kwargs.pop("id", None)
|
632
|
+
instance = generalManagerClass(manager_id).update(
|
633
|
+
creator_id=info.context.user.id, **kwargs
|
634
|
+
)
|
635
|
+
except Exception as e:
|
636
|
+
return self.__class__(
|
637
|
+
**{
|
638
|
+
"success": False,
|
639
|
+
"errors": [str(e)],
|
640
|
+
generalManagerClass.__name__: None,
|
641
|
+
}
|
642
|
+
)
|
643
|
+
return self.__class__(
|
644
|
+
**{
|
645
|
+
"success": True,
|
646
|
+
"errors": [],
|
647
|
+
generalManagerClass.__name__: instance,
|
648
|
+
}
|
649
|
+
)
|
650
|
+
|
651
|
+
return type(
|
652
|
+
f"Create{generalManagerClass.__name__}",
|
653
|
+
(graphene.Mutation,),
|
654
|
+
{
|
655
|
+
**default_return_values,
|
656
|
+
"__doc__": f"Mutation to update {generalManagerClass.__name__}",
|
657
|
+
"Arguments": type(
|
658
|
+
"Arguments",
|
659
|
+
(),
|
660
|
+
{
|
661
|
+
field_name: field
|
662
|
+
for field_name, field in cls.createWriteFields(
|
663
|
+
interface_cls
|
664
|
+
).items()
|
665
|
+
if field.editable
|
666
|
+
},
|
667
|
+
),
|
668
|
+
"mutate": update_mutation,
|
669
|
+
},
|
670
|
+
)
|
671
|
+
|
672
|
+
@classmethod
|
673
|
+
def generateDeleteMutationClass(
|
674
|
+
cls,
|
675
|
+
generalManagerClass: type[GeneralManager],
|
676
|
+
default_return_values: dict[str, Any],
|
677
|
+
) -> type[graphene.Mutation] | None:
|
678
|
+
"""
|
679
|
+
Generiert eine Mutation-Klasse für die Löschung eines Objekts.
|
680
|
+
"""
|
681
|
+
interface_cls: InterfaceBase | None = getattr(
|
682
|
+
generalManagerClass, "Interface", None
|
683
|
+
)
|
684
|
+
if not interface_cls:
|
685
|
+
return
|
686
|
+
|
687
|
+
def delete_mutation(
|
688
|
+
self,
|
689
|
+
info: GraphQLResolveInfo,
|
690
|
+
**kwargs: dict[str, Any],
|
691
|
+
) -> GeneralManager:
|
692
|
+
try:
|
693
|
+
manager_id = kwargs.pop("id", None)
|
694
|
+
instance = generalManagerClass(manager_id).deactivate(
|
695
|
+
creator_id=info.context.user.id
|
696
|
+
)
|
697
|
+
except Exception as e:
|
698
|
+
return self.__class__(
|
699
|
+
**{
|
700
|
+
"success": False,
|
701
|
+
"errors": [str(e)],
|
702
|
+
generalManagerClass.__name__: None,
|
703
|
+
}
|
704
|
+
)
|
705
|
+
return self.__class__(
|
706
|
+
**{
|
707
|
+
"success": True,
|
708
|
+
"errors": [],
|
709
|
+
generalManagerClass.__name__: instance,
|
710
|
+
}
|
711
|
+
)
|
712
|
+
|
713
|
+
return type(
|
714
|
+
f"Delete{generalManagerClass.__name__}",
|
715
|
+
(graphene.Mutation,),
|
716
|
+
{
|
717
|
+
**default_return_values,
|
718
|
+
"__doc__": f"Mutation to delete {generalManagerClass.__name__}",
|
719
|
+
"Arguments": type(
|
720
|
+
"Arguments",
|
721
|
+
(),
|
722
|
+
{
|
723
|
+
field_name: field
|
724
|
+
for field_name, field in cls.createWriteFields(
|
725
|
+
interface_cls
|
726
|
+
).items()
|
727
|
+
if field_name in generalManagerClass.Interface.input_fields
|
728
|
+
},
|
729
|
+
),
|
730
|
+
"mutate": delete_mutation,
|
731
|
+
},
|
732
|
+
)
|