GeneralManager 0.7.0__tar.gz → 0.9.0__tar.gz

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 (95) hide show
  1. {generalmanager-0.7.0 → generalmanager-0.9.0}/GeneralManager.egg-info/PKG-INFO +1 -1
  2. {generalmanager-0.7.0 → generalmanager-0.9.0}/GeneralManager.egg-info/SOURCES.txt +4 -32
  3. {generalmanager-0.7.0 → generalmanager-0.9.0}/PKG-INFO +1 -1
  4. {generalmanager-0.7.0 → generalmanager-0.9.0}/pyproject.toml +1 -1
  5. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/api/graphql.py +117 -65
  6. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/api/mutation.py +11 -10
  7. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/apps.py +62 -34
  8. generalmanager-0.9.0/src/general_manager/auxiliary/formatString.py +60 -0
  9. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/interface/baseInterface.py +1 -0
  10. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/interface/databaseBasedInterface.py +25 -78
  11. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/interface/databaseInterface.py +49 -14
  12. generalmanager-0.9.0/src/general_manager/interface/models.py +88 -0
  13. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/interface/readOnlyInterface.py +35 -28
  14. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/manager/generalManager.py +73 -14
  15. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/manager/input.py +9 -12
  16. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/manager/meta.py +26 -12
  17. generalmanager-0.9.0/src/general_manager/utils/testing.py +124 -0
  18. {generalmanager-0.7.0 → generalmanager-0.9.0}/tests/test_settings.py +10 -1
  19. generalmanager-0.9.0/tests/test_urls.py +1 -0
  20. generalmanager-0.7.0/tests/test_argsToKwargs.py +0 -45
  21. generalmanager-0.7.0/tests/test_autoFactory.py +0 -247
  22. generalmanager-0.7.0/tests/test_baseBucket.py +0 -325
  23. generalmanager-0.7.0/tests/test_baseInterface.py +0 -273
  24. generalmanager-0.7.0/tests/test_basePermission.py +0 -146
  25. generalmanager-0.7.0/tests/test_cacheDecorator.py +0 -396
  26. generalmanager-0.7.0/tests/test_cacheTracker.py +0 -76
  27. generalmanager-0.7.0/tests/test_calculationBucket.py +0 -373
  28. generalmanager-0.7.0/tests/test_calculationInterface.py +0 -124
  29. generalmanager-0.7.0/tests/test_databaseBasedInterface.py +0 -274
  30. generalmanager-0.7.0/tests/test_databaseBucket.py +0 -333
  31. generalmanager-0.7.0/tests/test_databaseInterface.py +0 -181
  32. generalmanager-0.7.0/tests/test_dependencyIndex.py +0 -967
  33. generalmanager-0.7.0/tests/test_factories.py +0 -351
  34. generalmanager-0.7.0/tests/test_factoryMethods.py +0 -228
  35. generalmanager-0.7.0/tests/test_filterParser.py +0 -196
  36. generalmanager-0.7.0/tests/test_generalManager.py +0 -278
  37. generalmanager-0.7.0/tests/test_generalManagerMeta.py +0 -582
  38. generalmanager-0.7.0/tests/test_graph_ql.py +0 -215
  39. generalmanager-0.7.0/tests/test_groupManager.py +0 -322
  40. generalmanager-0.7.0/tests/test_input.py +0 -165
  41. generalmanager-0.7.0/tests/test_jsonEncoder.py +0 -49
  42. generalmanager-0.7.0/tests/test_makeCacheKey.py +0 -372
  43. generalmanager-0.7.0/tests/test_managerBasedPermission.py +0 -293
  44. generalmanager-0.7.0/tests/test_measurement.py +0 -252
  45. generalmanager-0.7.0/tests/test_measurement_field.py +0 -122
  46. generalmanager-0.7.0/tests/test_modelDependencyCollector.py +0 -119
  47. generalmanager-0.7.0/tests/test_noneToZero.py +0 -17
  48. generalmanager-0.7.0/tests/test_readOnlyInterface.py +0 -370
  49. generalmanager-0.7.0/tests/test_rule_handler.py +0 -435
  50. generalmanager-0.7.0/tests/test_rules.py +0 -189
  51. generalmanager-0.7.0/tests/test_signals.py +0 -109
  52. {generalmanager-0.7.0 → generalmanager-0.9.0}/GeneralManager.egg-info/dependency_links.txt +0 -0
  53. {generalmanager-0.7.0 → generalmanager-0.9.0}/GeneralManager.egg-info/requires.txt +0 -0
  54. {generalmanager-0.7.0 → generalmanager-0.9.0}/GeneralManager.egg-info/top_level.txt +0 -0
  55. {generalmanager-0.7.0 → generalmanager-0.9.0}/LICENSE +0 -0
  56. {generalmanager-0.7.0 → generalmanager-0.9.0}/README.md +0 -0
  57. {generalmanager-0.7.0 → generalmanager-0.9.0}/setup.cfg +0 -0
  58. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/__init__.py +0 -0
  59. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/api/property.py +0 -0
  60. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/__init__.py +0 -0
  61. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/argsToKwargs.py +0 -0
  62. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/filterParser.py +0 -0
  63. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/jsonEncoder.py +0 -0
  64. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/makeCacheKey.py +0 -0
  65. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/noneToZero.py +0 -0
  66. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/pathMapping.py +0 -0
  67. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/bucket/baseBucket.py +0 -0
  68. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/bucket/calculationBucket.py +0 -0
  69. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/bucket/databaseBucket.py +0 -0
  70. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/bucket/groupBucket.py +0 -0
  71. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/cache/cacheDecorator.py +0 -0
  72. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/cache/cacheTracker.py +0 -0
  73. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/cache/dependencyIndex.py +0 -0
  74. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/cache/modelDependencyCollector.py +0 -0
  75. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/cache/signals.py +0 -0
  76. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/factory/__init__.py +0 -0
  77. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/factory/autoFactory.py +0 -0
  78. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/factory/factories.py +0 -0
  79. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/factory/factoryMethods.py +0 -0
  80. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/interface/__init__.py +0 -0
  81. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/interface/calculationInterface.py +0 -0
  82. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/manager/__init__.py +0 -0
  83. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/manager/groupManager.py +0 -0
  84. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/measurement/__init__.py +0 -0
  85. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/measurement/measurement.py +0 -0
  86. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/measurement/measurementField.py +0 -0
  87. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/permission/__init__.py +0 -0
  88. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/permission/basePermission.py +0 -0
  89. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/permission/fileBasedPermission.py +0 -0
  90. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/permission/managerBasedPermission.py +0 -0
  91. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/permission/permissionChecks.py +0 -0
  92. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/permission/permissionDataManager.py +0 -0
  93. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/rule/__init__.py +0 -0
  94. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/rule/handler.py +0 -0
  95. {generalmanager-0.7.0 → generalmanager-0.9.0}/src/general_manager/rule/rule.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.7.0
