GeneralManager 0.3.1__py3-none-any.whl → 0.3.2__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/cache/dependencyIndex.py +41 -45
- {generalmanager-0.3.1.dist-info → generalmanager-0.3.2.dist-info}/METADATA +1 -1
- {generalmanager-0.3.1.dist-info → generalmanager-0.3.2.dist-info}/RECORD +6 -6
- {generalmanager-0.3.1.dist-info → generalmanager-0.3.2.dist-info}/WHEEL +0 -0
- {generalmanager-0.3.1.dist-info → generalmanager-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.3.1.dist-info → generalmanager-0.3.2.dist-info}/top_level.txt +0 -0
@@ -33,18 +33,19 @@ type Dependency = Tuple[general_manager_name, filter_type, str]
|
|
33
33
|
INDEX_KEY = "dependency_index" # Key unter dem der gesamte Index liegt
|
34
34
|
LOCK_KEY = "dependency_index_lock" # Key für das Sperr‑Mutex
|
35
35
|
LOCK_TIMEOUT = 5 # Sekunden TTL für den Lock
|
36
|
+
UNDEFINED = object() # Dummy für nicht definierte Werte
|
36
37
|
|
37
38
|
|
38
39
|
# -----------------------------------------------------------------------------
|
39
40
|
# LOCKING HELPERS
|
40
41
|
# -----------------------------------------------------------------------------
|
41
42
|
def acquire_lock(timeout: int = LOCK_TIMEOUT) -> bool:
|
42
|
-
"""Atomar:
|
43
|
+
"""Atomar: create Lock key if it doesn't exist."""
|
43
44
|
return cache.add(LOCK_KEY, "1", timeout)
|
44
45
|
|
45
46
|
|
46
47
|
def release_lock() -> None:
|
47
|
-
"""
|
48
|
+
"""Release Lock key."""
|
48
49
|
cache.delete(LOCK_KEY)
|
49
50
|
|
50
51
|
|
@@ -52,7 +53,7 @@ def release_lock() -> None:
|
|
52
53
|
# INDEX ACCESS
|
53
54
|
# -----------------------------------------------------------------------------
|
54
55
|
def get_full_index() -> dependency_index:
|
55
|
-
"""
|
56
|
+
"""Load or initialize the full index."""
|
56
57
|
idx = cache.get(INDEX_KEY, None)
|
57
58
|
if idx is None:
|
58
59
|
idx: dependency_index = {"filter": {}, "exclude": {}}
|
@@ -61,7 +62,7 @@ def get_full_index() -> dependency_index:
|
|
61
62
|
|
62
63
|
|
63
64
|
def set_full_index(idx: dependency_index) -> None:
|
64
|
-
"""
|
65
|
+
"""Write the complete index back to the cache."""
|
65
66
|
cache.set(INDEX_KEY, idx, None)
|
66
67
|
|
67
68
|
|
@@ -78,15 +79,6 @@ def record_dependencies(
|
|
78
79
|
]
|
79
80
|
],
|
80
81
|
) -> None:
|
81
|
-
"""
|
82
|
-
Speichert die Abhängigkeiten eines Cache Eintrags.
|
83
|
-
:param cache_key: der Key unter dem das Ergebnis im cache steht
|
84
|
-
:param dependencies: Iterable von Tuplen (model_name, action, identifier)
|
85
|
-
action ∈ {'filter','exclude'} oder sonstige → 'id'
|
86
|
-
identifier = für filter/exclude: Dict String,
|
87
|
-
sonst: Primärschlüssel als String
|
88
|
-
"""
|
89
|
-
# 1) Lock holen (Spin‑Lock mit Timeout)
|
90
82
|
start = time.time()
|
91
83
|
while not acquire_lock():
|
92
84
|
if time.time() - start > LOCK_TIMEOUT:
|
@@ -105,7 +97,7 @@ def record_dependencies(
|
|
105
97
|
lookup_map.setdefault(val_key, set()).add(cache_key)
|
106
98
|
|
107
99
|
else:
|
108
|
-
#
|
100
|
+
# director ID Lookup as simple filter on 'id'
|
109
101
|
section = idx["filter"].setdefault(model_name, {})
|
110
102
|
lookup_map = section.setdefault("identification", {})
|
111
103
|
val_key = identifier
|
@@ -120,15 +112,12 @@ def record_dependencies(
|
|
120
112
|
# -----------------------------------------------------------------------------
|
121
113
|
# INDEX CLEANUP
|
122
114
|
# -----------------------------------------------------------------------------
|
123
|
-
def remove_cache_key_from_index(cache_key: str) ->
|
124
|
-
"""
|
125
|
-
Entfernt einen cache_key aus allen Einträgen in filter/exclude.
|
126
|
-
Nützlich, sobald Du den Cache gelöscht hast.
|
127
|
-
"""
|
115
|
+
def remove_cache_key_from_index(cache_key: str) -> None:
|
116
|
+
"""Remove a cache key from the index."""
|
128
117
|
start = time.time()
|
129
118
|
while not acquire_lock():
|
130
119
|
if time.time() - start > LOCK_TIMEOUT:
|
131
|
-
|
120
|
+
raise TimeoutError("Could not aquire lock for remove_cache_key_from_index")
|
132
121
|
time.sleep(0.05)
|
133
122
|
|
134
123
|
try:
|
@@ -149,14 +138,12 @@ def remove_cache_key_from_index(cache_key: str) -> bool:
|
|
149
138
|
set_full_index(idx)
|
150
139
|
finally:
|
151
140
|
release_lock()
|
152
|
-
return True
|
153
141
|
|
154
142
|
|
155
143
|
# -----------------------------------------------------------------------------
|
156
144
|
# CACHE INVALIDATION
|
157
145
|
# -----------------------------------------------------------------------------
|
158
146
|
def invalidate_cache_key(cache_key: str) -> None:
|
159
|
-
"""Löscht den CacheEintrag – hier nutzt du deinen CacheBackend."""
|
160
147
|
cache.delete(cache_key)
|
161
148
|
|
162
149
|
|
@@ -165,33 +152,28 @@ def capture_old_values(
|
|
165
152
|
sender: Type[GeneralManager], instance: GeneralManager | None, **kwargs
|
166
153
|
) -> None:
|
167
154
|
if instance is None:
|
168
|
-
# Wenn es kein Modell ist, gibt es nichts zu tun
|
169
155
|
return
|
170
156
|
manager_name = sender.__name__
|
171
157
|
idx = get_full_index()
|
172
|
-
#
|
158
|
+
# get all lookups for this model
|
173
159
|
lookups = set()
|
174
160
|
for action in ("filter", "exclude"):
|
175
161
|
lookups |= set(idx.get(action, {}).get(manager_name, {}))
|
176
162
|
if lookups and instance.identification:
|
177
|
-
#
|
163
|
+
# save old values for later comparison
|
178
164
|
vals = {}
|
179
165
|
for lookup in lookups:
|
180
166
|
attr_path = lookup.split("__")
|
181
167
|
obj = instance
|
182
|
-
for attr in attr_path:
|
183
|
-
|
184
|
-
|
168
|
+
for i, attr in enumerate(attr_path):
|
169
|
+
if getattr(obj, attr, UNDEFINED) is UNDEFINED:
|
170
|
+
lookup = "__".join(attr_path[:i])
|
185
171
|
break
|
172
|
+
obj = getattr(obj, attr, None)
|
186
173
|
vals[lookup] = obj
|
187
174
|
setattr(instance, "_old_values", vals)
|
188
175
|
|
189
176
|
|
190
|
-
# -----------------------------------------------------------------------------
|
191
|
-
# GENERIC CACHE INVALIDATION: vergleicht alt vs. neu und invalidiert nur bei Übergang
|
192
|
-
# -----------------------------------------------------------------------------
|
193
|
-
|
194
|
-
|
195
177
|
@receiver(post_data_change)
|
196
178
|
def generic_cache_invalidation(
|
197
179
|
sender: type[GeneralManager],
|
@@ -236,18 +218,32 @@ def generic_cache_invalidation(
|
|
236
218
|
# wildcard / regex
|
237
219
|
if op in ("contains", "startswith", "endswith", "regex"):
|
238
220
|
try:
|
239
|
-
|
240
|
-
except:
|
241
|
-
|
242
|
-
|
243
|
-
|
221
|
+
literal = ast.literal_eval(val_key)
|
222
|
+
except Exception:
|
223
|
+
literal = val_key
|
224
|
+
|
225
|
+
# ensure we always work with strings to avoid TypeErrors
|
226
|
+
text = "" if value is None else str(value)
|
227
|
+
if op == "contains":
|
228
|
+
return literal in text
|
229
|
+
if op == "startswith":
|
230
|
+
return text.startswith(literal)
|
231
|
+
if op == "endswith":
|
232
|
+
return text.endswith(literal)
|
233
|
+
# regex: val_key selbst als Pattern benutzen
|
234
|
+
if op == "regex":
|
235
|
+
try:
|
236
|
+
pattern = re.compile(val_key)
|
237
|
+
except re.error:
|
238
|
+
return False
|
239
|
+
return bool(pattern.search(text))
|
244
240
|
|
245
241
|
return False
|
246
242
|
|
247
243
|
for action in ("filter", "exclude"):
|
248
244
|
model_section = idx.get(action, {}).get(manager_name, {})
|
249
245
|
for lookup, lookup_map in model_section.items():
|
250
|
-
# 1)
|
246
|
+
# 1) get operator and attribute path
|
251
247
|
parts = lookup.split("__")
|
252
248
|
if parts[-1] in (
|
253
249
|
"gt",
|
@@ -266,8 +262,8 @@ def generic_cache_invalidation(
|
|
266
262
|
op = "eq"
|
267
263
|
attr_path = parts
|
268
264
|
|
269
|
-
# 2)
|
270
|
-
old_val = old_relevant_values.get(
|
265
|
+
# 2) get old & new value
|
266
|
+
old_val = old_relevant_values.get("__".join(attr_path))
|
271
267
|
|
272
268
|
obj = instance
|
273
269
|
for attr in attr_path:
|
@@ -276,14 +272,14 @@ def generic_cache_invalidation(
|
|
276
272
|
break
|
277
273
|
new_val = obj
|
278
274
|
|
279
|
-
# 3)
|
275
|
+
# 3) check against all cache_keys
|
280
276
|
for val_key, cache_keys in list(lookup_map.items()):
|
281
277
|
old_match = matches(op, old_val, val_key)
|
282
278
|
new_match = matches(op, new_val, val_key)
|
283
279
|
|
284
280
|
if action == "filter":
|
285
|
-
#
|
286
|
-
if new_match:
|
281
|
+
# Filter: invalidate if new match or old match
|
282
|
+
if new_match or old_match:
|
287
283
|
print(
|
288
284
|
f"Invalidate cache key {cache_keys} for filter {lookup} with value {val_key}"
|
289
285
|
)
|
@@ -292,7 +288,7 @@ def generic_cache_invalidation(
|
|
292
288
|
remove_cache_key_from_index(ck)
|
293
289
|
|
294
290
|
else: # action == 'exclude'
|
295
|
-
# Excludes:
|
291
|
+
# Excludes: invalidate only if matches changed
|
296
292
|
if old_match != new_match:
|
297
293
|
print(
|
298
294
|
f"Invalidate cache key {cache_keys} for exclude {lookup} with value {val_key}"
|
@@ -12,7 +12,7 @@ general_manager/auxiliary/noneToZero.py,sha256=KfQtMQnrT6vsYST0K7lv6pVujkDcK3XL8
|
|
12
12
|
general_manager/auxiliary/pathMapping.py,sha256=nrz5owQg2a69Yig1eCXorR9U0NSw7NmBAk5OkeoHTdA,6842
|
13
13
|
general_manager/cache/cacheDecorator.py,sha256=DK2ANIJgPpMxazfMSiFrI9OuVE_7K9zlIZQRrgaC2Lw,3268
|
14
14
|
general_manager/cache/cacheTracker.py,sha256=rRw3OhBDf86hTC2Xbt1ocRgZqwu8_kXk4lczamcADFg,2955
|
15
|
-
general_manager/cache/dependencyIndex.py,sha256=
|
15
|
+
general_manager/cache/dependencyIndex.py,sha256=kEbIAzzMzKlQgplKfcMYBPZ562zCBkOBKvJusxO_iC4,10537
|
16
16
|
general_manager/cache/modelDependencyCollector.py,sha256=wS2edbZsQ1aTfRlHj02lhuasZHCc2ucRGob-E7ejuoY,2433
|
17
17
|
general_manager/cache/signals.py,sha256=ZHeXKFMN7tj9t0J-vSqf_05_NhGqEF2sZtbZO3vaRqI,1234
|
18
18
|
general_manager/factory/__init__.py,sha256=DLSQbpSBpujPtDSZcruPc43OLWzKCCtf20gbalCDYRU,91
|
@@ -39,8 +39,8 @@ general_manager/permission/permissionDataManager.py,sha256=Ji7fsnuaKTa6M8yzCGyzr
|
|
39
39
|
general_manager/rule/__init__.py,sha256=4Har5cfPD1fmOsilTDod-ZUz3Com-tkl58jz7yY4fD0,23
|
40
40
|
general_manager/rule/handler.py,sha256=z8SFHTIZ0LbLh3fV56Mud0V4_OvWkqJjlHvFqau7Qfk,7334
|
41
41
|
general_manager/rule/rule.py,sha256=3FVCKGL7BTVoStdgOTdWQwuoVRIxAIAilV4VOzouDpc,10759
|
42
|
-
generalmanager-0.3.
|
43
|
-
generalmanager-0.3.
|
44
|
-
generalmanager-0.3.
|
45
|
-
generalmanager-0.3.
|
46
|
-
generalmanager-0.3.
|
42
|
+
generalmanager-0.3.2.dist-info/licenses/LICENSE,sha256=YGFm0ieb4KpkMRRt2qnWue6uFh0cUMtobwEBkHwajhc,1450
|
43
|
+
generalmanager-0.3.2.dist-info/METADATA,sha256=xE5D8fR08XEmMO8Fn2GbO5cu9Y1ESY1vvjZE660JfUA,8188
|
44
|
+
generalmanager-0.3.2.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
45
|
+
generalmanager-0.3.2.dist-info/top_level.txt,sha256=sTDtExP9ga-YP3h3h42yivUY-A2Q23C2nw6LNKOho4I,16
|
46
|
+
generalmanager-0.3.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|