GeneralManager 0.2.0__tar.gz → 0.3.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.2.0 → generalmanager-0.3.0}/GeneralManager.egg-info/PKG-INFO +1 -1
- {generalmanager-0.2.0 → generalmanager-0.3.0}/GeneralManager.egg-info/SOURCES.txt +4 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/PKG-INFO +1 -1
- {generalmanager-0.2.0 → generalmanager-0.3.0}/pyproject.toml +1 -1
- generalmanager-0.3.0/src/general_manager/auxiliary/jsonEncoder.py +19 -0
- generalmanager-0.3.0/src/general_manager/auxiliary/makeCacheKey.py +30 -0
- generalmanager-0.3.0/src/general_manager/cache/cacheDecorator.py +45 -0
- generalmanager-0.3.0/src/general_manager/cache/cacheTracker.py +34 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/cache/dependencyIndex.py +3 -1
- generalmanager-0.3.0/src/general_manager/cache/modelDependencyCollector.py +51 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/manager/generalManager.py +10 -4
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/rule/rule.py +1 -1
- generalmanager-0.3.0/tests/test_jsonEncoder.py +49 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_rule_handler.py +29 -29
- generalmanager-0.2.0/src/general_manager/cache/cacheDecorator.py +0 -72
- generalmanager-0.2.0/src/general_manager/cache/cacheTracker.py +0 -33
- {generalmanager-0.2.0 → generalmanager-0.3.0}/GeneralManager.egg-info/dependency_links.txt +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/GeneralManager.egg-info/requires.txt +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/GeneralManager.egg-info/top_level.txt +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/LICENSE +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/README.md +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/setup.cfg +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/__init__.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/api/graphql.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/api/mutation.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/api/property.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/apps.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/auxiliary/__init__.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/auxiliary/argsToKwargs.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/auxiliary/filterParser.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/auxiliary/noneToZero.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/cache/pathMapping.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/cache/signals.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/factory/__init__.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/factory/factories.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/factory/lazy_methods.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/interface/__init__.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/interface/baseInterface.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/interface/calculationInterface.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/interface/databaseInterface.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/manager/__init__.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/manager/groupManager.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/manager/input.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/manager/meta.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/measurement/__init__.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/measurement/measurement.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/measurement/measurementField.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/__init__.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/basePermission.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/fileBasedPermission.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/managerBasedPermission.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/permissionChecks.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/permissionDataManager.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/rule/__init__.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/rule/handler.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_argsToKwargs.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_basePermission.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_filterParser.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_graph_ql.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_input.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_managerBasedPermission.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_measurement.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_measurement_field.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_noneToZero.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_rules.py +0 -0
- {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_settings.py +0 -0
@@ -14,10 +14,13 @@ 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/jsonEncoder.py
|
18
|
+
src/general_manager/auxiliary/makeCacheKey.py
|
17
19
|
src/general_manager/auxiliary/noneToZero.py
|
18
20
|
src/general_manager/cache/cacheDecorator.py
|
19
21
|
src/general_manager/cache/cacheTracker.py
|
20
22
|
src/general_manager/cache/dependencyIndex.py
|
23
|
+
src/general_manager/cache/modelDependencyCollector.py
|
21
24
|
src/general_manager/cache/pathMapping.py
|
22
25
|
src/general_manager/cache/signals.py
|
23
26
|
src/general_manager/factory/__init__.py
|
@@ -49,6 +52,7 @@ tests/test_basePermission.py
|
|
49
52
|
tests/test_filterParser.py
|
50
53
|
tests/test_graph_ql.py
|
51
54
|
tests/test_input.py
|
55
|
+
tests/test_jsonEncoder.py
|
52
56
|
tests/test_managerBasedPermission.py
|
53
57
|
tests/test_measurement.py
|
54
58
|
tests/test_measurement_field.py
|
@@ -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()
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from typing import Any, Callable, Optional, Protocol, Set
|
2
|
+
from functools import wraps
|
3
|
+
from django.core.cache import cache as django_cache
|
4
|
+
from general_manager.cache.cacheTracker import DependencyTracker
|
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
|
8
|
+
|
9
|
+
|
10
|
+
class CacheBackend(Protocol):
|
11
|
+
def get(self, key: str) -> Any: ...
|
12
|
+
def set(self, key: str, value: Any, timeout: Optional[int] = None) -> None: ...
|
13
|
+
|
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:
|
24
|
+
@wraps(func)
|
25
|
+
def wrapper(*args, **kwargs):
|
26
|
+
key = make_cache_key(func, args, kwargs)
|
27
|
+
|
28
|
+
result = cache_backend.get(key)
|
29
|
+
if result is not None:
|
30
|
+
return result
|
31
|
+
|
32
|
+
with DependencyTracker() as dependencies:
|
33
|
+
result = func(*args, **kwargs)
|
34
|
+
ModelDependencyCollector.addArgs(dependencies, args, kwargs)
|
35
|
+
|
36
|
+
cache_backend.set(key, result, timeout)
|
37
|
+
|
38
|
+
if dependencies and timeout is None:
|
39
|
+
record_fn(key, dependencies)
|
40
|
+
|
41
|
+
return result
|
42
|
+
|
43
|
+
return wrapper
|
44
|
+
|
45
|
+
return decorator
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import threading
|
2
|
+
from general_manager.cache.dependencyIndex import (
|
3
|
+
general_manager_name,
|
4
|
+
Dependency,
|
5
|
+
filter_type,
|
6
|
+
)
|
7
|
+
|
8
|
+
# Thread-lokale Variable zur Speicherung der Abhängigkeiten
|
9
|
+
_dependency_storage = threading.local()
|
10
|
+
|
11
|
+
|
12
|
+
class DependencyTracker:
|
13
|
+
def __enter__(
|
14
|
+
self,
|
15
|
+
) -> set[Dependency]:
|
16
|
+
_dependency_storage.dependencies = set()
|
17
|
+
return _dependency_storage.dependencies
|
18
|
+
|
19
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
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
|
@@ -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
|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
from django.test import SimpleTestCase
|
2
|
+
from datetime import datetime, date, time
|
3
|
+
import json
|
4
|
+
from unittest.mock import patch
|
5
|
+
|
6
|
+
# Ensure the custom encoder module path is correct
|
7
|
+
from general_manager.auxiliary import jsonEncoder
|
8
|
+
|
9
|
+
|
10
|
+
class FakeGeneralManager:
|
11
|
+
def __init__(self, identification):
|
12
|
+
self.identification = identification
|
13
|
+
|
14
|
+
|
15
|
+
class CustomJSONEncoderTests(SimpleTestCase):
|
16
|
+
def setUp(self):
|
17
|
+
self.encoder_cls = jsonEncoder.CustomJSONEncoder
|
18
|
+
self.FakeGeneralManager = FakeGeneralManager
|
19
|
+
|
20
|
+
def test_serialize_datetime_date_time(self):
|
21
|
+
dt = datetime(2021, 12, 31, 23, 59, 59)
|
22
|
+
d = date(2020, 1, 1)
|
23
|
+
t = time(8, 30)
|
24
|
+
|
25
|
+
self.assertEqual(json.dumps(dt, cls=self.encoder_cls), f'"{dt.isoformat()}"')
|
26
|
+
self.assertEqual(json.dumps(d, cls=self.encoder_cls), f'"{d.isoformat()}"')
|
27
|
+
self.assertEqual(json.dumps(t, cls=self.encoder_cls), f'"{t.isoformat()}"')
|
28
|
+
|
29
|
+
def test_serialize_nested_datetime_in_dict(self):
|
30
|
+
dt = datetime(2022, 5, 10, 14, 0)
|
31
|
+
data = {"timestamp": dt}
|
32
|
+
result = json.dumps(data, cls=self.encoder_cls)
|
33
|
+
self.assertIn('"timestamp": "2022-05-10T14:00:00"', result)
|
34
|
+
|
35
|
+
def test_serialize_general_manager(self):
|
36
|
+
with patch.object(jsonEncoder, "GeneralManager", FakeGeneralManager):
|
37
|
+
gm = self.FakeGeneralManager({"id": 123, "name": "Test"})
|
38
|
+
dumped = json.dumps(gm, cls=self.encoder_cls)
|
39
|
+
expected = f'"{gm.__class__.__name__}(**{gm.identification})"'
|
40
|
+
self.assertEqual(dumped, expected)
|
41
|
+
|
42
|
+
def test_fallback_to_str_on_unserializable(self):
|
43
|
+
class Unserializable:
|
44
|
+
def __str__(self):
|
45
|
+
return "custom_str"
|
46
|
+
|
47
|
+
obj = Unserializable()
|
48
|
+
dumped = json.dumps(obj, cls=self.encoder_cls)
|
49
|
+
self.assertEqual(dumped, '"custom_str"')
|
@@ -79,7 +79,7 @@ def test_len_handler_success(expr, op_symbol, var_values, expected):
|
|
79
79
|
# DummyRule mit vorgegebener Op-Symbol-Antwort
|
80
80
|
rule = DummyRule(op_symbol)
|
81
81
|
result = len_handler.handle(
|
82
|
-
node, node.left, node.comparators[0], node.ops[0], var_values, rule
|
82
|
+
node, node.left, node.comparators[0], node.ops[0], var_values, rule # type: ignore
|
83
83
|
)
|
84
84
|
assert result == expected
|
85
85
|
|
@@ -88,7 +88,7 @@ def test_non_compare_node_returns_empty():
|
|
88
88
|
# z. B. ein Call-Knoten statt Compare
|
89
89
|
node = ast.parse("print('hallo')", mode="eval").body
|
90
90
|
rule = DummyRule(">")
|
91
|
-
assert len_handler.handle(node, None, None, None, {}, rule) == {}
|
91
|
+
assert len_handler.handle(node, None, None, None, {}, rule) == {} # type: ignore
|
92
92
|
|
93
93
|
|
94
94
|
@pytest.mark.parametrize(
|
@@ -104,11 +104,11 @@ def test_len_handler_invalid_raises(bad_expr, op):
|
|
104
104
|
with pytest.raises(ValueError):
|
105
105
|
len_handler.handle(
|
106
106
|
node,
|
107
|
-
node.left,
|
107
|
+
node.left, # type: ignore
|
108
108
|
getattr(node, "comparators", [None])[0],
|
109
|
-
node.ops[0] if hasattr(node, "ops") else None,
|
109
|
+
node.ops[0] if hasattr(node, "ops") else None, # type: ignore
|
110
110
|
{"x": "hi"},
|
111
|
-
rule,
|
111
|
+
rule, # type: ignore
|
112
112
|
)
|
113
113
|
|
114
114
|
|
@@ -151,7 +151,7 @@ def test_sum_handler_success(expr, op_symbol, var_values, expected):
|
|
151
151
|
node = ast.parse(expr, mode="eval").body
|
152
152
|
rule = DummyRule(op_symbol)
|
153
153
|
result = sum_handler.handle(
|
154
|
-
node, node.left, node.comparators[0], node.ops[0], var_values, rule
|
154
|
+
node, node.left, node.comparators[0], node.ops[0], var_values, rule # type: ignore
|
155
155
|
)
|
156
156
|
assert result == expected
|
157
157
|
|
@@ -160,7 +160,7 @@ def test_sum_handler_success(expr, op_symbol, var_values, expected):
|
|
160
160
|
def test_sum_handler_non_compare_returns_empty():
|
161
161
|
node = ast.parse("print(1)", mode="eval").body
|
162
162
|
rule = DummyRule(">")
|
163
|
-
assert sum_handler.handle(node, None, None, None, {}, rule) == {}
|
163
|
+
assert sum_handler.handle(node, None, None, None, {}, rule) == {} # type: ignore
|
164
164
|
|
165
165
|
|
166
166
|
@pytest.mark.parametrize(
|
@@ -177,11 +177,11 @@ def test_sum_handler_invalid(expr, var_values, error_msg):
|
|
177
177
|
with pytest.raises(ValueError) as excinfo:
|
178
178
|
sum_handler.handle(
|
179
179
|
node,
|
180
|
-
node.left,
|
180
|
+
node.left, # type: ignore
|
181
181
|
getattr(node, "comparators", [None])[0],
|
182
|
-
node.ops[0],
|
182
|
+
node.ops[0], # type: ignore
|
183
183
|
var_values,
|
184
|
-
rule,
|
184
|
+
rule, # type: ignore
|
185
185
|
)
|
186
186
|
assert error_msg in str(excinfo.value)
|
187
187
|
|
@@ -225,7 +225,7 @@ def test_max_handler_success(expr, op_symbol, var_values, expected):
|
|
225
225
|
node = ast.parse(expr, mode="eval").body
|
226
226
|
rule = DummyRule(op_symbol)
|
227
227
|
result = max_handler.handle(
|
228
|
-
node, node.left, node.comparators[0], node.ops[0], var_values, rule
|
228
|
+
node, node.left, node.comparators[0], node.ops[0], var_values, rule # type: ignore
|
229
229
|
)
|
230
230
|
assert result == expected
|
231
231
|
|
@@ -233,7 +233,7 @@ def test_max_handler_success(expr, op_symbol, var_values, expected):
|
|
233
233
|
def test_max_handler_non_compare_returns_empty():
|
234
234
|
node = ast.parse("42", mode="eval").body
|
235
235
|
rule = DummyRule("<")
|
236
|
-
assert max_handler.handle(node, None, None, None, {}, rule) == {}
|
236
|
+
assert max_handler.handle(node, None, None, None, {}, rule) == {} # type: ignore
|
237
237
|
|
238
238
|
|
239
239
|
@pytest.mark.parametrize(
|
@@ -250,11 +250,11 @@ def test_max_handler_invalid(expr, var_values, error_msg):
|
|
250
250
|
with pytest.raises(ValueError) as excinfo:
|
251
251
|
max_handler.handle(
|
252
252
|
node,
|
253
|
-
node.left,
|
253
|
+
node.left, # type: ignore
|
254
254
|
getattr(node, "comparators", [None])[0],
|
255
|
-
node.ops[0],
|
255
|
+
node.ops[0], # type: ignore
|
256
256
|
var_values,
|
257
|
-
rule,
|
257
|
+
rule, # type: ignore
|
258
258
|
)
|
259
259
|
assert error_msg in str(excinfo.value)
|
260
260
|
|
@@ -298,7 +298,7 @@ def test_min_handler_success(expr, op_symbol, var_values, expected):
|
|
298
298
|
node = ast.parse(expr, mode="eval").body
|
299
299
|
rule = DummyRule(op_symbol)
|
300
300
|
result = min_handler.handle(
|
301
|
-
node, node.left, node.comparators[0], node.ops[0], var_values, rule
|
301
|
+
node, node.left, node.comparators[0], node.ops[0], var_values, rule # type: ignore
|
302
302
|
)
|
303
303
|
assert result == expected
|
304
304
|
|
@@ -306,7 +306,7 @@ def test_min_handler_success(expr, op_symbol, var_values, expected):
|
|
306
306
|
def test_min_handler_non_compare_returns_empty():
|
307
307
|
node = ast.parse("'foo'", mode="eval").body
|
308
308
|
rule = DummyRule(">=")
|
309
|
-
assert min_handler.handle(node, None, None, None, {}, rule) == {}
|
309
|
+
assert min_handler.handle(node, None, None, None, {}, rule) == {} # type: ignore
|
310
310
|
|
311
311
|
|
312
312
|
@pytest.mark.parametrize(
|
@@ -323,11 +323,11 @@ def test_min_handler_invalid(expr, var_values, error_msg):
|
|
323
323
|
with pytest.raises(ValueError) as excinfo:
|
324
324
|
min_handler.handle(
|
325
325
|
node,
|
326
|
-
node.left,
|
326
|
+
node.left, # type: ignore
|
327
327
|
getattr(node, "comparators", [None])[0],
|
328
|
-
node.ops[0],
|
328
|
+
node.ops[0], # type: ignore
|
329
329
|
var_values,
|
330
|
-
rule,
|
330
|
+
rule, # type: ignore
|
331
331
|
)
|
332
332
|
assert error_msg in str(excinfo.value)
|
333
333
|
|
@@ -361,15 +361,15 @@ def test_mixed_numeric_types(expr, op_symbol, var_values, expected):
|
|
361
361
|
# dispatch auf den richtigen Handler
|
362
362
|
if expr.startswith("sum"):
|
363
363
|
result = sum_handler.handle(
|
364
|
-
node, node.left, node.comparators[0], node.ops[0], var_values, rule
|
364
|
+
node, node.left, node.comparators[0], node.ops[0], var_values, rule # type: ignore
|
365
365
|
)
|
366
366
|
elif expr.startswith("max"):
|
367
367
|
result = max_handler.handle(
|
368
|
-
node, node.left, node.comparators[0], node.ops[0], var_values, rule
|
368
|
+
node, node.left, node.comparators[0], node.ops[0], var_values, rule # type: ignore
|
369
369
|
)
|
370
370
|
else:
|
371
371
|
result = min_handler.handle(
|
372
|
-
node, node.left, node.comparators[0], node.ops[0], var_values, rule
|
372
|
+
node, node.left, node.comparators[0], node.ops[0], var_values, rule # type: ignore
|
373
373
|
)
|
374
374
|
assert result == expected
|
375
375
|
|
@@ -382,7 +382,7 @@ def test_sum_handler_large_collection():
|
|
382
382
|
node = ast.parse(expr, mode="eval").body
|
383
383
|
rule = DummyRule(">=")
|
384
384
|
result = sum_handler.handle(
|
385
|
-
node, node.left, node.comparators[0], node.ops[0], {"large": large}, rule
|
385
|
+
node, node.left, node.comparators[0], node.ops[0], {"large": large}, rule # type: ignore
|
386
386
|
)
|
387
387
|
assert result == {"large": f"[large] (sum={n}) is too small (>= {n})!"}
|
388
388
|
|
@@ -394,7 +394,7 @@ def test_max_handler_large_collection():
|
|
394
394
|
node = ast.parse(expr, mode="eval").body
|
395
395
|
rule = DummyRule("==")
|
396
396
|
result = max_handler.handle(
|
397
|
-
node, node.left, node.comparators[0], node.ops[0], {"large": large}, rule
|
397
|
+
node, node.left, node.comparators[0], node.ops[0], {"large": large}, rule # type: ignore
|
398
398
|
)
|
399
399
|
assert result == {"large": f"[large] (max={n-1}) must be {n-1}!"}
|
400
400
|
|
@@ -406,7 +406,7 @@ def test_min_handler_large_collection():
|
|
406
406
|
node = ast.parse(expr, mode="eval").body
|
407
407
|
rule = DummyRule("==")
|
408
408
|
result = min_handler.handle(
|
409
|
-
node, node.left, node.comparators[0], node.ops[0], {"large": large}, rule
|
409
|
+
node, node.left, node.comparators[0], node.ops[0], {"large": large}, rule # type: ignore
|
410
410
|
)
|
411
411
|
assert result == {"large": "[large] (min=0) must be 0!"}
|
412
412
|
|
@@ -426,9 +426,9 @@ def test_handler_none_value_raises(handler, expr, error_msg):
|
|
426
426
|
with pytest.raises(ValueError) as excinfo:
|
427
427
|
handler.handle(
|
428
428
|
node,
|
429
|
-
node.left,
|
430
|
-
node.comparators[0],
|
431
|
-
node.ops[0],
|
429
|
+
node.left, # type: ignore
|
430
|
+
node.comparators[0], # type: ignore
|
431
|
+
node.ops[0], # type: ignore
|
432
432
|
{"x": None},
|
433
433
|
rule,
|
434
434
|
)
|
@@ -1,72 +0,0 @@
|
|
1
|
-
from typing import Callable, Optional
|
2
|
-
from functools import wraps
|
3
|
-
from django.core.cache import cache as django_cache
|
4
|
-
from hashlib import sha256
|
5
|
-
from general_manager.cache.cacheTracker import DependencyTracker
|
6
|
-
from general_manager.cache.dependencyIndex import record_dependencies, get_full_index
|
7
|
-
|
8
|
-
|
9
|
-
def cached(timeout: Optional[int] = None) -> Callable:
|
10
|
-
"""
|
11
|
-
Decorator to cache the result of a function for a specified timeout.
|
12
|
-
If no timeout is provided, the cache will expire when a dependency is invalidated.
|
13
|
-
"""
|
14
|
-
|
15
|
-
def decorator(func: Callable) -> Callable:
|
16
|
-
|
17
|
-
@wraps(func)
|
18
|
-
def wrapper(*args, **kwargs):
|
19
|
-
from general_manager.manager.generalManager import GeneralManager, Bucket
|
20
|
-
|
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
|
-
with DependencyTracker() as dependencies:
|
30
|
-
result = func(*args, **kwargs)
|
31
|
-
|
32
|
-
def collect_model_dependencies(obj):
|
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)
|
60
|
-
|
61
|
-
django_cache.set(django_cache_key, result, timeout)
|
62
|
-
|
63
|
-
if dependencies and not timeout:
|
64
|
-
record_dependencies(
|
65
|
-
django_cache_key,
|
66
|
-
dependencies,
|
67
|
-
)
|
68
|
-
return result
|
69
|
-
|
70
|
-
return wrapper
|
71
|
-
|
72
|
-
return decorator
|
@@ -1,33 +0,0 @@
|
|
1
|
-
import threading
|
2
|
-
from general_manager.cache.dependencyIndex import general_manager_name
|
3
|
-
from typing import Any, Literal
|
4
|
-
|
5
|
-
|
6
|
-
# Thread-lokale Variable zur Speicherung der Abhängigkeiten
|
7
|
-
_dependency_storage = threading.local()
|
8
|
-
|
9
|
-
|
10
|
-
class DependencyTracker:
|
11
|
-
def __enter__(
|
12
|
-
self,
|
13
|
-
) -> set[
|
14
|
-
tuple[general_manager_name, Literal["filter", "exclude", "identification"], str]
|
15
|
-
]:
|
16
|
-
_dependency_storage.dependencies = set()
|
17
|
-
return _dependency_storage.dependencies
|
18
|
-
|
19
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
20
|
-
# Optional: Aufräumen oder weitere Verarbeitung
|
21
|
-
pass
|
22
|
-
|
23
|
-
|
24
|
-
def addDependency(class_name: str, operation: str, identifier: str) -> None:
|
25
|
-
"""
|
26
|
-
Adds a dependency to the dependency storage.
|
27
|
-
"""
|
28
|
-
if hasattr(_dependency_storage, "dependencies"):
|
29
|
-
dependencies: set[
|
30
|
-
tuple[general_manager_name, Literal["filter", "exclude", "id"], str]
|
31
|
-
] = _dependency_storage.dependencies
|
32
|
-
|
33
|
-
_dependency_storage.dependencies.add((class_name, operation, identifier))
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/interface/baseInterface.py
RENAMED
File without changes
|
{generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/interface/calculationInterface.py
RENAMED
File without changes
|
{generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/interface/databaseInterface.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/measurement/measurement.py
RENAMED
File without changes
|
{generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/measurement/measurementField.py
RENAMED
File without changes
|
File without changes
|
{generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/basePermission.py
RENAMED
File without changes
|
{generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/fileBasedPermission.py
RENAMED
File without changes
|
File without changes
|
{generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/permissionChecks.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|