django-bulk-hooks 0.1.216__tar.gz → 0.1.218__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.
Potentially problematic release.
This version of django-bulk-hooks might be problematic. Click here for more details.
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/PKG-INFO +1 -1
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/django_bulk_hooks/conditions.py +1 -6
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/django_bulk_hooks/engine.py +4 -16
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/django_bulk_hooks/queryset.py +19 -47
- django_bulk_hooks-0.1.218/django_bulk_hooks/registry.py +29 -0
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/pyproject.toml +1 -1
- django_bulk_hooks-0.1.216/django_bulk_hooks/registry.py +0 -37
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/LICENSE +0 -0
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/README.md +0 -0
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/django_bulk_hooks/__init__.py +0 -0
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/django_bulk_hooks/constants.py +0 -0
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/django_bulk_hooks/context.py +0 -0
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/django_bulk_hooks/decorators.py +0 -0
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/django_bulk_hooks/enums.py +0 -0
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/django_bulk_hooks/handler.py +0 -0
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/django_bulk_hooks/manager.py +0 -0
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/django_bulk_hooks/models.py +0 -0
- {django_bulk_hooks-0.1.216 → django_bulk_hooks-0.1.218}/django_bulk_hooks/priority.py +0 -0
|
@@ -77,13 +77,8 @@ class HasChanged(HookCondition):
|
|
|
77
77
|
previous = resolve_dotted_attr(original_instance, self.field)
|
|
78
78
|
|
|
79
79
|
# Add more detailed debugging
|
|
80
|
-
print(f"DEBUG: Field '{self.field}' - current value: {current} (type: {type(current)})")
|
|
81
|
-
print(f"DEBUG: Field '{self.field}' - previous value: {previous} (type: {type(previous)})")
|
|
82
|
-
print(f"DEBUG: Values are equal: {current == previous}")
|
|
83
|
-
|
|
84
80
|
result = (current != previous) == self.has_changed
|
|
85
|
-
print(f"DEBUG: HasChanged
|
|
86
|
-
|
|
81
|
+
print(f"DEBUG: HasChanged {self.field} result={result}")
|
|
87
82
|
return result
|
|
88
83
|
|
|
89
84
|
|
|
@@ -23,17 +23,11 @@ def run(model_cls, event, new_records, old_records=None, ctx=None):
|
|
|
23
23
|
import traceback
|
|
24
24
|
|
|
25
25
|
stack = traceback.format_stack()
|
|
26
|
-
print(
|
|
27
|
-
f"DEBUG: engine.run called for {model_cls.__name__}.{event} with {len(new_records)} records"
|
|
28
|
-
)
|
|
29
|
-
print(f"DEBUG: Call stack (last 3 frames):")
|
|
30
|
-
for line in stack[-4:-1]: # Show last 3 frames before this one
|
|
31
|
-
print(f" {line.strip()}")
|
|
32
|
-
print(f"DEBUG: Total hooks found: {len(hooks)}")
|
|
26
|
+
print(f"DEBUG: engine.run {model_cls.__name__}.{event} {len(new_records)} records")
|
|
33
27
|
|
|
34
28
|
# Check if we're in a bypass context
|
|
35
29
|
if ctx and hasattr(ctx, 'bypass_hooks') and ctx.bypass_hooks:
|
|
36
|
-
print(f"DEBUG:
|
|
30
|
+
print(f"DEBUG: engine.run bypassed")
|
|
37
31
|
return
|
|
38
32
|
|
|
39
33
|
# For BEFORE_* events, run model.clean() first for validation
|
|
@@ -47,7 +41,7 @@ def run(model_cls, event, new_records, old_records=None, ctx=None):
|
|
|
47
41
|
|
|
48
42
|
# Process hooks
|
|
49
43
|
for handler_cls, method_name, condition, priority in hooks:
|
|
50
|
-
print(f"DEBUG: Processing
|
|
44
|
+
print(f"DEBUG: Processing {handler_cls.__name__}.{method_name}")
|
|
51
45
|
handler_instance = handler_cls()
|
|
52
46
|
func = getattr(handler_instance, method_name)
|
|
53
47
|
|
|
@@ -60,20 +54,16 @@ def run(model_cls, event, new_records, old_records=None, ctx=None):
|
|
|
60
54
|
strict=True,
|
|
61
55
|
):
|
|
62
56
|
if not condition:
|
|
63
|
-
print(f"DEBUG: No condition, adding record {new.pk if hasattr(new, 'pk') else 'No PK'}")
|
|
64
57
|
to_process_new.append(new)
|
|
65
58
|
to_process_old.append(original)
|
|
66
59
|
else:
|
|
67
60
|
condition_result = condition.check(new, original)
|
|
68
|
-
print(f"DEBUG: Condition {condition.__class__.__name__} check result: {condition_result} for record {new.pk if hasattr(new, 'pk') else 'No PK'}")
|
|
69
61
|
if condition_result:
|
|
70
62
|
to_process_new.append(new)
|
|
71
63
|
to_process_old.append(original)
|
|
72
64
|
|
|
73
65
|
if to_process_new:
|
|
74
|
-
print(
|
|
75
|
-
f"DEBUG: Executing hook {handler_cls.__name__}.{method_name} for {len(to_process_new)} records"
|
|
76
|
-
)
|
|
66
|
+
print(f"DEBUG: Executing {handler_cls.__name__}.{method_name} for {len(to_process_new)} records")
|
|
77
67
|
try:
|
|
78
68
|
func(
|
|
79
69
|
new_records=to_process_new,
|
|
@@ -82,5 +72,3 @@ def run(model_cls, event, new_records, old_records=None, ctx=None):
|
|
|
82
72
|
except Exception as e:
|
|
83
73
|
print(f"DEBUG: Hook execution failed: {e}")
|
|
84
74
|
raise
|
|
85
|
-
else:
|
|
86
|
-
print(f"DEBUG: No records to process for hook {handler_cls.__name__}.{method_name}")
|
|
@@ -78,12 +78,14 @@ class HookQuerySetMixin:
|
|
|
78
78
|
|
|
79
79
|
# If we're in a bulk operation context, skip hooks to prevent double execution
|
|
80
80
|
if current_bypass_hooks:
|
|
81
|
-
print(f"DEBUG: update
|
|
81
|
+
print(f"DEBUG: update skipping hooks (bulk context)")
|
|
82
82
|
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
83
83
|
else:
|
|
84
|
-
print(f"DEBUG: update
|
|
84
|
+
print(f"DEBUG: update running hooks (standalone)")
|
|
85
85
|
ctx = HookContext(model_cls, bypass_hooks=False)
|
|
86
|
-
# Run
|
|
86
|
+
# Run validation hooks first
|
|
87
|
+
engine.run(model_cls, VALIDATE_UPDATE, instances, originals, ctx=ctx)
|
|
88
|
+
# Then run BEFORE_UPDATE hooks
|
|
87
89
|
engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
|
|
88
90
|
|
|
89
91
|
# Use Django's built-in update logic directly
|
|
@@ -113,10 +115,10 @@ class HookQuerySetMixin:
|
|
|
113
115
|
|
|
114
116
|
# Run AFTER_UPDATE hooks only for standalone updates
|
|
115
117
|
if not current_bypass_hooks:
|
|
116
|
-
print(f"DEBUG: update
|
|
118
|
+
print(f"DEBUG: update running AFTER_UPDATE")
|
|
117
119
|
engine.run(model_cls, AFTER_UPDATE, instances, originals, ctx=ctx)
|
|
118
120
|
else:
|
|
119
|
-
print(f"DEBUG: update
|
|
121
|
+
print(f"DEBUG: update skipping AFTER_UPDATE (bulk context)")
|
|
120
122
|
|
|
121
123
|
return update_count
|
|
122
124
|
|
|
@@ -181,7 +183,7 @@ class HookQuerySetMixin:
|
|
|
181
183
|
engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
|
|
182
184
|
else:
|
|
183
185
|
ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
|
|
184
|
-
print(f"DEBUG:
|
|
186
|
+
print(f"DEBUG: bulk_create bypassed hooks")
|
|
185
187
|
|
|
186
188
|
# For MTI models, we need to handle them specially
|
|
187
189
|
if is_mti:
|
|
@@ -236,9 +238,7 @@ class HookQuerySetMixin:
|
|
|
236
238
|
f"bulk_update expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
|
|
237
239
|
)
|
|
238
240
|
|
|
239
|
-
print(f"DEBUG: bulk_update
|
|
240
|
-
print(f"DEBUG: bulk_update for model {model_cls.__name__} with {len(objs)} objects")
|
|
241
|
-
print(f"DEBUG: kwargs: {kwargs}")
|
|
241
|
+
print(f"DEBUG: bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}")
|
|
242
242
|
|
|
243
243
|
# Check for MTI
|
|
244
244
|
is_mti = False
|
|
@@ -248,35 +248,13 @@ class HookQuerySetMixin:
|
|
|
248
248
|
break
|
|
249
249
|
|
|
250
250
|
if not bypass_hooks:
|
|
251
|
-
print(f"DEBUG: bulk_update
|
|
252
|
-
original_map = {
|
|
253
|
-
obj.pk: obj
|
|
254
|
-
for obj in model_cls._base_manager.filter(
|
|
255
|
-
pk__in=[obj.pk for obj in objs]
|
|
256
|
-
)
|
|
257
|
-
}
|
|
258
|
-
originals = [original_map.get(obj.pk) for obj in objs]
|
|
259
|
-
|
|
251
|
+
print(f"DEBUG: bulk_update setting bypass_hooks=False (hooks will run in update())")
|
|
260
252
|
ctx = HookContext(model_cls, bypass_hooks=False)
|
|
261
|
-
|
|
262
|
-
# Run validation hooks first
|
|
263
|
-
if not bypass_validation:
|
|
264
|
-
engine.run(model_cls, VALIDATE_UPDATE, objs, originals, ctx=ctx)
|
|
265
|
-
|
|
266
|
-
# Then run business logic hooks
|
|
267
|
-
engine.run(model_cls, BEFORE_UPDATE, objs, originals, ctx=ctx)
|
|
268
|
-
|
|
269
|
-
# Detect modified fields during hooks
|
|
270
|
-
modified_fields = self._detect_modified_fields(objs, originals)
|
|
271
|
-
if modified_fields:
|
|
272
|
-
fields_set = set(fields)
|
|
273
|
-
fields_set.update(modified_fields)
|
|
274
|
-
fields = list(fields_set)
|
|
253
|
+
originals = [None] * len(objs) # Placeholder for after_update call
|
|
275
254
|
else:
|
|
276
|
-
print(f"DEBUG: bulk_update
|
|
255
|
+
print(f"DEBUG: bulk_update setting bypass_hooks=True (no hooks)")
|
|
277
256
|
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
278
|
-
|
|
279
|
-
originals = [None] * len(objs) # Ensure originals is defined for after_update call
|
|
257
|
+
originals = [None] * len(objs) # Ensure originals is defined for after_update call
|
|
280
258
|
|
|
281
259
|
# Handle auto_now fields like Django's update_or_create does
|
|
282
260
|
fields_set = set(fields)
|
|
@@ -301,22 +279,16 @@ class HookQuerySetMixin:
|
|
|
301
279
|
for k, v in kwargs.items()
|
|
302
280
|
if k not in ["bypass_hooks", "bypass_validation"]
|
|
303
281
|
}
|
|
304
|
-
print(f"DEBUG: Calling Django
|
|
305
|
-
# Call Django's bulk_update with hook suspension to prevent double execution
|
|
306
|
-
# Django's bulk_update internally calls .update() which would trigger our hooks again
|
|
307
|
-
print(f"DEBUG: About to call Django's bulk_update")
|
|
282
|
+
print(f"DEBUG: Calling Django bulk_update")
|
|
308
283
|
result = super().bulk_update(objs, fields, **django_kwargs)
|
|
309
|
-
print(f"DEBUG: Django
|
|
284
|
+
print(f"DEBUG: Django bulk_update done: {result}")
|
|
310
285
|
|
|
286
|
+
# Note: We don't run AFTER_UPDATE hooks here to prevent double execution
|
|
287
|
+
# The update() method will handle all hook execution based on thread-local state
|
|
311
288
|
if not bypass_hooks:
|
|
312
|
-
print(
|
|
313
|
-
f"DEBUG: bypass_hooks=False, running AFTER_UPDATE hooks for {len(objs)} objects"
|
|
314
|
-
)
|
|
315
|
-
engine.run(model_cls, AFTER_UPDATE, objs, originals, ctx=ctx)
|
|
289
|
+
print(f"DEBUG: bulk_update skipping AFTER_UPDATE (update() will handle)")
|
|
316
290
|
else:
|
|
317
|
-
print(
|
|
318
|
-
f"DEBUG: bypass_hooks=True, skipping AFTER_UPDATE hooks for {len(objs)} objects"
|
|
319
|
-
)
|
|
291
|
+
print(f"DEBUG: bulk_update bypassed hooks")
|
|
320
292
|
|
|
321
293
|
return result
|
|
322
294
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from django_bulk_hooks.priority import Priority
|
|
5
|
+
|
|
6
|
+
_hooks: dict[tuple[type, str], list[tuple[type, str, Callable, int]]] = {}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register_hook(
|
|
10
|
+
model, event, handler_cls, method_name, condition, priority: Union[int, Priority]
|
|
11
|
+
):
|
|
12
|
+
key = (model, event)
|
|
13
|
+
hooks = _hooks.setdefault(key, [])
|
|
14
|
+
hooks.append((handler_cls, method_name, condition, priority))
|
|
15
|
+
# keep sorted by priority
|
|
16
|
+
hooks.sort(key=lambda x: x[3])
|
|
17
|
+
print(f"DEBUG: Registered {handler_cls.__name__}.{method_name} for {model.__name__}.{event}")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_hooks(model, event):
|
|
21
|
+
key = (model, event)
|
|
22
|
+
hooks = _hooks.get(key, [])
|
|
23
|
+
print(f"DEBUG: get_hooks {model.__name__}.{event} found {len(hooks)} hooks")
|
|
24
|
+
return hooks
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def list_all_hooks():
|
|
28
|
+
"""Debug function to list all registered hooks"""
|
|
29
|
+
return _hooks
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "django-bulk-hooks"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.218"
|
|
4
4
|
description = "Hook-style hooks for Django bulk operations like bulk_create and bulk_update."
|
|
5
5
|
authors = ["Konrad Beck <konrad.beck@merchantcapital.co.za>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
from collections.abc import Callable
|
|
2
|
-
from typing import Union
|
|
3
|
-
|
|
4
|
-
from django_bulk_hooks.priority import Priority
|
|
5
|
-
|
|
6
|
-
_hooks: dict[tuple[type, str], list[tuple[type, str, Callable, int]]] = {}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def register_hook(
|
|
10
|
-
model, event, handler_cls, method_name, condition, priority: Union[int, Priority]
|
|
11
|
-
):
|
|
12
|
-
key = (model, event)
|
|
13
|
-
hooks = _hooks.setdefault(key, [])
|
|
14
|
-
hooks.append((handler_cls, method_name, condition, priority))
|
|
15
|
-
# keep sorted by priority
|
|
16
|
-
hooks.sort(key=lambda x: x[3])
|
|
17
|
-
print(f"DEBUG: Registered hook {handler_cls.__name__}.{method_name} for {model.__name__}.{event} with condition: {condition}")
|
|
18
|
-
print(f"DEBUG: Model class: {model} (id: {id(model)})")
|
|
19
|
-
print(f"DEBUG: Total hooks for {model.__name__}.{event}: {len(hooks)}")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def get_hooks(model, event):
|
|
23
|
-
key = (model, event)
|
|
24
|
-
hooks = _hooks.get(key, [])
|
|
25
|
-
print(f"DEBUG: get_hooks called for {model.__name__}.{event}, found {len(hooks)} hooks")
|
|
26
|
-
print(f"DEBUG: Hook key: {key}")
|
|
27
|
-
print(f"DEBUG: Model class: {model} (id: {id(model)})")
|
|
28
|
-
print(f"DEBUG: All registered hook keys: {list(_hooks.keys())}")
|
|
29
|
-
for hook in hooks:
|
|
30
|
-
handler_cls, method_name, condition, priority = hook
|
|
31
|
-
print(f"DEBUG: Hook: {handler_cls.__name__}.{method_name} with condition: {condition}")
|
|
32
|
-
return hooks
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def list_all_hooks():
|
|
36
|
-
"""Debug function to list all registered hooks"""
|
|
37
|
-
return _hooks
|
|
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
|