3
+ Version: 0.9.0
4
4
  Summary: Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching.
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License-Expression: MIT
@@ -14,6 +14,7 @@ src/general_manager/api/property.py
14
14
  src/general_manager/auxiliary/__init__.py
15
15
  src/general_manager/auxiliary/argsToKwargs.py
16
16
  src/general_manager/auxiliary/filterParser.py
17
+ src/general_manager/auxiliary/formatString.py
17
18
  src/general_manager/auxiliary/jsonEncoder.py
18
19
  src/general_manager/auxiliary/makeCacheKey.py
19
20
  src/general_manager/auxiliary/noneToZero.py
@@ -36,6 +37,7 @@ src/general_manager/interface/baseInterface.py
36
37
  src/general_manager/interface/calculationInterface.py
37
38
  src/general_manager/interface/databaseBasedInterface.py
38
39
  src/general_manager/interface/databaseInterface.py
40
+ src/general_manager/interface/models.py
39
41
  src/general_manager/interface/readOnlyInterface.py
40
42
  src/general_manager/manager/__init__.py
41
43
  src/general_manager/manager/generalManager.py
@@ -54,36 +56,6 @@ src/general_manager/permission/permissionDataManager.py
54
56
  src/general_manager/rule/__init__.py
55
57
  src/general_manager/rule/handler.py
56
58
  src/general_manager/rule/rule.py
57
- tests/test_argsToKwargs.py
58
- tests/test_autoFactory.py
59
- tests/test_baseBucket.py
60
- tests/test_baseInterface.py
61
- tests/test_basePermission.py
62
- tests/test_cacheDecorator.py
63
- tests/test_cacheTracker.py
64
- tests/test_calculationBucket.py
65
- tests/test_calculationInterface.py
66
- tests/test_databaseBasedInterface.py
67
- tests/test_databaseBucket.py
68
- tests/test_databaseInterface.py
69
- tests/test_dependencyIndex.py
70
- tests/test_factories.py
71
- tests/test_factoryMethods.py
72
- tests/test_filterParser.py
73
- tests/test_generalManager.py
74
- tests/test_generalManagerMeta.py
75
- tests/test_graph_ql.py
76
- tests/test_groupManager.py
77
- tests/test_input.py
78
- tests/test_jsonEncoder.py
79
- tests/test_makeCacheKey.py
80
- tests/test_managerBasedPermission.py
81
- tests/test_measurement.py
82
- tests/test_measurement_field.py
83
- tests/test_modelDependencyCollector.py
84
- tests/test_noneToZero.py
85
- tests/test_readOnlyInterface.py
86
- tests/test_rule_handler.py
87
- tests/test_rules.py
59
+ src/general_manager/utils/testing.py
88
60
  tests/test_settings.py
