GeneralManager 0.3.1__tar.gz → 0.3.2__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 (70) hide show
  1. {generalmanager-0.3.1 → generalmanager-0.3.2}/GeneralManager.egg-info/PKG-INFO +1 -1
  2. {generalmanager-0.3.1 → generalmanager-0.3.2}/GeneralManager.egg-info/SOURCES.txt +3 -1
  3. {generalmanager-0.3.1 → generalmanager-0.3.2}/PKG-INFO +1 -1
  4. {generalmanager-0.3.1 → generalmanager-0.3.2}/pyproject.toml +1 -1
  5. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/cache/dependencyIndex.py +41 -45
  6. generalmanager-0.3.2/tests/test_dependencyIndex.py +967 -0
  7. generalmanager-0.3.2/tests/test_signals.py +109 -0
  8. {generalmanager-0.3.1 → generalmanager-0.3.2}/GeneralManager.egg-info/dependency_links.txt +0 -0
  9. {generalmanager-0.3.1 → generalmanager-0.3.2}/GeneralManager.egg-info/requires.txt +0 -0
  10. {generalmanager-0.3.1 → generalmanager-0.3.2}/GeneralManager.egg-info/top_level.txt +0 -0
  11. {generalmanager-0.3.1 → generalmanager-0.3.2}/LICENSE +0 -0
  12. {generalmanager-0.3.1 → generalmanager-0.3.2}/README.md +0 -0
  13. {generalmanager-0.3.1 → generalmanager-0.3.2}/setup.cfg +0 -0
  14. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/__init__.py +0 -0
  15. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/api/graphql.py +0 -0
  16. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/api/mutation.py +0 -0
  17. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/api/property.py +0 -0
  18. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/apps.py +0 -0
  19. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/auxiliary/__init__.py +0 -0
  20. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/auxiliary/argsToKwargs.py +0 -0
  21. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/auxiliary/filterParser.py +0 -0
  22. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/auxiliary/jsonEncoder.py +0 -0
  23. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/auxiliary/makeCacheKey.py +0 -0
  24. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/auxiliary/noneToZero.py +0 -0
  25. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/auxiliary/pathMapping.py +0 -0
  26. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/cache/cacheDecorator.py +0 -0
  27. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/cache/cacheTracker.py +0 -0
  28. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/cache/modelDependencyCollector.py +0 -0
  29. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/cache/signals.py +0 -0
  30. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/factory/__init__.py +0 -0
  31. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/factory/factories.py +0 -0
  32. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/factory/lazy_methods.py +0 -0
  33. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/interface/__init__.py +0 -0
  34. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/interface/baseInterface.py +0 -0
  35. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/interface/calculationInterface.py +0 -0
  36. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/interface/databaseInterface.py +0 -0
  37. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/manager/__init__.py +0 -0
  38. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/manager/generalManager.py +0 -0
  39. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/manager/groupManager.py +0 -0
  40. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/manager/input.py +0 -0
  41. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/manager/meta.py +0 -0
  42. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/measurement/__init__.py +0 -0
  43. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/measurement/measurement.py +0 -0
  44. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/measurement/measurementField.py +0 -0
  45. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/permission/__init__.py +0 -0
  46. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/permission/basePermission.py +0 -0
  47. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/permission/fileBasedPermission.py +0 -0
  48. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/permission/managerBasedPermission.py +0 -0
  49. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/permission/permissionChecks.py +0 -0
  50. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/permission/permissionDataManager.py +0 -0
  51. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/rule/__init__.py +0 -0
  52. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/rule/handler.py +0 -0
  53. {generalmanager-0.3.1 → generalmanager-0.3.2}/src/general_manager/rule/rule.py +0 -0
  54. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_argsToKwargs.py +0 -0
  55. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_basePermission.py +0 -0
  56. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_cacheDecorator.py +0 -0
  57. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_cacheTracker.py +0 -0
  58. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_filterParser.py +0 -0
  59. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_graph_ql.py +0 -0
  60. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_input.py +0 -0
  61. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_jsonEncoder.py +0 -0
  62. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_makeCacheKey.py +0 -0
  63. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_managerBasedPermission.py +0 -0
  64. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_measurement.py +0 -0
  65. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_measurement_field.py +0 -0
  66. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_modelDependencyCollector.py +0 -0
  67. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_noneToZero.py +0 -0
  68. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_rule_handler.py +0 -0
  69. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_rules.py +0 -0
  70. {generalmanager-0.3.1 → generalmanager-0.3.2}/tests/test_settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Kurzbeschreibung deines Pakets
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License: Non-Commercial MIT License
@@ -51,6 +51,7 @@ tests/test_argsToKwargs.py
51
51
  tests/test_basePermission.py
52
52
  tests/test_cacheDecorator.py
53
53
  tests/test_cacheTracker.py
54
+ tests/test_dependencyIndex.py
54
55
  tests/test_filterParser.py
55
56
  tests/test_graph_ql.py
56
57
  tests/test_input.py
@@ -63,4 +64,5 @@ tests/test_modelDependencyCollector.py
63
64
  tests/test_noneToZero.py
64
65
  tests/test_rule_handler.py
65
66
  tests/test_rules.py
66
- tests/test_settings.py
67
+ tests/test_settings.py
68
+ tests/test_signals.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.3.1
3
+ Version: 0.3.2
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.3.1"
10
+ version = "0.3.2"
11
11
  description = "Kurzbeschreibung deines Pakets"
12
12
  readme = "README.md"
13
13
  authors = [
@@ -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: legt den LOCK_KEY an, wenn noch frei."""
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
- """Gibt den Lock frei."""
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
- """Lädt oder initialisiert den kompletten Index."""
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
- """Schreibt den kompletten Index zurück in den Cache."""
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
- # Direkter IDLookup als simpler filter auf 'id'
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) -> bool:
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
- return False
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
- # Welche Lookups interessieren uns für diesen Model?
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
- # Speichere alle relevanten Attribute für später
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
- obj = getattr(obj, attr, None)
184
- if obj is None:
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
- pattern = re.compile(val_key)
240
- except:
241
- return False
242
- text = value or ""
243
- return bool(pattern.search(text))
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) Operator und Attributpfad ermitteln
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) Alten und neuen Wert holen
270
- old_val = old_relevant_values.get(lookup)
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) Für jedes val_key prüfen
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
- # Direkte & alle Filter-Abhängigkeiten: immer invalidieren, wenn neu matcht
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: nur invalidieren, wenn sich der Match-Status ändert
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}"