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.
@@ -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, get_full_index
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
- 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
- """
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
- from general_manager.manager.generalManager import GeneralManager, Bucket
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
- 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)
36
+ cache_backend.set(key, result, timeout)
60
37
 
61
- django_cache.set(django_cache_key, result, timeout)
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 general_manager_name
3
- from typing import Any, Literal
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
- # 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))
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
 
@@ -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
@@ -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=RmUDyJfkouO2-R2KZZJF1n8RCB0FjVa2OoTeLQ0vUyg,2934
11
- general_manager/cache/cacheTracker.py,sha256=vUQCarqABIW_O02hbKeRo0thurmPD0TfrOlIUh7kneI,1045
12
- general_manager/cache/dependencyIndex.py,sha256=KUDroajKrWfJv0PO2EQVNTrYq7XxrpBT4MGKBgPwMwo,10676
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=xt7tvwTaPGhh7Z8VhbdMJuScsl78B9QDVQoMWv4amuo,5158
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=jmZxMFxthMPcc85Xx_zb8l7hajJK2SFAIgBZpE8bk8g,10736
39
- generalmanager-0.2.0.dist-info/licenses/LICENSE,sha256=YGFm0ieb4KpkMRRt2qnWue6uFh0cUMtobwEBkHwajhc,1450
40
- generalmanager-0.2.0.dist-info/METADATA,sha256=RIR7LsPxNSSbPErDku5Vv-YdlFb2BYD9QjXowcEOais,8188
41
- generalmanager-0.2.0.dist-info/WHEEL,sha256=A8Eltl-h0W-qZDVezsLjjslosEH_pdYC2lQ0JcbgCzs,91
42
- generalmanager-0.2.0.dist-info/top_level.txt,sha256=sTDtExP9ga-YP3h3h42yivUY-A2Q23C2nw6LNKOho4I,16
43
- generalmanager-0.2.0.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.0)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5