89
- tests/test_signals.py
61
+ tests/test_urls.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.7.0
3
+ Version: 0.9.0
4
4
  Summary: Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching.
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "GeneralManager"
7
- version = "0.7.0"
7
+ version = "0.9.0"
8
8
  description = "Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching."
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Tim Kleindick", email = "tkleindick@yahoo.de" }]
@@ -5,19 +5,19 @@ from decimal import Decimal
5
5
  from datetime import date, datetime
6
6
  import json
7
7
 
8
- # Eigene Module
9
8
  from general_manager.measurement.measurement import Measurement
10
9
  from general_manager.manager.generalManager import GeneralManagerMeta, GeneralManager
11
10
  from general_manager.api.property import GraphQLProperty
12
11
  from general_manager.bucket.baseBucket import Bucket
13
12
  from general_manager.interface.baseInterface import InterfaceBase
13
+ from django.db.models import NOT_PROVIDED
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from general_manager.permission.basePermission import BasePermission
17
17
  from graphene import ResolveInfo as GraphQLResolveInfo
18
18
 
19
19
 
20
- class MeasurementType(graphene.ObjectType): # type: ignore
20
+ class MeasurementType(graphene.ObjectType):
21
21
  value = graphene.Float()
22
22
  unit = graphene.String()
23
23
 
@@ -102,11 +102,9 @@ class GraphQL:
102
102
  @classmethod
103
103
  def createGraphqlInterface(cls, generalManagerClass: GeneralManagerMeta) -> None:
104
104
  """
105
- Erzeugt ein GraphQL-Interface für die übergebene Manager-Klasse.
106
- Dabei werden:
107
- - Attribute aus dem Interface in Graphene-Felder abgebildet
108
- - Zu jedem Feld ein Resolver generiert und hinzugefügt
109
- - Der neue Type in das Registry eingetragen und Queries angehängt.
105
+ Generates and registers a GraphQL ObjectType for the specified GeneralManager class.
106
+
107
+ This method maps interface attributes and GraphQLProperty attributes to Graphene fields, creates corresponding resolvers, registers the resulting type in the internal registry, and attaches relevant query fields to the schema.
110
108
  """
111
109
  interface_cls: InterfaceBase | None = getattr(
112
110
  generalManagerClass, "Interface", None
@@ -117,14 +115,14 @@ class GraphQL:
117
115
  graphene_type_name = f"{generalManagerClass.__name__}Type"
118
116
  fields: dict[str, Any] = {}
119
117
 
120
- # Felder aus dem Interface mappen
118
+ # Map Attribute Types to Graphene Fields
121
119
  for field_name, field_info in interface_cls.getAttributeTypes().items():
122
120
  field_type = field_info["type"]
123
121
  fields[field_name] = cls._mapFieldToGrapheneRead(field_type, field_name)
124
122
  resolver_name = f"resolve_{field_name}"
125
123
  fields[resolver_name] = cls._createResolver(field_name, field_type)
126
124
 
127
- # Zusätzliche GraphQLPropertys verarbeiten
125
+ # handle GraphQLProperty attributes
128
126
  for attr_name, attr_value in generalManagerClass.__dict__.items():
129
127
  if isinstance(attr_value, GraphQLProperty):
130
128
  type_hints = get_args(attr_value.graphql_type_hint)
@@ -350,10 +348,14 @@ class GraphQL:
350
348
  base_getter: Callable[[Any], Any], fallback_manager_class: type[GeneralManager]
351
349
  ) -> Callable[..., Any]:
352
350
  """
353
- Erzeugt einen Resolver für List-Felder, der:
354
- - Eine Basisabfrage (base_queryset) über den base_getter ermittelt
355
- - Zuerst die permission-basierten Filter anwendet
356
- - Anschließend Filter, Excludes, Sortierung und Paginierung übernimmt
351
+ Creates a resolver function for list fields that applies permission filters, query filters, sorting, pagination, and optional grouping to a queryset.
352
+
353
+ Parameters:
354
+ base_getter (Callable): Function to obtain the base queryset from the parent instance.
355
+ fallback_manager_class (type[GeneralManager]): Manager class to use if the queryset does not specify one.
356
+
357
+ Returns:
358
+ Callable: A resolver function for use in GraphQL list fields.
357
359
  """
358
360
 
359
361
  def resolver(
@@ -367,8 +369,23 @@ class GraphQL:
367
369
  page_size: int | None = None,
368
370
  group_by: list[str] | None = None,
369
371
  ) -> Any:
