GeneralManager 0.2.0__py3-none-any.whl → 0.3.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/auxiliary/jsonEncoder.py +19 -0
- general_manager/auxiliary/makeCacheKey.py +30 -0
- general_manager/cache/cacheDecorator.py +25 -52
- general_manager/cache/cacheTracker.py +21 -20
- general_manager/cache/dependencyIndex.py +3 -1
- general_manager/cache/modelDependencyCollector.py +51 -0
- general_manager/manager/generalManager.py +10 -4
- general_manager/rule/rule.py +1 -1
- {generalmanager-0.2.0.dist-info → generalmanager-0.3.0.dist-info}/METADATA +1 -1
- {generalmanager-0.2.0.dist-info → generalmanager-0.3.0.dist-info}/RECORD +13 -10
- {generalmanager-0.2.0.dist-info → generalmanager-0.3.0.dist-info}/WHEEL +1 -1
- {generalmanager-0.2.0.dist-info → generalmanager-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.2.0.dist-info → generalmanager-0.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
from datetime import datetime, date, time
|
2
|
+
import json
|
3
|
+
from general_manager.manager.generalManager import GeneralManager
|
4
|
+
|
5
|
+
|
6
|
+
class CustomJSONEncoder(json.JSONEncoder):
|
7
|
+
def default(self, o):
|
8
|
+
|
9
|
+
# Serialize datetime objects as ISO strings
|
10
|
+
if isinstance(o, (datetime, date, time)):
|
11
|
+
return o.isoformat()
|
12
|
+
# Handle GeneralManager instances
|
13
|
+
if isinstance(o, GeneralManager):
|
14
|
+
return f"{o.__class__.__name__}(**{o.identification})"
|
15
|
+
try:
|
16
|
+
return super().default(o)
|
17
|
+
except TypeError:
|
18
|
+
# Fallback: convert all other objects to str
|
19
|
+
return str(o)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import inspect
|
2
|
+
import json
|
3
|
+
from general_manager.auxiliary.jsonEncoder import CustomJSONEncoder
|
4
|
+
from hashlib import sha256
|
5
|
+
|
6
|
+
|
7
|
+
def make_cache_key(func, args, kwargs):
|
8
|
+
"""
|
9
|
+
Generate a deterministic cache key for a function call.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
func: The function being called
|
13
|
+
args: Positional arguments to the function
|
14
|
+
kwargs: Keyword arguments to the function
|
15
|
+
|
16
|
+
Returns:
|
17
|
+
str: A hexadecimal SHA-256 hash that uniquely identifies this function call
|
18
|
+
"""
|
19
|
+
sig = inspect.signature(func)
|
20
|
+
bound = sig.bind_partial(*args, **kwargs)
|
21
|
+
bound.apply_defaults()
|
22
|
+
payload = {
|
23
|
+
"module": func.__module__,
|
24
|
+
"name": func.__name__,
|
25
|
+
"args": bound.arguments,
|
26
|
+
}
|
27
|
+
raw = json.dumps(
|
28
|
+
payload, sort_keys=True, default=str, cls=CustomJSONEncoder
|
29
|
+
).encode()
|
30
|
+
return sha256(raw, usedforsecurity=False).hexdigest()
|
@@ -1,70 +1,43 @@
|
|
1
|
-
from typing import Callable, Optional
|
1
|
+
from typing import Any, Callable, Optional, Protocol, Set
|
2
2
|
from functools import wraps
|
3
3
|
from django.core.cache import cache as django_cache
|
4
|
-
from hashlib import sha256
|
5
4
|
from general_manager.cache.cacheTracker import DependencyTracker
|
6
|
-
from general_manager.cache.dependencyIndex import record_dependencies,
|
5
|
+
from general_manager.cache.dependencyIndex import record_dependencies, Dependency
|
6
|
+
from general_manager.cache.modelDependencyCollector import ModelDependencyCollector
|
7
|
+
from general_manager.auxiliary.makeCacheKey import make_cache_key
|
7
8
|
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
If no timeout is provided, the cache will expire when a dependency is invalidated.
|
13
|
-
"""
|
10
|
+
class CacheBackend(Protocol):
|
11
|
+
def get(self, key: str) -> Any: ...
|
12
|
+
def set(self, key: str, value: Any, timeout: Optional[int] = None) -> None: ...
|
14
13
|
|
15
|
-
def decorator(func: Callable) -> Callable:
|
16
14
|
|
15
|
+
RecordFn = Callable[[str, Set[Dependency]], None]
|
16
|
+
|
17
|
+
|
18
|
+
def cached(
|
19
|
+
timeout: Optional[int] = None,
|
20
|
+
cache_backend: CacheBackend = django_cache,
|
21
|
+
record_fn: RecordFn = record_dependencies,
|
22
|
+
) -> Callable:
|
23
|
+
def decorator(func: Callable) -> Callable:
|
17
24
|
@wraps(func)
|
18
25
|
def wrapper(*args, **kwargs):
|
19
|
-
|
26
|
+
key = make_cache_key(func, args, kwargs)
|
27
|
+
|
28
|
+
result = cache_backend.get(key)
|
29
|
+
if result is not None:
|
30
|
+
return result
|
20
31
|
|
21
|
-
django_cache_key = sha256(
|
22
|
-
f"{func.__module__}.{func.__name__}:{args}:{kwargs}".encode(),
|
23
|
-
usedforsecurity=False,
|
24
|
-
).hexdigest()
|
25
|
-
cached_result = django_cache.get(django_cache_key)
|
26
|
-
if cached_result is not None:
|
27
|
-
return cached_result
|
28
|
-
# Dependency Tracking aktivieren
|
29
32
|
with DependencyTracker() as dependencies:
|
30
33
|
result = func(*args, **kwargs)
|
34
|
+
ModelDependencyCollector.addArgs(dependencies, args, kwargs)
|
31
35
|
|
32
|
-
|
33
|
-
"""Rekursiv Django-Model-Instanzen im Objekt finden."""
|
34
|
-
if isinstance(obj, GeneralManager):
|
35
|
-
yield (
|
36
|
-
obj.__class__.__name__,
|
37
|
-
"identification",
|
38
|
-
f"{obj.identification}",
|
39
|
-
)
|
40
|
-
elif isinstance(obj, Bucket):
|
41
|
-
yield (obj._manager_class.__name__, "filter", f"{obj.filters}")
|
42
|
-
yield (obj._manager_class.__name__, "exclude", f"{obj.excludes}")
|
43
|
-
elif isinstance(obj, dict):
|
44
|
-
for v in obj.values():
|
45
|
-
yield from collect_model_dependencies(v)
|
46
|
-
elif isinstance(obj, (list, tuple, set)):
|
47
|
-
for item in obj:
|
48
|
-
yield from collect_model_dependencies(item)
|
49
|
-
|
50
|
-
if args and isinstance(args[0], GeneralManager):
|
51
|
-
self = args[0]
|
52
|
-
for attr_val in self.__dict__.values():
|
53
|
-
for dependency_tuple in collect_model_dependencies(attr_val):
|
54
|
-
dependencies.add(dependency_tuple)
|
55
|
-
|
56
|
-
for dependency_tuple in collect_model_dependencies(args):
|
57
|
-
dependencies.add(dependency_tuple)
|
58
|
-
for dependency_tuple in collect_model_dependencies(kwargs):
|
59
|
-
dependencies.add(dependency_tuple)
|
36
|
+
cache_backend.set(key, result, timeout)
|
60
37
|
|
61
|
-
|
38
|
+
if dependencies and timeout is None:
|
39
|
+
record_fn(key, dependencies)
|
62
40
|
|
63
|
-
if dependencies and not timeout:
|
64
|
-
record_dependencies(
|
65
|
-
django_cache_key,
|
66
|
-
dependencies,
|
67
|
-
)
|
68
41
|
return result
|
69
42
|
|
70
43
|
return wrapper
|
@@ -1,7 +1,9 @@
|
|
1
1
|
import threading
|
2
|
-
from general_manager.cache.dependencyIndex import
|
3
|
-
|
4
|
-
|
2
|
+
from general_manager.cache.dependencyIndex import (
|
3
|
+
general_manager_name,
|
4
|
+
Dependency,
|
5
|
+
filter_type,
|
6
|
+
)
|
5
7
|
|
6
8
|
# Thread-lokale Variable zur Speicherung der Abhängigkeiten
|
7
9
|
_dependency_storage = threading.local()
|
@@ -10,24 +12,23 @@ _dependency_storage = threading.local()
|
|
10
12
|
class DependencyTracker:
|
11
13
|
def __enter__(
|
12
14
|
self,
|
13
|
-
) -> set[
|
14
|
-
tuple[general_manager_name, Literal["filter", "exclude", "identification"], str]
|
15
|
-
]:
|
15
|
+
) -> set[Dependency]:
|
16
16
|
_dependency_storage.dependencies = set()
|
17
17
|
return _dependency_storage.dependencies
|
18
18
|
|
19
19
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
20
|
+
if hasattr(_dependency_storage, "dependencies"):
|
21
|
+
del _dependency_storage.dependencies
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def track(
|
25
|
+
class_name: general_manager_name,
|
26
|
+
operation: filter_type,
|
27
|
+
identifier: str,
|
28
|
+
) -> None:
|
29
|
+
"""
|
30
|
+
Adds a dependency to the dependency storage.
|
31
|
+
"""
|
32
|
+
if hasattr(_dependency_storage, "dependencies"):
|
33
|
+
dependencies: set[Dependency] = _dependency_storage.dependencies
|
34
|
+
dependencies.add((class_name, operation, identifier))
|
@@ -6,7 +6,7 @@ import re
|
|
6
6
|
from django.core.cache import cache
|
7
7
|
from general_manager.cache.signals import post_data_change, pre_data_change
|
8
8
|
from django.dispatch import receiver
|
9
|
-
from typing import Literal, Any, Iterable, TYPE_CHECKING, Type
|
9
|
+
from typing import Literal, Any, Iterable, TYPE_CHECKING, Type, Tuple
|
10
10
|
|
11
11
|
if TYPE_CHECKING:
|
12
12
|
from general_manager.manager.generalManager import GeneralManager
|
@@ -24,6 +24,8 @@ type dependency_index = dict[
|
|
24
24
|
],
|
25
25
|
]
|
26
26
|
|
27
|
+
type filter_type = Literal["filter", "exclude", "identification"]
|
28
|
+
type Dependency = Tuple[general_manager_name, filter_type, str]
|
27
29
|
|
28
30
|
# -----------------------------------------------------------------------------
|
29
31
|
# CONFIG
|
@@ -0,0 +1,51 @@
|
|
1
|
+
from typing import Generator
|
2
|
+
from general_manager.manager.generalManager import GeneralManager, Bucket
|
3
|
+
from general_manager.cache.dependencyIndex import (
|
4
|
+
general_manager_name,
|
5
|
+
Dependency,
|
6
|
+
filter_type,
|
7
|
+
)
|
8
|
+
|
9
|
+
|
10
|
+
class ModelDependencyCollector:
|
11
|
+
|
12
|
+
def __init__(self, dependencies: set[Dependency]):
|
13
|
+
"""
|
14
|
+
Initialize the ModelDependencyCollector with a set of dependencies.
|
15
|
+
"""
|
16
|
+
self.dependencies = dependencies
|
17
|
+
|
18
|
+
@staticmethod
|
19
|
+
def collect(obj) -> Generator[tuple[general_manager_name, filter_type, str]]:
|
20
|
+
"""Recursively find Django model instances in the object."""
|
21
|
+
if isinstance(obj, GeneralManager):
|
22
|
+
yield (
|
23
|
+
obj.__class__.__name__,
|
24
|
+
"identification",
|
25
|
+
f"{obj.identification}",
|
26
|
+
)
|
27
|
+
elif isinstance(obj, Bucket):
|
28
|
+
yield (obj._manager_class.__name__, "filter", f"{obj.filters}")
|
29
|
+
yield (obj._manager_class.__name__, "exclude", f"{obj.excludes}")
|
30
|
+
elif isinstance(obj, dict):
|
31
|
+
for v in obj.values():
|
32
|
+
yield from ModelDependencyCollector.collect(v)
|
33
|
+
elif isinstance(obj, (list, tuple, set)):
|
34
|
+
for item in obj:
|
35
|
+
yield from ModelDependencyCollector.collect(item)
|
36
|
+
|
37
|
+
@staticmethod
|
38
|
+
def addArgs(dependencies: set[Dependency], args: tuple, kwargs: dict) -> None:
|
39
|
+
"""
|
40
|
+
Add dependencies to the dependency set.
|
41
|
+
"""
|
42
|
+
if args and isinstance(args[0], GeneralManager):
|
43
|
+
inner_self = args[0]
|
44
|
+
for attr_val in inner_self.__dict__.values():
|
45
|
+
for dependency_tuple in ModelDependencyCollector.collect(attr_val):
|
46
|
+
dependencies.add(dependency_tuple)
|
47
|
+
|
48
|
+
for dependency_tuple in ModelDependencyCollector.collect(args):
|
49
|
+
dependencies.add(dependency_tuple)
|
50
|
+
for dependency_tuple in ModelDependencyCollector.collect(kwargs):
|
51
|
+
dependencies.add(dependency_tuple)
|
@@ -7,7 +7,7 @@ from general_manager.interface.baseInterface import (
|
|
7
7
|
GeneralManagerType,
|
8
8
|
)
|
9
9
|
from general_manager.api.property import GraphQLProperty
|
10
|
-
from general_manager.cache.cacheTracker import
|
10
|
+
from general_manager.cache.cacheTracker import DependencyTracker
|
11
11
|
from general_manager.cache.signals import dataChange
|
12
12
|
|
13
13
|
if TYPE_CHECKING:
|
@@ -21,7 +21,9 @@ class GeneralManager(Generic[GeneralManagerType], metaclass=GeneralManagerMeta):
|
|
21
21
|
def __init__(self, *args: Any, **kwargs: Any):
|
22
22
|
self._interface = self.Interface(*args, **kwargs)
|
23
23
|
self.__id: dict[str, Any] = self._interface.identification
|
24
|
-
|
24
|
+
DependencyTracker.track(
|
25
|
+
self.__class__.__name__, "identification", f"{self.__id}"
|
26
|
+
)
|
25
27
|
|
26
28
|
def __str__(self):
|
27
29
|
return f"{self.__class__.__name__}(**{self.__id})"
|
@@ -108,12 +110,16 @@ class GeneralManager(Generic[GeneralManagerType], metaclass=GeneralManagerMeta):
|
|
108
110
|
|
109
111
|
@classmethod
|
110
112
|
def filter(cls, **kwargs: Any) -> Bucket[GeneralManagerType]:
|
111
|
-
|
113
|
+
DependencyTracker.track(
|
114
|
+
cls.__name__, "filter", f"{cls.__parse_identification(kwargs)}"
|
115
|
+
)
|
112
116
|
return cls.Interface.filter(**kwargs)
|
113
117
|
|
114
118
|
@classmethod
|
115
119
|
def exclude(cls, **kwargs: Any) -> Bucket[GeneralManagerType]:
|
116
|
-
|
120
|
+
DependencyTracker.track(
|
121
|
+
cls.__name__, "exclude", f"{cls.__parse_identification(kwargs)}"
|
122
|
+
)
|
117
123
|
return cls.Interface.exclude(**kwargs)
|
118
124
|
|
119
125
|
@classmethod
|
general_manager/rule/rule.py
CHANGED
@@ -81,7 +81,7 @@ class Rule(Generic[GeneralManagerType]):
|
|
81
81
|
inst = cls()
|
82
82
|
self._handlers[inst.function_name] = inst
|
83
83
|
for path in getattr(settings, "RULE_HANDLERS", []):
|
84
|
-
handler_cls = import_string(path)
|
84
|
+
handler_cls: type[BaseRuleHandler] = import_string(path)
|
85
85
|
inst = handler_cls()
|
86
86
|
self._handlers[inst.function_name] = inst
|
87
87
|
|
@@ -6,10 +6,13 @@ general_manager/api/property.py,sha256=oc93p1P8dcIvrNorRuqD1EJVsd6eYttYhZuAS0s28
|
|
6
6
|
general_manager/auxiliary/__init__.py,sha256=4IwKJzsNxGduF-Ej0u1BNHVaMhkql8PjHbVtx9DOTSY,76
|
7
7
|
general_manager/auxiliary/argsToKwargs.py,sha256=kmp1xonpQp4X_y8ZJG6c5uOW7zQwo0HtPqsHWVzXRSM,921
|
8
8
|
general_manager/auxiliary/filterParser.py,sha256=wmR4YzsnYgjI2Co5eyvCFROldotAraHx_GiCDJo79IY,5410
|
9
|
+
general_manager/auxiliary/jsonEncoder.py,sha256=TDsgFQvheITHZgdmn-m8tk1_QCzpT0XwEHo7bY3Qe-M,638
|
10
|
+
general_manager/auxiliary/makeCacheKey.py,sha256=o2ZPe5ZjiZhHxYgSESBSMnOmQutkTxw5DLeJM7tOw84,881
|
9
11
|
general_manager/auxiliary/noneToZero.py,sha256=KfQtMQnrT6vsYST0K7lv6pVujkDcK3XL8czHYOhgqKQ,539
|
10
|
-
general_manager/cache/cacheDecorator.py,sha256=
|
11
|
-
general_manager/cache/cacheTracker.py,sha256=
|
12
|
-
general_manager/cache/dependencyIndex.py,sha256=
|
12
|
+
general_manager/cache/cacheDecorator.py,sha256=7DbPMw-2Xm1-5eCMVWyjUl_jdVtTJUKNpLW2WNJEhI0,1471
|
13
|
+
general_manager/cache/cacheTracker.py,sha256=RmUWwAXMS5LQT8-w7IiG67FTBSxF1V0w4IDH1WzgmuM,998
|
14
|
+
general_manager/cache/dependencyIndex.py,sha256=iuOjthmH5ehHCkWiM9iLbgzGlo4Cf2wRKkm-QhIQ024,10813
|
15
|
+
general_manager/cache/modelDependencyCollector.py,sha256=Lt8mNpnp-AJaPCYrTu_UB1m_Hj1prfEXaPrxRwk3Qqs,1983
|
13
16
|
general_manager/cache/pathMapping.py,sha256=WtECIek9fI-2_nqIYI4Ux9Lan6g8P9TMO_AfthkznX8,5656
|
14
17
|
general_manager/cache/signals.py,sha256=ZHeXKFMN7tj9t0J-vSqf_05_NhGqEF2sZtbZO3vaRqI,1234
|
15
18
|
general_manager/factory/__init__.py,sha256=DLSQbpSBpujPtDSZcruPc43OLWzKCCtf20gbalCDYRU,91
|
@@ -20,7 +23,7 @@ general_manager/interface/baseInterface.py,sha256=mvSKUlA-0fazNnaIXGBwkiZxmX8DM_
|
|
20
23
|
general_manager/interface/calculationInterface.py,sha256=GzSNXjU6Z7bFz60gHyMKkI5xNUDIPuniV8wbyVtQT50,14250
|
21
24
|
general_manager/interface/databaseInterface.py,sha256=i3Z-rkHnoGzV8cFZjBKBmIjlWjGJ2CzdlsiQfL2JUas,28288
|
22
25
|
general_manager/manager/__init__.py,sha256=l3RYp62aEhj3Y975_XUTIzo35LUnkTJHkb_hgChnXXI,111
|
23
|
-
general_manager/manager/generalManager.py,sha256=
|
26
|
+
general_manager/manager/generalManager.py,sha256=md-3zVCOMa-vC8ToCOEMKXeDuV7JtHLlSStQXh4cgBE,5258
|
24
27
|
general_manager/manager/groupManager.py,sha256=O4FABqbm7KlZw6t36Ot3HU1FsBYN0h6Zhmk7ktN8a-Y,10087
|
25
28
|
general_manager/manager/input.py,sha256=iKawV3P1QICz-0AQUF00OvH7LZYxussg3svpvCUl8hE,2977
|
26
29
|
general_manager/manager/meta.py,sha256=5wHrCVnua5c38vpVZSCesrNvgydQDH8h6pxW6_QgCDg,3107
|
@@ -35,9 +38,9 @@ general_manager/permission/permissionChecks.py,sha256=T-9khBqiwM4ASBdey9p07sC_xg
|
|
35
38
|
general_manager/permission/permissionDataManager.py,sha256=Ji7fsnuaKTa6M8yzCGyzrIHyGa_ZvqJM7sXR97-uTrA,1937
|
36
39
|
general_manager/rule/__init__.py,sha256=4Har5cfPD1fmOsilTDod-ZUz3Com-tkl58jz7yY4fD0,23
|
37
40
|
general_manager/rule/handler.py,sha256=z8SFHTIZ0LbLh3fV56Mud0V4_OvWkqJjlHvFqau7Qfk,7334
|
38
|
-
general_manager/rule/rule.py,sha256=
|
39
|
-
generalmanager-0.
|
40
|
-
generalmanager-0.
|
41
|
-
generalmanager-0.
|
42
|
-
generalmanager-0.
|
43
|
-
generalmanager-0.
|
41
|
+
general_manager/rule/rule.py,sha256=3FVCKGL7BTVoStdgOTdWQwuoVRIxAIAilV4VOzouDpc,10759
|
42
|
+
generalmanager-0.3.0.dist-info/licenses/LICENSE,sha256=YGFm0ieb4KpkMRRt2qnWue6uFh0cUMtobwEBkHwajhc,1450
|
43
|
+
generalmanager-0.3.0.dist-info/METADATA,sha256=sKfEf7wuzdUhszsnlCJ49iydcFKidlwV58863yosGlw,8188
|
44
|
+
generalmanager-0.3.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
45
|
+
generalmanager-0.3.0.dist-info/top_level.txt,sha256=sTDtExP9ga-YP3h3h42yivUY-A2Q23C2nw6LNKOho4I,16
|
46
|
+
generalmanager-0.3.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|