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.
Files changed (66) hide show
  1. {generalmanager-0.2.0 → generalmanager-0.3.0}/GeneralManager.egg-info/PKG-INFO +1 -1
  2. {generalmanager-0.2.0 → generalmanager-0.3.0}/GeneralManager.egg-info/SOURCES.txt +4 -0
  3. {generalmanager-0.2.0 → generalmanager-0.3.0}/PKG-INFO +1 -1
  4. {generalmanager-0.2.0 → generalmanager-0.3.0}/pyproject.toml +1 -1
  5. generalmanager-0.3.0/src/general_manager/auxiliary/jsonEncoder.py +19 -0
  6. generalmanager-0.3.0/src/general_manager/auxiliary/makeCacheKey.py +30 -0
  7. generalmanager-0.3.0/src/general_manager/cache/cacheDecorator.py +45 -0
  8. generalmanager-0.3.0/src/general_manager/cache/cacheTracker.py +34 -0
  9. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/cache/dependencyIndex.py +3 -1
  10. generalmanager-0.3.0/src/general_manager/cache/modelDependencyCollector.py +51 -0
  11. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/manager/generalManager.py +10 -4
  12. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/rule/rule.py +1 -1
  13. generalmanager-0.3.0/tests/test_jsonEncoder.py +49 -0
  14. {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_rule_handler.py +29 -29
  15. generalmanager-0.2.0/src/general_manager/cache/cacheDecorator.py +0 -72
  16. generalmanager-0.2.0/src/general_manager/cache/cacheTracker.py +0 -33
  17. {generalmanager-0.2.0 → generalmanager-0.3.0}/GeneralManager.egg-info/dependency_links.txt +0 -0
  18. {generalmanager-0.2.0 → generalmanager-0.3.0}/GeneralManager.egg-info/requires.txt +0 -0
  19. {generalmanager-0.2.0 → generalmanager-0.3.0}/GeneralManager.egg-info/top_level.txt +0 -0
  20. {generalmanager-0.2.0 → generalmanager-0.3.0}/LICENSE +0 -0
  21. {generalmanager-0.2.0 → generalmanager-0.3.0}/README.md +0 -0
  22. {generalmanager-0.2.0 → generalmanager-0.3.0}/setup.cfg +0 -0
  23. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/__init__.py +0 -0
  24. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/api/graphql.py +0 -0
  25. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/api/mutation.py +0 -0
  26. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/api/property.py +0 -0
  27. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/apps.py +0 -0
  28. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/auxiliary/__init__.py +0 -0
  29. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/auxiliary/argsToKwargs.py +0 -0
  30. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/auxiliary/filterParser.py +0 -0
  31. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/auxiliary/noneToZero.py +0 -0
  32. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/cache/pathMapping.py +0 -0
  33. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/cache/signals.py +0 -0
  34. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/factory/__init__.py +0 -0
  35. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/factory/factories.py +0 -0
  36. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/factory/lazy_methods.py +0 -0
  37. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/interface/__init__.py +0 -0
  38. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/interface/baseInterface.py +0 -0
  39. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/interface/calculationInterface.py +0 -0
  40. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/interface/databaseInterface.py +0 -0
  41. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/manager/__init__.py +0 -0
  42. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/manager/groupManager.py +0 -0
  43. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/manager/input.py +0 -0
  44. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/manager/meta.py +0 -0
  45. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/measurement/__init__.py +0 -0
  46. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/measurement/measurement.py +0 -0
  47. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/measurement/measurementField.py +0 -0
  48. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/__init__.py +0 -0
  49. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/basePermission.py +0 -0
  50. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/fileBasedPermission.py +0 -0
  51. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/managerBasedPermission.py +0 -0
  52. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/permissionChecks.py +0 -0
  53. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/permission/permissionDataManager.py +0 -0
  54. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/rule/__init__.py +0 -0
  55. {generalmanager-0.2.0 → generalmanager-0.3.0}/src/general_manager/rule/handler.py +0 -0
  56. {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_argsToKwargs.py +0 -0
  57. {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_basePermission.py +0 -0
  58. {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_filterParser.py +0 -0
  59. {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_graph_ql.py +0 -0
  60. {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_input.py +0 -0
  61. {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_managerBasedPermission.py +0 -0
  62. {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_measurement.py +0 -0
  63. {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_measurement_field.py +0 -0
  64. {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_noneToZero.py +0 -0
  65. {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_rules.py +0 -0
  66. {generalmanager-0.2.0 → generalmanager-0.3.0}/tests/test_settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Kurzbeschreibung deines Pakets
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License: Non-Commercial MIT License
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Kurzbeschreibung deines Pakets
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License: Non-Commercial MIT License
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "GeneralManager"
10
- version = "0.2.0"
10
+ version = "0.3.0"
11
11
  description = "Kurzbeschreibung deines Pakets"
12
12
  readme = "README.md"
13
13
  authors = [
@@ -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 addDependency
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
- addDependency(self.__class__.__name__, "identification", f"{self.__id}")
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
- addDependency(cls.__name__, "filter", f"{cls.__parse_identification(kwargs)}")
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
- addDependency(cls.__name__, "exclude", f"{cls.__parse_identification(kwargs)}")
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