372
+ """
373
+ Resolves a list field by applying permission filters, query parameters, sorting, pagination, and optional grouping to a queryset.
374
+
375
+ Parameters:
376
+ filter: Optional filter criteria as a dictionary or JSON string.
377
+ exclude: Optional exclusion criteria as a dictionary or JSON string.
378
+ sort_by: Optional sorting field as a Graphene Enum.
379
+ reverse: If True, reverses the sort order.
380
+ page: Optional page number for pagination.
381
+ page_size: Optional number of items per page.
382
+ group_by: Optional list of field names to group results by.
383
+
384
+ Returns:
385
+ The filtered, sorted, paginated, and optionally grouped queryset.
386
+ """
370
387
  base_queryset = base_getter(self)
371
- # Verwende _manager_class aus dem Attribut falls vorhanden, ansonsten das Fallback
388
+ # use _manager_class from the attribute if available, otherwise fallback
372
389
  manager_class = getattr(
373
390
  base_queryset, "_manager_class", fallback_manager_class
374
391
  )
@@ -441,8 +458,9 @@ class GraphQL:
441
458
  cls, graphene_type: type, generalManagerClass: GeneralManagerMeta
442
459
  ) -> None:
443
460
  """
444
- Fügt dem Schema Abfragen hinzu (Liste und Einzelobjekt) basierend auf der
445
- GeneralManager-Klasse.
461
+ Adds list and single-item query fields for a GeneralManager-derived class to the GraphQL schema.
462
+
463
+ This method registers both a list query (with filtering, sorting, pagination, and grouping) and a single-item query (using identification fields) for the specified manager class. The corresponding resolvers are also attached to the schema.
446
464
  """
447
465
  if not issubclass(generalManagerClass, GeneralManager):
448
466
  raise TypeError(
@@ -452,7 +470,7 @@ class GraphQL:
452
470
  if not hasattr(cls, "_query_fields"):
453
471
  cls._query_fields: dict[str, Any] = {}
454
472
 
455
- # Resolver und Feld für die Listenabfrage
473
+ # resolver and field for the list query
456
474
  list_field_name = f"{generalManagerClass.__name__.lower()}_list"
457
475
  filter_options = cls._createFilterOptions(
458
476
  generalManagerClass.__name__.lower(), generalManagerClass
@@ -475,7 +493,7 @@ class GraphQL:
475
493
  cls._query_fields[list_field_name] = list_field
476
494
  cls._query_fields[f"resolve_{list_field_name}"] = list_resolver
477
495
 
478
- # Resolver und Feld für die Einzelobjektabfrage
496
+ # resolver and field for the single item query
479
497
  item_field_name = generalManagerClass.__name__.lower()
480
498
  identification_fields = {}
481
499
  for (
@@ -505,11 +523,21 @@ class GraphQL:
505
523
 
506
524
  @classmethod
507
525
  def createWriteFields(cls, interface_cls: InterfaceBase) -> dict[str, Any]:
526
+ """
527
+ Generate a dictionary of Graphene input fields for mutations based on the attributes of the provided interface class.
528
+
529
+ Skips fields that are system-managed (`changed_by`, `created_at`, `updated_at`) or marked as derived. For attributes referencing `GeneralManager` subclasses, uses ID fields; for list references, uses a list of IDs. All other types are mapped to their corresponding Graphene scalar types. Each field is annotated with an `editable` attribute indicating if it can be modified. Adds an optional `history_comment` field marked as editable.
530
+
531
+ Returns:
532
+ dict[str, Any]: A dictionary mapping attribute names to Graphene input fields for use in mutation arguments.
533
+ """
508
534
  fields: dict[str, Any] = {}
509
535
 
510
536
  for name, info in interface_cls.getAttributeTypes().items():
511
537
  if name in ["changed_by", "created_at", "updated_at"]:
512
538
  continue
539
+ if info["is_derived"]:
540
+ continue
513
541
 
514
542
  typ = info["type"]
515
543
  req = info["is_required"]
@@ -534,11 +562,11 @@ class GraphQL:
534
562
  default_value=default,
535
563
  )
536
564
 
537
- # Markierung, damit Dein generate*-Code weiß, was editable ist
565
+ # mark for generate* code to know what is editable
538
566
  setattr(fld, "editable", info["is_editable"])
539
567
  fields[name] = fld
540
568
 
541
- # history_comment bleibt optional ohne Default
569
+ # history_comment is always optional without a default value
542
570
  fields["history_comment"] = graphene.String()
543
571
  setattr(fields["history_comment"], "editable", True)
544
572
 
@@ -551,7 +579,12 @@ class GraphQL:
551
579
  default_return_values: dict[str, Any],
552
580
  ) -> type[graphene.Mutation] | None:
553
581
  """
554
- Generiert eine Mutation-Klasse für die Erstellung eines Objekts.
582
+ Generates a Graphene mutation class for creating an instance of the specified GeneralManager subclass.
583
+
584
+ The generated mutation class defines a `mutate` method that filters out fields with `NOT_PROVIDED` values, invokes the `create` method on the manager class with the provided arguments and the current user's ID, and returns a dictionary indicating success or failure along with any errors and the created instance.
585
+
586
+ Returns:
587
+ The generated Graphene mutation class, or None if the manager class does not define an interface.
555
588
  """
556
589
  interface_cls: InterfaceBase | None = getattr(
557
590
  generalManagerClass, "Interface", None
@@ -563,26 +596,32 @@ class GraphQL:
563
596
  self,
564
597
  info: GraphQLResolveInfo,
565
598
  **kwargs: dict[str, Any],
566
- ) -> GeneralManager:
599
+ ) -> dict:
600
+ """
601
+ Creates a new instance of the specified manager class using provided input arguments.
602
+
603
+ Filters out fields with default "not provided" values before creation. Returns a dictionary indicating success status, any errors encountered, and the created instance under a key named after the manager class.
604
+ """
567
605
  try:
