GeneralManager 0.6.1__tar.gz → 0.7.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.
- {generalmanager-0.6.1 → generalmanager-0.7.0}/GeneralManager.egg-info/PKG-INFO +1 -1
- {generalmanager-0.6.1 → generalmanager-0.7.0}/GeneralManager.egg-info/SOURCES.txt +2 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/PKG-INFO +1 -1
- {generalmanager-0.6.1 → generalmanager-0.7.0}/pyproject.toml +1 -1
- generalmanager-0.7.0/src/general_manager/apps.py +170 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/cache/dependencyIndex.py +10 -2
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/interface/baseInterface.py +8 -9
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/interface/databaseBasedInterface.py +28 -13
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/interface/databaseInterface.py +58 -34
- generalmanager-0.7.0/src/general_manager/interface/readOnlyInterface.py +265 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/manager/meta.py +1 -1
- generalmanager-0.7.0/tests/test_databaseInterface.py +181 -0
- generalmanager-0.7.0/tests/test_readOnlyInterface.py +370 -0
- generalmanager-0.6.1/src/general_manager/apps.py +0 -83
- generalmanager-0.6.1/src/general_manager/interface/readOnlyInterface.py +0 -107
- {generalmanager-0.6.1 → generalmanager-0.7.0}/GeneralManager.egg-info/dependency_links.txt +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/GeneralManager.egg-info/requires.txt +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/GeneralManager.egg-info/top_level.txt +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/LICENSE +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/README.md +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/setup.cfg +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/__init__.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/api/graphql.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/api/mutation.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/api/property.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/auxiliary/__init__.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/auxiliary/argsToKwargs.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/auxiliary/filterParser.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/auxiliary/jsonEncoder.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/auxiliary/makeCacheKey.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/auxiliary/noneToZero.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/auxiliary/pathMapping.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/bucket/baseBucket.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/bucket/calculationBucket.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/bucket/databaseBucket.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/bucket/groupBucket.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/cache/cacheDecorator.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/cache/cacheTracker.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/cache/modelDependencyCollector.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/cache/signals.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/factory/__init__.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/factory/autoFactory.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/factory/factories.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/factory/factoryMethods.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/interface/__init__.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/interface/calculationInterface.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/manager/__init__.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/manager/generalManager.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/manager/groupManager.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/manager/input.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/measurement/__init__.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/measurement/measurement.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/measurement/measurementField.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/permission/__init__.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/permission/basePermission.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/permission/fileBasedPermission.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/permission/managerBasedPermission.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/permission/permissionChecks.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/permission/permissionDataManager.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/rule/__init__.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/rule/handler.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/rule/rule.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_argsToKwargs.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_autoFactory.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_baseBucket.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_baseInterface.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_basePermission.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_cacheDecorator.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_cacheTracker.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_calculationBucket.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_calculationInterface.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_databaseBasedInterface.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_databaseBucket.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_dependencyIndex.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_factories.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_factoryMethods.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_filterParser.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_generalManager.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_generalManagerMeta.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_graph_ql.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_groupManager.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_input.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_jsonEncoder.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_makeCacheKey.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_managerBasedPermission.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_measurement.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_measurement_field.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_modelDependencyCollector.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_noneToZero.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_rule_handler.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_rules.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_settings.py +0 -0
- {generalmanager-0.6.1 → generalmanager-0.7.0}/tests/test_signals.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: GeneralManager
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.7.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
|
@@ -65,6 +65,7 @@ tests/test_calculationBucket.py
|
|
65
65
|
tests/test_calculationInterface.py
|
66
66
|
tests/test_databaseBasedInterface.py
|
67
67
|
tests/test_databaseBucket.py
|
68
|
+
tests/test_databaseInterface.py
|
68
69
|
tests/test_dependencyIndex.py
|
69
70
|
tests/test_factories.py
|
70
71
|
tests/test_factoryMethods.py
|
@@ -81,6 +82,7 @@ tests/test_measurement.py
|
|
81
82
|
tests/test_measurement_field.py
|
82
83
|
tests/test_modelDependencyCollector.py
|
83
84
|
tests/test_noneToZero.py
|
85
|
+
tests/test_readOnlyInterface.py
|
84
86
|
tests/test_rule_handler.py
|
85
87
|
tests/test_rules.py
|
86
88
|
tests/test_settings.py
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: GeneralManager
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.7.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
|
+
version = "0.7.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" }]
|
@@ -0,0 +1,170 @@
|
|
1
|
+
from django.apps import AppConfig
|
2
|
+
import graphene
|
3
|
+
import os
|
4
|
+
from django.conf import settings
|
5
|
+
from django.urls import path
|
6
|
+
from graphene_django.views import GraphQLView
|
7
|
+
from importlib import import_module
|
8
|
+
from general_manager.manager.generalManager import GeneralManager
|
9
|
+
from general_manager.manager.meta import GeneralManagerMeta
|
10
|
+
from general_manager.manager.input import Input
|
11
|
+
from general_manager.api.property import graphQlProperty
|
12
|
+
from general_manager.api.graphql import GraphQL
|
13
|
+
from typing import TYPE_CHECKING, Type
|
14
|
+
from django.core.checks import register
|
15
|
+
import logging
|
16
|
+
|
17
|
+
|
18
|
+
if TYPE_CHECKING:
|
19
|
+
from general_manager.interface.readOnlyInterface import ReadOnlyInterface
|
20
|
+
|
21
|
+
logger = logging.getLogger(__name__)
|
22
|
+
|
23
|
+
|
24
|
+
class GeneralmanagerConfig(AppConfig):
|
25
|
+
default_auto_field = "django.db.models.BigAutoField"
|
26
|
+
name = "general_manager"
|
27
|
+
|
28
|
+
def ready(self):
|
29
|
+
"""
|
30
|
+
Initializes the general_manager app when Django starts.
|
31
|
+
|
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.
|
33
|
+
"""
|
34
|
+
self.handleReadOnlyInterface()
|
35
|
+
self.initializeGeneralManagerClasses()
|
36
|
+
if getattr(settings, "AUTOCREATE_GRAPHQL", False):
|
37
|
+
self.handleGraphQL()
|
38
|
+
|
39
|
+
def handleReadOnlyInterface(self):
|
40
|
+
"""
|
41
|
+
Sets up synchronization and schema validation for all registered read-only interfaces.
|
42
|
+
|
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.
|
44
|
+
"""
|
45
|
+
self.patchReadOnlyInterfaceSync(GeneralManagerMeta.read_only_classes)
|
46
|
+
from general_manager.interface.readOnlyInterface import ReadOnlyInterface
|
47
|
+
|
48
|
+
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
|
51
|
+
|
52
|
+
register(
|
53
|
+
lambda app_configs, model=read_only_interface._model, manager_class=general_manager_class, **kwargs: ReadOnlyInterface.ensureSchemaIsUpToDate(
|
54
|
+
manager_class, model
|
55
|
+
),
|
56
|
+
"general_manager",
|
57
|
+
)
|
58
|
+
|
59
|
+
@staticmethod
|
60
|
+
def patchReadOnlyInterfaceSync(general_manager_classes: list[Type[GeneralManager]]):
|
61
|
+
"""
|
62
|
+
Monkey-patches Django's management command runner to synchronize read-only interface data before executing commands.
|
63
|
+
|
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`.
|
65
|
+
"""
|
66
|
+
from django.core.management.base import BaseCommand
|
67
|
+
|
68
|
+
original_run_from_argv = BaseCommand.run_from_argv
|
69
|
+
|
70
|
+
def run_from_argv_with_sync(self, argv):
|
71
|
+
# Ensure syncData is only called at real run of runserver
|
72
|
+
"""
|
73
|
+
Runs the management command and synchronizes read-only interface data before execution when appropriate.
|
74
|
+
|
75
|
+
Synchronization occurs for all registered read-only interfaces unless the command is 'runserver' in an autoreload subprocess.
|
76
|
+
"""
|
77
|
+
run_main = os.environ.get("RUN_MAIN") == "true"
|
78
|
+
command = argv[1] if len(argv) > 1 else None
|
79
|
+
if command != "runserver" or run_main:
|
80
|
+
logger.debug("start syncing ReadOnlyInterface data...")
|
81
|
+
for general_manager_class in general_manager_classes:
|
82
|
+
read_only_interface: ReadOnlyInterface = general_manager_class.Interface # type: ignore
|
83
|
+
read_only_interface.syncData()
|
84
|
+
|
85
|
+
logger.debug("finished syncing ReadOnlyInterface data.")
|
86
|
+
|
87
|
+
return original_run_from_argv(self, argv)
|
88
|
+
|
89
|
+
BaseCommand.run_from_argv = run_from_argv_with_sync
|
90
|
+
|
91
|
+
def initializeGeneralManagerClasses(self):
|
92
|
+
"""
|
93
|
+
Initializes attributes and interconnections for all GeneralManager classes.
|
94
|
+
|
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.
|
96
|
+
"""
|
97
|
+
logger.debug("Initializing GeneralManager classes...")
|
98
|
+
|
99
|
+
logger.debug("starting to create attributes for GeneralManager classes...")
|
100
|
+
for (
|
101
|
+
general_manager_class
|
102
|
+
) in GeneralManagerMeta.pending_attribute_initialization:
|
103
|
+
attributes = general_manager_class.Interface.getAttributes()
|
104
|
+
setattr(general_manager_class, "_attributes", attributes)
|
105
|
+
GeneralManagerMeta.createAtPropertiesForAttributes(
|
106
|
+
attributes.keys(), general_manager_class
|
107
|
+
)
|
108
|
+
|
109
|
+
logger.debug("starting to connect inputs to other general manager classes...")
|
110
|
+
for general_manager_class in GeneralManagerMeta.all_classes:
|
111
|
+
attributes = getattr(general_manager_class.Interface, "input_fields", {})
|
112
|
+
for attribute_name, attribute in attributes.items():
|
113
|
+
if isinstance(attribute, Input) and issubclass(
|
114
|
+
attribute.type, GeneralManager
|
115
|
+
):
|
116
|
+
connected_manager = attribute.type
|
117
|
+
func = lambda x, attribute_name=attribute_name: general_manager_class.filter(
|
118
|
+
**{attribute_name: x}
|
119
|
+
)
|
120
|
+
|
121
|
+
func.__annotations__ = {"return": general_manager_class}
|
122
|
+
setattr(
|
123
|
+
connected_manager,
|
124
|
+
f"{general_manager_class.__name__.lower()}_list",
|
125
|
+
graphQlProperty(func),
|
126
|
+
)
|
127
|
+
|
128
|
+
def handleGraphQL(self):
|
129
|
+
"""
|
130
|
+
Sets up GraphQL interfaces, mutations, and schema for all pending general manager classes, and adds the GraphQL endpoint to the Django URL configuration.
|
131
|
+
"""
|
132
|
+
logger.debug("Starting to create GraphQL interfaces and mutations...")
|
133
|
+
for general_manager_class in GeneralManagerMeta.pending_graphql_interfaces:
|
134
|
+
GraphQL.createGraphqlInterface(general_manager_class)
|
135
|
+
GraphQL.createGraphqlMutation(general_manager_class)
|
136
|
+
|
137
|
+
query_class = type("Query", (graphene.ObjectType,), GraphQL._query_fields)
|
138
|
+
GraphQL._query_class = query_class
|
139
|
+
|
140
|
+
mutation_class = type(
|
141
|
+
"Mutation",
|
142
|
+
(graphene.ObjectType,),
|
143
|
+
{name: mutation.Field() for name, mutation in GraphQL._mutations.items()},
|
144
|
+
)
|
145
|
+
GraphQL._mutation_class = mutation_class
|
146
|
+
|
147
|
+
schema = graphene.Schema(
|
148
|
+
query=GraphQL._query_class,
|
149
|
+
mutation=GraphQL._mutation_class,
|
150
|
+
)
|
151
|
+
self.addGraphqlUrl(schema)
|
152
|
+
|
153
|
+
def addGraphqlUrl(self, schema):
|
154
|
+
"""
|
155
|
+
Dynamically adds a GraphQL endpoint to the Django URL configuration using the provided schema.
|
156
|
+
|
157
|
+
Raises an exception if the ROOT_URLCONF setting is not defined.
|
158
|
+
"""
|
159
|
+
logging.debug("Adding GraphQL URL to Django settings...")
|
160
|
+
root_url_conf_path = getattr(settings, "ROOT_URLCONF", None)
|
161
|
+
graph_ql_url = getattr(settings, "GRAPHQL_URL", "graphql/")
|
162
|
+
if not root_url_conf_path:
|
163
|
+
raise Exception("ROOT_URLCONF not found in settings")
|
164
|
+
urlconf = import_module(root_url_conf_path)
|
165
|
+
urlconf.urlpatterns.append(
|
166
|
+
path(
|
167
|
+
graph_ql_url,
|
168
|
+
GraphQLView.as_view(graphiql=True, schema=schema),
|
169
|
+
)
|
170
|
+
)
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
import time
|
3
3
|
import ast
|
4
4
|
import re
|
5
|
+
import logging
|
5
6
|
|
6
7
|
from django.core.cache import cache
|
7
8
|
from general_manager.cache.signals import post_data_change, pre_data_change
|
@@ -27,6 +28,8 @@ type dependency_index = dict[
|
|
27
28
|
type filter_type = Literal["filter", "exclude", "identification"]
|
28
29
|
type Dependency = Tuple[general_manager_name, filter_type, str]
|
29
30
|
|
31
|
+
logger = logging.getLogger(__name__)
|
32
|
+
|
30
33
|
# -----------------------------------------------------------------------------
|
31
34
|
# CONFIG
|
32
35
|
# -----------------------------------------------------------------------------
|
@@ -181,6 +184,11 @@ def generic_cache_invalidation(
|
|
181
184
|
old_relevant_values: dict[str, Any],
|
182
185
|
**kwargs,
|
183
186
|
):
|
187
|
+
"""
|
188
|
+
Invalidates cached query results related to a model instance when its data changes.
|
189
|
+
|
190
|
+
This function is intended to be used as a Django signal handler. It compares old and new values of relevant fields on a model instance against registered cache dependencies (filters and excludes). If a change affects any cached queryset result, the corresponding cache keys are invalidated and removed from the dependency index.
|
191
|
+
"""
|
184
192
|
manager_name = sender.__name__
|
185
193
|
idx = get_full_index()
|
186
194
|
|
@@ -280,7 +288,7 @@ def generic_cache_invalidation(
|
|
280
288
|
if action == "filter":
|
281
289
|
# Filter: invalidate if new match or old match
|
282
290
|
if new_match or old_match:
|
283
|
-
|
291
|
+
logger.info(
|
284
292
|
f"Invalidate cache key {cache_keys} for filter {lookup} with value {val_key}"
|
285
293
|
)
|
286
294
|
for ck in list(cache_keys):
|
@@ -290,7 +298,7 @@ def generic_cache_invalidation(
|
|
290
298
|
else: # action == 'exclude'
|
291
299
|
# Excludes: invalidate only if matches changed
|
292
300
|
if old_match != new_match:
|
293
|
-
|
301
|
+
logger.info(
|
294
302
|
f"Invalidate cache key {cache_keys} for exclude {lookup} with value {val_key}"
|
295
303
|
)
|
296
304
|
for ck in list(cache_keys):
|
{generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/interface/baseInterface.py
RENAMED
@@ -18,7 +18,6 @@ from general_manager.auxiliary import args_to_kwargs
|
|
18
18
|
if TYPE_CHECKING:
|
19
19
|
from general_manager.manager.input import Input
|
20
20
|
from general_manager.manager.generalManager import GeneralManager
|
21
|
-
from general_manager.manager.meta import GeneralManagerMeta
|
22
21
|
from general_manager.bucket.baseBucket import Bucket
|
23
22
|
|
24
23
|
|
@@ -28,7 +27,7 @@ type attributes = dict[str, Any]
|
|
28
27
|
type interfaceBaseClass = Type[InterfaceBase]
|
29
28
|
type newlyCreatedInterfaceClass = Type[InterfaceBase]
|
30
29
|
type relatedClass = Type[Model] | None
|
31
|
-
type newlyCreatedGeneralManagerClass =
|
30
|
+
type newlyCreatedGeneralManagerClass = Type[GeneralManager]
|
32
31
|
|
33
32
|
type classPreCreationMethod = Callable[
|
34
33
|
[generalManagerClassName, attributes, interfaceBaseClass],
|
@@ -55,7 +54,7 @@ class AttributeTypedDict(TypedDict):
|
|
55
54
|
|
56
55
|
|
57
56
|
class InterfaceBase(ABC):
|
58
|
-
_parent_class:
|
57
|
+
_parent_class: Type[GeneralManager]
|
59
58
|
_interface_type: ClassVar[str]
|
60
59
|
input_fields: dict[str, Input]
|
61
60
|
|
@@ -70,9 +69,9 @@ class InterfaceBase(ABC):
|
|
70
69
|
) -> dict[str, Any]:
|
71
70
|
"""
|
72
71
|
Parses and validates input arguments into a structured identification dictionary.
|
73
|
-
|
72
|
+
|
74
73
|
Converts positional and keyword arguments into a dictionary keyed by input field names, normalizing argument names and ensuring all required fields are present. Processes input fields in dependency order, casting and validating each value. Raises a `TypeError` for unexpected or missing arguments and a `ValueError` if circular dependencies among input fields are detected.
|
75
|
-
|
74
|
+
|
76
75
|
Returns:
|
77
76
|
A dictionary mapping input field names to their validated and cast values.
|
78
77
|
"""
|
@@ -131,7 +130,7 @@ class InterfaceBase(ABC):
|
|
131
130
|
) -> None:
|
132
131
|
"""
|
133
132
|
Validates the type and allowed values of an input field.
|
134
|
-
|
133
|
+
|
135
134
|
Ensures that the provided value matches the expected type for the specified input field. In debug mode, also checks that the value is among the allowed possible values if defined, supporting both callables and iterables. Raises a TypeError for invalid types or possible value definitions, and a ValueError if the value is not permitted.
|
136
135
|
"""
|
137
136
|
input_field = self.input_fields[name]
|
@@ -218,13 +217,13 @@ class InterfaceBase(ABC):
|
|
218
217
|
def getFieldType(cls, field_name: str) -> type:
|
219
218
|
"""
|
220
219
|
Returns the type of the specified input field.
|
221
|
-
|
220
|
+
|
222
221
|
Args:
|
223
222
|
field_name: The name of the input field.
|
224
|
-
|
223
|
+
|
225
224
|
Returns:
|
226
225
|
The Python type associated with the given field name.
|
227
|
-
|
226
|
+
|
228
227
|
Raises:
|
229
228
|
NotImplementedError: This method must be implemented by subclasses.
|
230
229
|
"""
|
@@ -66,12 +66,18 @@ def getFullCleanMethode(model: Type[models.Model]) -> Callable[..., None]:
|
|
66
66
|
return full_clean
|
67
67
|
|
68
68
|
|
69
|
-
class
|
69
|
+
class GeneralManagerBasisModel(models.Model):
|
70
70
|
_general_manager_class: ClassVar[Type[GeneralManager]]
|
71
71
|
is_active = models.BooleanField(default=True)
|
72
|
+
history = HistoricalRecords(inherit=True)
|
73
|
+
|
74
|
+
class Meta:
|
75
|
+
abstract = True
|
76
|
+
|
77
|
+
|
78
|
+
class GeneralManagerModel(GeneralManagerBasisModel):
|
72
79
|
changed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
73
80
|
changed_by_id: int
|
74
|
-
history = HistoricalRecords(inherit=True)
|
75
81
|
|
76
82
|
@property
|
77
83
|
def _history_user(self) -> AbstractUser:
|
@@ -90,12 +96,12 @@ class GeneralManagerModel(models.Model):
|
|
90
96
|
"""
|
91
97
|
self.changed_by = value
|
92
98
|
|
93
|
-
class Meta:
|
99
|
+
class Meta: # type: ignore
|
94
100
|
abstract = True
|
95
101
|
|
96
102
|
|
97
103
|
class DBBasedInterface(InterfaceBase):
|
98
|
-
_model:
|
104
|
+
_model: Type[GeneralManagerBasisModel]
|
99
105
|
input_fields: dict[str, Input] = {"id": Input(int)}
|
100
106
|
|
101
107
|
def __init__(
|
@@ -113,7 +119,7 @@ class DBBasedInterface(InterfaceBase):
|
|
113
119
|
self.pk = self.identification["id"]
|
114
120
|
self._instance = self.getData(search_date)
|
115
121
|
|
116
|
-
def getData(self, search_date: datetime | None = None) ->
|
122
|
+
def getData(self, search_date: datetime | None = None) -> GeneralManagerBasisModel:
|
117
123
|
"""
|
118
124
|
Retrieves the model instance by primary key, optionally as of a specified historical date.
|
119
125
|
|
@@ -177,8 +183,8 @@ class DBBasedInterface(InterfaceBase):
|
|
177
183
|
|
178
184
|
@classmethod
|
179
185
|
def getHistoricalRecord(
|
180
|
-
cls, instance:
|
181
|
-
) ->
|
186
|
+
cls, instance: GeneralManagerBasisModel, search_date: datetime | None = None
|
187
|
+
) -> GeneralManagerBasisModel:
|
182
188
|
"""
|
183
189
|
Retrieves the most recent historical record of a model instance at or before a specified date.
|
184
190
|
|
@@ -440,14 +446,23 @@ class DBBasedInterface(InterfaceBase):
|
|
440
446
|
|
441
447
|
@staticmethod
|
442
448
|
def _preCreate(
|
443
|
-
name: generalManagerClassName,
|
449
|
+
name: generalManagerClassName,
|
450
|
+
attrs: attributes,
|
451
|
+
interface: interfaceBaseClass,
|
452
|
+
base_model_class=GeneralManagerModel,
|
444
453
|
) -> tuple[attributes, interfaceBaseClass, relatedClass]:
|
445
454
|
# Felder aus der Interface-Klasse sammeln
|
446
455
|
"""
|
447
|
-
Dynamically creates a Django model, its associated interface class, and a factory class based on the provided interface definition.
|
448
|
-
|
449
|
-
This method extracts fields and meta information from the interface class, constructs a new Django model inheriting from
|
450
|
-
|
456
|
+
Dynamically creates a Django model class, its associated interface class, and a factory class based on the provided interface definition.
|
457
|
+
|
458
|
+
This method extracts fields and meta information from the interface class, constructs a new Django model inheriting from the specified base model class, attaches custom validation rules if present, and generates corresponding interface and factory classes. The resulting classes are returned for integration into the general manager framework.
|
459
|
+
|
460
|
+
Parameters:
|
461
|
+
name: The name for the dynamically created model class.
|
462
|
+
attrs: The attributes dictionary to be updated with the new interface and factory classes.
|
463
|
+
interface: The interface base class defining the model structure and metadata.
|
464
|
+
base_model_class: The base class to use for the new model (defaults to GeneralManagerModel).
|
465
|
+
|
451
466
|
Returns:
|
452
467
|
A tuple containing the updated attributes dictionary, the new interface class, and the newly created model class.
|
453
468
|
"""
|
@@ -474,7 +489,7 @@ class DBBasedInterface(InterfaceBase):
|
|
474
489
|
delattr(meta_class, "rules")
|
475
490
|
|
476
491
|
# Modell erstellen
|
477
|
-
model = type(name, (
|
492
|
+
model = type(name, (base_model_class,), model_fields)
|
478
493
|
if meta_class and rules:
|
479
494
|
setattr(model._meta, "rules", rules)
|
480
495
|
# full_clean Methode hinzufügen
|
{generalmanager-0.6.1 → generalmanager-0.7.0}/src/general_manager/interface/databaseInterface.py
RENAMED
@@ -18,36 +18,24 @@ class DatabaseInterface(DBBasedInterface):
|
|
18
18
|
def create(
|
19
19
|
cls, creator_id: int, history_comment: str | None = None, **kwargs: Any
|
20
20
|
) -> int:
|
21
|
-
from general_manager.manager.generalManager import GeneralManager
|
22
21
|
|
23
|
-
cls.
|
24
|
-
kwargs, many_to_many_kwargs = cls.
|
25
|
-
instance = cls._model()
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
key = f"{key}_id"
|
30
|
-
setattr(instance, key, value)
|
31
|
-
for key, value in many_to_many_kwargs.items():
|
32
|
-
getattr(instance, key).set(value)
|
33
|
-
return cls.__save_with_history(instance, creator_id, history_comment)
|
22
|
+
cls._checkForInvalidKwargs(cls._model, kwargs=kwargs)
|
23
|
+
kwargs, many_to_many_kwargs = cls._sortKwargs(cls._model, kwargs)
|
24
|
+
instance = cls.__setAttrForWrite(cls._model(), kwargs)
|
25
|
+
pk = cls._save_with_history(instance, creator_id, history_comment)
|
26
|
+
cls.__setManyToManyAttributes(instance, many_to_many_kwargs)
|
27
|
+
return pk
|
34
28
|
|
35
29
|
def update(
|
36
30
|
self, creator_id: int, history_comment: str | None = None, **kwargs: Any
|
37
31
|
) -> int:
|
38
|
-
from general_manager.manager.generalManager import GeneralManager
|
39
32
|
|
40
|
-
self.
|
41
|
-
kwargs, many_to_many_kwargs = self.
|
42
|
-
instance = self._model.objects.get(pk=self.pk)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
key = f"{key}_id"
|
47
|
-
setattr(instance, key, value)
|
48
|
-
for key, value in many_to_many_kwargs.items():
|
49
|
-
getattr(instance, key).set(value)
|
50
|
-
return self.__save_with_history(instance, creator_id, history_comment)
|
33
|
+
self._checkForInvalidKwargs(self._model, kwargs=kwargs)
|
34
|
+
kwargs, many_to_many_kwargs = self._sortKwargs(self._model, kwargs)
|
35
|
+
instance = self.__setAttrForWrite(self._model.objects.get(pk=self.pk), kwargs)
|
36
|
+
pk = self._save_with_history(instance, creator_id, history_comment)
|
37
|
+
self.__setManyToManyAttributes(instance, many_to_many_kwargs)
|
38
|
+
return pk
|
51
39
|
|
52
40
|
def deactivate(self, creator_id: int, history_comment: str | None = None) -> int:
|
53
41
|
instance = self._model.objects.get(pk=self.pk)
|
@@ -56,32 +44,68 @@ class DatabaseInterface(DBBasedInterface):
|
|
56
44
|
history_comment = f"{history_comment} (deactivated)"
|
57
45
|
else:
|
58
46
|
history_comment = "Deactivated"
|
59
|
-
return self.
|
47
|
+
return self._save_with_history(instance, creator_id, history_comment)
|
60
48
|
|
61
49
|
@staticmethod
|
62
|
-
def
|
50
|
+
def __setManyToManyAttributes(
|
51
|
+
instance: GeneralManagerModel, many_to_many_kwargs: dict[str, list[Any]]
|
52
|
+
) -> GeneralManagerModel:
|
53
|
+
"""
|
54
|
+
Sets many-to-many attributes for the given instance based on the provided kwargs.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
instance: The model instance to update.
|
58
|
+
many_to_many_kwargs: A dictionary containing many-to-many field names and their corresponding values.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
The updated model instance.
|
62
|
+
"""
|
63
|
+
for key, value in many_to_many_kwargs.items():
|
64
|
+
if not value:
|
65
|
+
continue
|
66
|
+
field_name = key.split("_id_list")[0]
|
67
|
+
getattr(instance, field_name).set(value)
|
68
|
+
|
69
|
+
return instance
|
70
|
+
|
71
|
+
@staticmethod
|
72
|
+
def __setAttrForWrite(
|
73
|
+
instance: GeneralManagerModel,
|
74
|
+
kwargs: dict[str, Any],
|
75
|
+
) -> GeneralManagerModel:
|
76
|
+
from general_manager.manager.generalManager import GeneralManager
|
77
|
+
|
78
|
+
for key, value in kwargs.items():
|
79
|
+
if isinstance(value, GeneralManager):
|
80
|
+
value = value.identification["id"]
|
81
|
+
key = f"{key}_id"
|
82
|
+
setattr(instance, key, value)
|
83
|
+
return instance
|
84
|
+
|
85
|
+
@staticmethod
|
86
|
+
def _checkForInvalidKwargs(model: Type[models.Model], kwargs: dict[str, Any]):
|
63
87
|
attributes = vars(model)
|
64
|
-
|
88
|
+
field_names = {f.name for f in model._meta.get_fields()}
|
65
89
|
for key in kwargs:
|
66
|
-
|
90
|
+
temp_key = key.split("_id_list")[0] # Remove '_id_list' suffix
|
91
|
+
if temp_key not in attributes and temp_key not in field_names:
|
67
92
|
raise ValueError(f"{key} does not exsist in {model.__name__}")
|
68
93
|
|
69
94
|
@staticmethod
|
70
|
-
def
|
95
|
+
def _sortKwargs(
|
71
96
|
model: Type[models.Model], kwargs: dict[Any, Any]
|
72
97
|
) -> tuple[dict[str, Any], dict[str, list[Any]]]:
|
73
|
-
many_to_many_fields = model._meta.many_to_many
|
98
|
+
many_to_many_fields = [field.name for field in model._meta.many_to_many]
|
74
99
|
many_to_many_kwargs: dict[Any, Any] = {}
|
75
|
-
for key, value in kwargs.items():
|
100
|
+
for key, value in list(kwargs.items()):
|
76
101
|
many_to_many_key = key.split("_id_list")[0]
|
77
102
|
if many_to_many_key in many_to_many_fields:
|
78
|
-
many_to_many_kwargs[key] =
|
79
|
-
kwargs.pop(key)
|
103
|
+
many_to_many_kwargs[key] = kwargs.pop(key)
|
80
104
|
return kwargs, many_to_many_kwargs
|
81
105
|
|
82
106
|
@classmethod
|
83
107
|
@transaction.atomic
|
84
|
-
def
|
108
|
+
def _save_with_history(
|
85
109
|
cls, instance: GeneralManagerModel, creator_id: int, history_comment: str | None
|
86
110
|
) -> int:
|
87
111
|
"""
|