GeneralManager 0.7.0__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- general_manager/api/graphql.py +117 -65
- general_manager/api/mutation.py +11 -10
- general_manager/apps.py +62 -34
- general_manager/auxiliary/formatString.py +60 -0
- general_manager/interface/baseInterface.py +1 -0
- general_manager/interface/databaseBasedInterface.py +25 -78
- general_manager/interface/databaseInterface.py +49 -14
- general_manager/interface/models.py +88 -0
- general_manager/interface/readOnlyInterface.py +35 -28
- general_manager/manager/generalManager.py +73 -14
- general_manager/manager/input.py +9 -12
- general_manager/manager/meta.py +26 -12
- general_manager/utils/testing.py +124 -0
- {generalmanager-0.7.0.dist-info → generalmanager-0.9.0.dist-info}/METADATA +1 -1
- {generalmanager-0.7.0.dist-info → generalmanager-0.9.0.dist-info}/RECORD +18 -15
- {generalmanager-0.7.0.dist-info → generalmanager-0.9.0.dist-info}/WHEEL +0 -0
- {generalmanager-0.7.0.dist-info → generalmanager-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.7.0.dist-info → generalmanager-0.9.0.dist-info}/top_level.txt +0 -0
general_manager/api/graphql.py
CHANGED
@@ -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):
|
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
|
-
|
106
|
-
|
107
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
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
|
-
#
|
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
|
-
|
445
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
-
|
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
|
-
) ->
|
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
|
573
|
-
|
574
|
-
|
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
|
-
|
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
|
-
) ->
|
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
|
635
|
-
|
636
|
-
|
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
|
-
|
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
|
-
) ->
|
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
|
697
|
-
|
698
|
-
|
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__}",
|
general_manager/api/mutation.py
CHANGED
@@ -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
|
21
|
-
|
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):
|
general_manager/apps.py
CHANGED
@@ -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
|
-
|
32
|
+
Performs initialization tasks for the general_manager app when Django starts.
|
31
33
|
|
32
|
-
Sets up
|
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
|
-
|
44
|
+
@staticmethod
|
45
|
+
def handleReadOnlyInterface(
|
46
|
+
read_only_classes: list[Type[GeneralManager[Any, ReadOnlyInterface]]],
|
47
|
+
):
|
40
48
|
"""
|
41
|
-
|
49
|
+
Configures synchronization and schema validation for the provided read-only interface classes.
|
42
50
|
|
43
|
-
|
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
|
-
|
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
|
50
|
-
read_only_interface
|
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(
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
115
|
+
Initializes attributes and establishes dynamic relationships for GeneralManager classes.
|
94
116
|
|
95
|
-
For each pending
|
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
|
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
|
-
|
148
|
+
@staticmethod
|
149
|
+
def handleGraphQL(
|
150
|
+
pending_graphql_interfaces: list[Type[GeneralManager]],
|
151
|
+
):
|
129
152
|
"""
|
130
|
-
|
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
|
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
|
-
|
174
|
+
GeneralmanagerConfig.addGraphqlUrl(schema)
|
152
175
|
|
153
|
-
|
176
|
+
@staticmethod
|
177
|
+
def addGraphqlUrl(schema):
|
154
178
|
"""
|
155
|
-
|
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
|
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)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
def snake_to_pascal(s: str) -> str:
|
2
|
+
"""
|
3
|
+
Convert a snake_case string to PascalCase.
|
4
|
+
|
5
|
+
Parameters:
|
6
|
+
s (str): The input string in snake_case format.
|
7
|
+
|
8
|
+
Returns:
|
9
|
+
str: The converted string in PascalCase format.
|
10
|
+
"""
|
11
|
+
return "".join(p.title() for p in s.split("_"))
|
12
|
+
|
13
|
+
|
14
|
+
def snake_to_camel(s: str) -> str:
|
15
|
+
"""
|
16
|
+
Convert a snake_case string to camelCase.
|
17
|
+
|
18
|
+
Parameters:
|
19
|
+
s (str): The snake_case string to convert.
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
str: The input string converted to camelCase.
|
23
|
+
"""
|
24
|
+
parts = s.split("_")
|
25
|
+
return parts[0] + "".join(p.title() for p in parts[1:])
|
26
|
+
|
27
|
+
|
28
|
+
def pascal_to_snake(s: str) -> str:
|
29
|
+
"""
|
30
|
+
Convert a PascalCase string to snake_case.
|
31
|
+
|
32
|
+
Parameters:
|
33
|
+
s (str): The PascalCase string to convert.
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
str: The converted snake_case string.
|
37
|
+
"""
|
38
|
+
return "".join(["_" + c.lower() if c.isupper() else c for c in s]).lstrip("_")
|
39
|
+
|
40
|
+
|
41
|
+
def camel_to_snake(s: str) -> str:
|
42
|
+
"""
|
43
|
+
Convert a camelCase string to snake_case.
|
44
|
+
|
45
|
+
Parameters:
|
46
|
+
s (str): The camelCase string to convert.
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
str: The converted snake_case string. Returns an empty string if the input is empty.
|
50
|
+
"""
|
51
|
+
if not s:
|
52
|
+
return ""
|
53
|
+
parts = [s[0].lower()]
|
54
|
+
for c in s[1:]:
|
55
|
+
if c.isupper():
|
56
|
+
parts.append("_")
|
57
|
+
parts.append(c.lower())
|
58
|
+
else:
|
59
|
+
parts.append(c)
|
60
|
+
return "".join(parts)
|