606
+ kwargs = {
607
+ field_name: value
608
+ for field_name, value in kwargs.items()
609
+ if value is not NOT_PROVIDED
610
+ }
568
611
  instance = generalManagerClass.create(
569
612
  **kwargs, creator_id=info.context.user.id
570
613
  )
571
614
  except Exception as e:
572
- return self.__class__(
573
- **{
574
- "success": False,
575
- "errors": [str(e)],
576
- generalManagerClass.__name__: None,
577
- }
578
- )
579
- return self.__class__(
580
- **{
581
- "success": True,
582
- "errors": [],
583
- generalManagerClass.__name__: instance,
615
+ return {
616
+ "success": False,
617
+ "errors": [str(e)],
584
618
  }
585
- )
619
+
620
+ return {
621
+ "success": True,
622
+ "errors": [],
623
+ generalManagerClass.__name__: instance,
624
+ }
586
625
 
587
626
  return type(
588
627
  f"Create{generalManagerClass.__name__}",
@@ -612,7 +651,12 @@ class GraphQL:
612
651
  default_return_values: dict[str, Any],
613
652
  ) -> type[graphene.Mutation] | None:
614
653
  """
615
- Generiert eine Mutation-Klasse für die Aktualisierung eines Objekts.
654
+ Generates a GraphQL mutation class for updating an instance of a GeneralManager subclass.
655
+
656
+ The generated mutation accepts editable fields as arguments, calls the `update` method on the manager instance, and returns a dictionary indicating success, errors, and the updated instance. If the manager class does not define an `Interface`, returns None.
657
+
658
+ Returns:
659
+ The generated Graphene mutation class, or None if no interface is defined.
616
660
  """
617
661
  interface_cls: InterfaceBase | None = getattr(
618
662
  generalManagerClass, "Interface", None
@@ -624,27 +668,32 @@ class GraphQL:
624
668
  self,
625
669
  info: GraphQLResolveInfo,
626
670
  **kwargs: dict[str, Any],
627
- ) -> GeneralManager:
671
+ ) -> dict:
672
+ """
673
+ Handles the update mutation for a GeneralManager instance, applying provided field updates and returning the operation result.
674
+
675
+ Parameters:
676
+ info (GraphQLResolveInfo): GraphQL resolver context containing user and request information.
677
+ **kwargs: Fields to update, including the required 'id' of the instance.
678
+
679
+ Returns:
680
+ dict: A dictionary containing the success status, any error messages, and the updated instance keyed by its class name.
681
+ """
628
682
  try:
629
683
  manager_id = kwargs.pop("id", None)
630
684
  instance = generalManagerClass(manager_id).update(
631
685
  creator_id=info.context.user.id, **kwargs
632
686
  )
633
687
  except Exception as e:
