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,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
+ )