634
- return self.__class__(
635
- **{
636
- "success": False,
637
- "errors": [str(e)],
638
- generalManagerClass.__name__: None,
639
- }
640
- )
641
- return self.__class__(
642
- **{
643
- "success": True,
644
- "errors": [],
645
- generalManagerClass.__name__: instance,
688
+ return {
689
+ "success": False,
690
+ "errors": [str(e)],
646
691
  }
647
- )
692
+ return {
693
+ "success": True,
694
+ "errors": [],
695
+ generalManagerClass.__name__: instance,
696
+ }
648
697
 
649
698
  return type(
650
699
  f"Create{generalManagerClass.__name__}",
@@ -674,7 +723,9 @@ class GraphQL:
674
723
  default_return_values: dict[str, Any],
675
724
  ) -> type[graphene.Mutation] | None:
676
725
  """
677
- Generiert eine Mutation-Klasse für die Löschung eines Objekts.
726
+ Generates a GraphQL mutation class for deleting (deactivating) an instance of a GeneralManager subclass.
727
+
728
+ The generated mutation accepts input fields defined in the manager's interface, deactivates the specified instance, and returns a dictionary indicating success or failure, along with any errors and the deleted instance.
678
729
  """
679
730
  interface_cls: InterfaceBase | None = getattr(
680
731
  generalManagerClass, "Interface", None
@@ -686,27 +737,28 @@ class GraphQL:
686
737
  self,
687
738
  info: GraphQLResolveInfo,
688
739
  **kwargs: dict[str, Any],
689
- ) -> GeneralManager:
740
+ ) -> dict:
741
+ """
742
+ Deletes (deactivates) an instance of the specified GeneralManager class and returns the operation result.
743
+
744
+ Returns:
745
+ dict: A dictionary containing the success status, any error messages, and the deactivated instance under the class name key.
746
+ """
690
747
  try:
691
748
  manager_id = kwargs.pop("id", None)
692
749
  instance = generalManagerClass(manager_id).deactivate(
693
750
  creator_id=info.context.user.id
694
751
  )
695
752
  except Exception as e:
696
- return self.__class__(
697
- **{
698
- "success": False,
699
- "errors": [str(e)],
700
- generalManagerClass.__name__: None,
701
- }
702
- )
703
- return self.__class__(
704
- **{
705
- "success": True,
706
- "errors": [],
707
- generalManagerClass.__name__: instance,
753
+ return {
754
+ "success": False,
755
+ "errors": [str(e)],
708
756
  }
709
- )
757
+ return {
758
+ "success": True,
759
+ "errors": [],
760
+ generalManagerClass.__name__: instance,
761
+ }
710
762
 
711
763
  return type(
712
764
  f"Delete{generalManagerClass.__name__}",
@@ -5,20 +5,21 @@ import graphene
5
5
  from general_manager.api.graphql import GraphQL
6
6
  from general_manager.manager.generalManager import GeneralManager
7
7
 
8
-
9
- def snake_to_pascal(s: str) -> str:
10
- return "".join(p.title() for p in s.split("_"))
11
-
12
-
13
- def snake_to_camel(s: str) -> str:
14
- parts = s.split("_")
15
- return parts[0] + "".join(p.title() for p in parts[1:])
8
+ from general_manager.auxiliary.formatString import snake_to_camel
16
9
 
17
10
 
18
11
  def graphQlMutation(needs_role: Optional[str] = None, auth_required: bool = False):
19
12
  """
20
- Decorator to generate a graphene.Mutation from a function and register it.
21
- :param auth_required: if True, enforces that info.context.user is authenticated.
13
+ Decorator that transforms a function into a GraphQL mutation class and registers it for use in a Graphene-based API.
14
+
15
+ The decorated function must have type hints for all parameters (except `info`) and a return annotation. The decorator dynamically generates a mutation class with arguments and output fields based on the function's signature and return type. It also enforces authentication if `auth_required` is set to True, returning an error if the user is not authenticated.
16
+
17
+ Parameters:
18
+ needs_role (Optional[str]): Reserved for future use to specify a required user role.
19
+ auth_required (bool): If True, the mutation requires an authenticated user.
20
+
21
+ Returns:
22
+ Callable: A decorator that registers the mutation and returns the original function.
22
23
  """
23
24
 
24
25
  def decorator(fn):
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  from django.apps import AppConfig
2
3
  import graphene
3
4
  import os
@@ -10,9 +11,10 @@ from general_manager.manager.meta import GeneralManagerMeta
10
11
  from general_manager.manager.input import Input
11
12
  from general_manager.api.property import graphQlProperty
12
13
  from general_manager.api.graphql import GraphQL
13
- from typing import TYPE_CHECKING, Type
14
+ from typing import TYPE_CHECKING, Type, Any, cast
14
15
  from django.core.checks import register
15
16
  import logging
17
+ from django.core.management.base import BaseCommand
16
18
 
17
19
 
18
20
  if TYPE_CHECKING:
@@ -27,27 +29,35 @@ class GeneralmanagerConfig(AppConfig):
27
29
 
28
30
  def ready(self):
29
31
  """
30
- Initializes the general_manager app when Django starts.
32
+ Performs initialization tasks for the general_manager app when Django starts.
31
33
 
32
- Sets up read-only interface synchronization and schema validation, initializes general manager class attributes and connections, and conditionally configures the GraphQL schema and endpoint based on settings.
34
+ Sets up synchronization and schema validation for read-only interfaces, initializes attributes and property accessors for general manager classes, and configures the GraphQL schema and endpoint if enabled in settings.
33
35
  """
34
- self.handleReadOnlyInterface()
35
- self.initializeGeneralManagerClasses()
36
+ self.handleReadOnlyInterface(GeneralManagerMeta.read_only_classes)
37
+ self.initializeGeneralManagerClasses(
38
+ GeneralManagerMeta.pending_attribute_initialization,
39
+ GeneralManagerMeta.all_classes,
40
+ )
36
41
  if getattr(settings, "AUTOCREATE_GRAPHQL", False):
37
- self.handleGraphQL()
42
+ self.handleGraphQL(GeneralManagerMeta.pending_graphql_interfaces)
38
43
 
39
- def handleReadOnlyInterface(self):
44
+ @staticmethod
45
+ def handleReadOnlyInterface(
46
+ read_only_classes: list[Type[GeneralManager[Any, ReadOnlyInterface]]],
47
+ ):
40
48
  """
41
- Sets up synchronization and schema validation for all registered read-only interfaces.
49
+ Configures synchronization and schema validation for the provided read-only interface classes.
42
50
 
43
- This method patches Django's management command execution to ensure read-only interfaces are synchronized during server runs. It also registers system checks for each read-only interface to validate that their schemas are up to date.
51
+ Ensures that each read-only interface is synchronized before Django management commands run, and registers system checks to validate that their schemas are up to date.
44
52
  """
45
- self.patchReadOnlyInterfaceSync(GeneralManagerMeta.read_only_classes)
53
+ GeneralmanagerConfig.patchReadOnlyInterfaceSync(read_only_classes)
46
54
  from general_manager.interface.readOnlyInterface import ReadOnlyInterface
47
55
 
48
56
  logger.debug("starting to register ReadOnlyInterface schema warnings...")
49
- for general_manager_class in GeneralManagerMeta.read_only_classes:
50
- read_only_interface: ReadOnlyInterface = general_manager_class.Interface # type: ignore
57
+ for general_manager_class in read_only_classes:
58
+ read_only_interface = cast(
59
+ Type[ReadOnlyInterface], general_manager_class.Interface
60
+ )
51
61
 
52
62
  register(
53
63
  lambda app_configs, model=read_only_interface._model, manager_class=general_manager_class, **kwargs: ReadOnlyInterface.ensureSchemaIsUpToDate(
@@ -57,29 +67,37 @@ class GeneralmanagerConfig(AppConfig):
57
67
  )
58
68
 
59
69
  @staticmethod
60
- def patchReadOnlyInterfaceSync(general_manager_classes: list[Type[GeneralManager]]):
70
+ def patchReadOnlyInterfaceSync(
71
+ general_manager_classes: list[Type[GeneralManager[Any, ReadOnlyInterface]]],
72
+ ):
61
73
  """
62
- Monkey-patches Django's management command runner to synchronize read-only interface data before executing commands.
74
+ Monkey-patches Django's management command runner to synchronize all provided read-only interfaces before executing any management command, except during autoreload subprocesses of 'runserver'.
63
75
 
64
- This ensures that for each provided general manager class, its associated read-only interface's `syncData` method is called before running management commands, except during autoreload subprocesses for `runserver`.
76
+ For each class in `general_manager_classes`, the associated read-only interface's `syncData` method is called prior to command execution, ensuring data consistency before management operations.
65
77
  """
66
- from django.core.management.base import BaseCommand
78
+ from general_manager.interface.readOnlyInterface import ReadOnlyInterface
67
79
 
68
80
  original_run_from_argv = BaseCommand.run_from_argv
69
81
 
70
82
  def run_from_argv_with_sync(self, argv):
71
83
  # Ensure syncData is only called at real run of runserver
72
84
  """
73
- Runs the management command and synchronizes read-only interface data before execution when appropriate.
85
+ Executes a Django management command, synchronizing all registered read-only interfaces before execution unless running in an autoreload subprocess of 'runserver'.
86
+
87
+ Parameters:
88
+ argv (list): Command-line arguments for the management command.
74
89
 
75
- Synchronization occurs for all registered read-only interfaces unless the command is 'runserver' in an autoreload subprocess.
90
+ Returns:
91
+ The result of the original management command execution.
76
92
  """
77
93
  run_main = os.environ.get("RUN_MAIN") == "true"
78
94
  command = argv[1] if len(argv) > 1 else None
79
95
  if command != "runserver" or run_main:
80
96
  logger.debug("start syncing ReadOnlyInterface data...")
81
97
  for general_manager_class in general_manager_classes:
82
- read_only_interface: ReadOnlyInterface = general_manager_class.Interface # type: ignore
98
+ read_only_interface = cast(
99
+ Type[ReadOnlyInterface], general_manager_class.Interface
100
+ )
83
101
  read_only_interface.syncData()
84
102
 
85
103
  logger.debug("finished syncing ReadOnlyInterface data.")
@@ -88,18 +106,20 @@ class GeneralmanagerConfig(AppConfig):
88
106
 
89
107
  BaseCommand.run_from_argv = run_from_argv_with_sync
90
108
 
91
- def initializeGeneralManagerClasses(self):
109
+ @staticmethod
110
+ def initializeGeneralManagerClasses(
111
+ pending_attribute_initialization: list[Type[GeneralManager]],
112
+ all_classes: list[Type[GeneralManager]],
113
+ ):
92
114
  """
93
- Initializes attributes and interconnections for all GeneralManager classes.
115
+ Initializes attributes and establishes dynamic relationships for GeneralManager classes.
94
116
 
95
- For each pending GeneralManager class, sets up its attributes and creates property accessors. Then, for all GeneralManager classes, connects input fields referencing other GeneralManager subclasses by dynamically adding GraphQL properties to enable filtered access to related objects.
117
+ For each class pending attribute initialization, assigns interface attributes and creates property accessors. Then, for all registered GeneralManager classes, connects input fields referencing other GeneralManager subclasses by adding GraphQL properties to enable filtered access to related objects.
96
118
  """
97
119
  logger.debug("Initializing GeneralManager classes...")
98
120
 
99
121
  logger.debug("starting to create attributes for GeneralManager classes...")
100
- for (
101
- general_manager_class
102
- ) in GeneralManagerMeta.pending_attribute_initialization:
122
+ for general_manager_class in pending_attribute_initialization:
103
123
  attributes = general_manager_class.Interface.getAttributes()
104
124
  setattr(general_manager_class, "_attributes", attributes)
105
125
  GeneralManagerMeta.createAtPropertiesForAttributes(
@@ -107,7 +127,7 @@ class GeneralmanagerConfig(AppConfig):
107
127
  )
108
128
 
109
129
  logger.debug("starting to connect inputs to other general manager classes...")
110
- for general_manager_class in GeneralManagerMeta.all_classes:
130
+ for general_manager_class in all_classes:
111
131
  attributes = getattr(general_manager_class.Interface, "input_fields", {})
112
132
  for attribute_name, attribute in attributes.items():
113
133
  if isinstance(attribute, Input) and issubclass(
@@ -125,12 +145,15 @@ class GeneralmanagerConfig(AppConfig):
125
145
  graphQlProperty(func),
126
146
  )
127
147
 
128
- def handleGraphQL(self):
148
+ @staticmethod
149
+ def handleGraphQL(
150
+ pending_graphql_interfaces: list[Type[GeneralManager]],
151
+ ):
129
152
  """
130
- Sets up GraphQL interfaces, mutations, and schema for all pending general manager classes, and adds the GraphQL endpoint to the Django URL configuration.
153
+ Creates GraphQL interfaces and mutations for the provided general manager classes, builds the GraphQL schema, and registers the GraphQL endpoint in the Django URL configuration.
131
154
  """
132
155
  logger.debug("Starting to create GraphQL interfaces and mutations...")
133
- for general_manager_class in GeneralManagerMeta.pending_graphql_interfaces:
156
+ for general_manager_class in pending_graphql_interfaces:
134
157
  GraphQL.createGraphqlInterface(general_manager_class)
135
158
  GraphQL.createGraphqlMutation(general_manager_class)
136
159
 
@@ -148,17 +171,22 @@ class GeneralmanagerConfig(AppConfig):
148
171
  query=GraphQL._query_class,
149
172
  mutation=GraphQL._mutation_class,
150
173
  )
151
- self.addGraphqlUrl(schema)
174
+ GeneralmanagerConfig.addGraphqlUrl(schema)
152
175
 
153
- def addGraphqlUrl(self, schema):
176
+ @staticmethod
177
+ def addGraphqlUrl(schema):
154
178
  """
155
- Dynamically adds a GraphQL endpoint to the Django URL configuration using the provided schema.
179
+ Adds a GraphQL endpoint to the Django URL configuration using the provided schema.
180
+
181
+ Parameters:
182
+ schema: The GraphQL schema to use for the endpoint.
156
183
 
157
- Raises an exception if the ROOT_URLCONF setting is not defined.
184
+ Raises:
185
+ Exception: If the ROOT_URLCONF setting is not defined in Django settings.
158
186
  """
159
187
  logging.debug("Adding GraphQL URL to Django settings...")
160
188
  root_url_conf_path = getattr(settings, "ROOT_URLCONF", None)
161
- graph_ql_url = getattr(settings, "GRAPHQL_URL", "graphql/")
189
+ graph_ql_url = getattr(settings, "GRAPHQL_URL", "graphql")
162
190
  if not root_url_conf_path:
163
191
  raise Exception("ROOT_URLCONF not found in settings")
164
192
  urlconf = import_module(root_url_conf_path)