django-bulk-hooks 0.1.227__py3-none-any.whl → 0.1.228__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.
Potentially problematic release.
This version of django-bulk-hooks might be problematic. Click here for more details.
- django_bulk_hooks/engine.py +4 -2
- django_bulk_hooks/enums.py +1 -15
- django_bulk_hooks/handler.py +38 -25
- django_bulk_hooks/priority.py +6 -6
- django_bulk_hooks/queryset.py +43 -12
- django_bulk_hooks/registry.py +6 -2
- {django_bulk_hooks-0.1.227.dist-info → django_bulk_hooks-0.1.228.dist-info}/METADATA +1 -1
- django_bulk_hooks-0.1.228.dist-info/RECORD +17 -0
- django_bulk_hooks-0.1.227.dist-info/RECORD +0 -17
- {django_bulk_hooks-0.1.227.dist-info → django_bulk_hooks-0.1.228.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.1.227.dist-info → django_bulk_hooks-0.1.228.dist-info}/WHEEL +0 -0
django_bulk_hooks/engine.py
CHANGED
|
@@ -43,7 +43,8 @@ def run(model_cls, event, new_records, old_records=None, ctx=None):
|
|
|
43
43
|
logger.error("Validation failed for %s: %s", instance, e)
|
|
44
44
|
raise
|
|
45
45
|
|
|
46
|
-
# Process hooks in priority order
|
|
46
|
+
# Process hooks in priority order (highest priority first)
|
|
47
|
+
# Registry now sorts by priority (highest first)
|
|
47
48
|
for handler_cls, method_name, condition, priority in hooks:
|
|
48
49
|
logger.debug(f"Processing {handler_cls.__name__}.{method_name} (priority: {priority})")
|
|
49
50
|
|
|
@@ -80,8 +81,9 @@ def run(model_cls, event, new_records, old_records=None, ctx=None):
|
|
|
80
81
|
try:
|
|
81
82
|
func(
|
|
82
83
|
new_records=to_process_new,
|
|
83
|
-
old_records=to_process_old if any(to_process_old) else None,
|
|
84
|
+
old_records=to_process_old if any(x is not None for x in to_process_old) else None,
|
|
84
85
|
)
|
|
85
86
|
except Exception as e:
|
|
86
87
|
logger.error(f"Hook execution failed in {handler_cls.__name__}.{method_name}: {e}")
|
|
88
|
+
# Re-raise the exception to ensure proper error handling
|
|
87
89
|
raise
|
django_bulk_hooks/enums.py
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class Priority(IntEnum):
|
|
5
|
-
"""
|
|
6
|
-
Named priorities for django-bulk-hooks hooks.
|
|
7
|
-
Replaces module-level constants with a clean IntEnum.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
HIGHEST = 0 # runs first
|
|
11
|
-
HIGH = 25 # runs early
|
|
12
|
-
NORMAL = 50 # default ordering
|
|
13
|
-
LOW = 75 # runs late
|
|
14
|
-
LOWEST = 100 # runs last
|
|
15
|
-
|
|
1
|
+
from django_bulk_hooks.priority import Priority
|
|
16
2
|
|
|
17
3
|
DEFAULT_PRIORITY = Priority.NORMAL
|
django_bulk_hooks/handler.py
CHANGED
|
@@ -61,36 +61,43 @@ class HookContextState:
|
|
|
61
61
|
return hook_vars.model
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
Hook = HookContextState()
|
|
65
|
-
|
|
66
|
-
|
|
67
64
|
class HookMeta(type):
|
|
68
65
|
"""Metaclass that automatically registers hooks when Hook classes are defined."""
|
|
69
66
|
|
|
70
67
|
def __new__(mcs, name, bases, namespace):
|
|
71
68
|
cls = super().__new__(mcs, name, bases, namespace)
|
|
72
69
|
|
|
73
|
-
# Register hooks for this class
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
70
|
+
# Register hooks for this class, including inherited methods
|
|
71
|
+
# We need to check all methods in the MRO to handle inheritance
|
|
72
|
+
for attr_name in dir(cls):
|
|
73
|
+
if attr_name.startswith('_'):
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
attr = getattr(cls, attr_name)
|
|
78
|
+
if callable(attr) and hasattr(attr, "hooks_hooks"):
|
|
79
|
+
for model_cls, event, condition, priority in attr.hooks_hooks:
|
|
80
|
+
# Create a unique key for this hook registration
|
|
81
|
+
key = (model_cls, event, cls, attr_name)
|
|
82
|
+
|
|
83
|
+
# Register the hook
|
|
84
|
+
register_hook(
|
|
85
|
+
model=model_cls,
|
|
86
|
+
event=event,
|
|
87
|
+
handler_cls=cls,
|
|
88
|
+
method_name=attr_name,
|
|
89
|
+
condition=condition,
|
|
90
|
+
priority=priority,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
logger.debug(
|
|
94
|
+
f"Registered hook {cls.__name__}.{attr_name} "
|
|
95
|
+
f"for {model_cls.__name__}.{event} with priority {priority}"
|
|
96
|
+
)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
# Skip attributes that can't be accessed
|
|
99
|
+
logger.debug(f"Skipping attribute {attr_name}: {e}")
|
|
100
|
+
continue
|
|
94
101
|
|
|
95
102
|
return cls
|
|
96
103
|
|
|
@@ -132,7 +139,7 @@ class Hook(metaclass=HookMeta):
|
|
|
132
139
|
hook_vars.event = event
|
|
133
140
|
hook_vars.model = model
|
|
134
141
|
|
|
135
|
-
hooks =
|
|
142
|
+
hooks = get_hooks(model, event)
|
|
136
143
|
|
|
137
144
|
def _execute():
|
|
138
145
|
new_local = new_records or []
|
|
@@ -161,6 +168,8 @@ class Hook(metaclass=HookMeta):
|
|
|
161
168
|
logger.exception(
|
|
162
169
|
"Error in hook %s.%s", handler_cls.__name__, method_name
|
|
163
170
|
)
|
|
171
|
+
# Re-raise the exception to ensure proper error handling
|
|
172
|
+
raise
|
|
164
173
|
|
|
165
174
|
conn = transaction.get_connection()
|
|
166
175
|
try:
|
|
@@ -174,3 +183,7 @@ class Hook(metaclass=HookMeta):
|
|
|
174
183
|
hook_vars.event = None
|
|
175
184
|
hook_vars.model = None
|
|
176
185
|
hook_vars.depth -= 1
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# Create a global Hook instance for context access
|
|
189
|
+
HookContext = HookContextState()
|
django_bulk_hooks/priority.py
CHANGED
|
@@ -5,12 +5,12 @@ class Priority(IntEnum):
|
|
|
5
5
|
"""
|
|
6
6
|
Named priorities for django-bulk-hooks hooks.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
Hooks are sorted in
|
|
8
|
+
Higher values run earlier (higher priority).
|
|
9
|
+
Hooks are sorted in descending order.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
LOWEST = 0 # runs last
|
|
13
|
+
LOW = 25 # runs later
|
|
14
14
|
NORMAL = 50 # default ordering
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
HIGH = 75 # runs early
|
|
16
|
+
HIGHEST = 100 # runs first
|
django_bulk_hooks/queryset.py
CHANGED
|
@@ -95,22 +95,49 @@ class HookQuerySetMixin:
|
|
|
95
95
|
|
|
96
96
|
# Run hooks only if not bypassed
|
|
97
97
|
if not bypass_hooks:
|
|
98
|
+
ctx = HookContext(model_cls)
|
|
98
99
|
# Run VALIDATE_UPDATE hooks
|
|
99
|
-
engine.run(model_cls, VALIDATE_UPDATE, instances, originals,
|
|
100
|
+
engine.run(model_cls, VALIDATE_UPDATE, instances, originals, ctx=ctx)
|
|
100
101
|
|
|
101
|
-
#
|
|
102
|
-
|
|
102
|
+
# For subqueries, we need to compute the values and apply them to instances
|
|
103
|
+
# before running BEFORE_UPDATE hooks
|
|
104
|
+
if has_subquery:
|
|
105
|
+
# Create a temporary update to compute the values
|
|
106
|
+
# We'll use a subquery to compute values without actually updating
|
|
107
|
+
for field_name, value in kwargs.items():
|
|
108
|
+
if (hasattr(value, "query") and hasattr(value.query, "model")) or \
|
|
109
|
+
(hasattr(value, "get_source_expressions") and value.get_source_expressions()):
|
|
110
|
+
# This is a complex expression - compute it for each instance
|
|
111
|
+
for instance in instances:
|
|
112
|
+
# Create a single-instance queryset to compute the value
|
|
113
|
+
single_qs = model_cls._base_manager.filter(pk=instance.pk)
|
|
114
|
+
computed_values = single_qs.annotate(computed_field=value).values_list('computed_field', flat=True)
|
|
115
|
+
if computed_values:
|
|
116
|
+
setattr(instance, field_name, computed_values[0])
|
|
117
|
+
else:
|
|
118
|
+
# For simple updates, apply the values directly
|
|
119
|
+
for obj in instances:
|
|
120
|
+
for field, value in kwargs.items():
|
|
121
|
+
setattr(obj, field, value)
|
|
122
|
+
|
|
123
|
+
# Run BEFORE_UPDATE hooks with updated instances
|
|
124
|
+
engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
|
|
103
125
|
|
|
104
126
|
if has_subquery:
|
|
105
127
|
# For complex expressions, use Django's native update
|
|
106
128
|
# This handles Subquery, Case, F expressions, etc. correctly
|
|
107
129
|
result = super().update(**kwargs)
|
|
130
|
+
|
|
131
|
+
# After updating with complex expressions, we need to reload the instances
|
|
132
|
+
# to get the computed values for the AFTER_UPDATE hooks
|
|
133
|
+
if not bypass_hooks:
|
|
134
|
+
# Reload instances to get computed values
|
|
135
|
+
updated_instances = list(model_cls._base_manager.filter(pk__in=pks))
|
|
136
|
+
# Maintain the original order
|
|
137
|
+
updated_map = {obj.pk: obj for obj in updated_instances}
|
|
138
|
+
instances = [updated_map.get(obj.pk, obj) for obj in instances]
|
|
108
139
|
else:
|
|
109
|
-
# For simple field updates,
|
|
110
|
-
for obj in instances:
|
|
111
|
-
for field, value in kwargs.items():
|
|
112
|
-
setattr(obj, field, value)
|
|
113
|
-
|
|
140
|
+
# For simple field updates, instances have already been updated in the hook section
|
|
114
141
|
# Perform database update using Django's native bulk_update
|
|
115
142
|
# We use the base manager to avoid recursion
|
|
116
143
|
base_manager = model_cls._base_manager
|
|
@@ -120,7 +147,8 @@ class HookQuerySetMixin:
|
|
|
120
147
|
|
|
121
148
|
# Run AFTER_UPDATE hooks only if not bypassed
|
|
122
149
|
if not bypass_hooks:
|
|
123
|
-
|
|
150
|
+
ctx = HookContext(model_cls)
|
|
151
|
+
engine.run(model_cls, AFTER_UPDATE, instances, originals, ctx=ctx)
|
|
124
152
|
|
|
125
153
|
return result
|
|
126
154
|
|
|
@@ -273,14 +301,16 @@ class HookQuerySetMixin:
|
|
|
273
301
|
|
|
274
302
|
# Run VALIDATE_UPDATE hooks
|
|
275
303
|
if not bypass_validation:
|
|
304
|
+
ctx = HookContext(model_cls)
|
|
276
305
|
engine.run(
|
|
277
|
-
model_cls, VALIDATE_UPDATE, objs, originals,
|
|
306
|
+
model_cls, VALIDATE_UPDATE, objs, originals, ctx=ctx
|
|
278
307
|
)
|
|
279
308
|
|
|
280
309
|
# Run BEFORE_UPDATE hooks
|
|
281
310
|
if not bypass_hooks:
|
|
311
|
+
ctx = HookContext(model_cls)
|
|
282
312
|
engine.run(
|
|
283
|
-
model_cls, BEFORE_UPDATE, objs, originals,
|
|
313
|
+
model_cls, BEFORE_UPDATE, objs, originals, ctx=ctx
|
|
284
314
|
)
|
|
285
315
|
|
|
286
316
|
# Perform database update using Django's native bulk_update
|
|
@@ -290,7 +320,8 @@ class HookQuerySetMixin:
|
|
|
290
320
|
|
|
291
321
|
# Run AFTER_UPDATE hooks
|
|
292
322
|
if not bypass_hooks:
|
|
293
|
-
|
|
323
|
+
ctx = HookContext(model_cls)
|
|
324
|
+
engine.run(model_cls, AFTER_UPDATE, objs, originals, ctx=ctx)
|
|
294
325
|
|
|
295
326
|
return result
|
|
296
327
|
|
django_bulk_hooks/registry.py
CHANGED
|
@@ -42,8 +42,8 @@ def register_hook(
|
|
|
42
42
|
# Add the hook
|
|
43
43
|
hooks.append((handler_cls, method_name, condition, priority))
|
|
44
44
|
|
|
45
|
-
# Sort by priority (
|
|
46
|
-
hooks.sort(key=lambda x: x[3])
|
|
45
|
+
# Sort by priority (highest numbers execute first)
|
|
46
|
+
hooks.sort(key=lambda x: x[3], reverse=True)
|
|
47
47
|
|
|
48
48
|
logger.debug(
|
|
49
49
|
f"Registered {handler_cls.__name__}.{method_name} "
|
|
@@ -71,6 +71,10 @@ def get_hooks(model, event):
|
|
|
71
71
|
# Log hook discovery for debugging
|
|
72
72
|
if hooks:
|
|
73
73
|
logger.debug(f"Found {len(hooks)} hooks for {model.__name__}.{event}")
|
|
74
|
+
for handler_cls, method_name, condition, priority in hooks:
|
|
75
|
+
logger.debug(f" - {handler_cls.__name__}.{method_name} (priority: {priority})")
|
|
76
|
+
else:
|
|
77
|
+
logger.debug(f"No hooks found for {model.__name__}.{event}")
|
|
74
78
|
|
|
75
79
|
return hooks
|
|
76
80
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
django_bulk_hooks/__init__.py,sha256=uUgpnb9AWjIAcWNpCMqBcOewSnpJjJYH6cjPbQkzoNU,140
|
|
2
|
+
django_bulk_hooks/conditions.py,sha256=V_f3Di2uCVUjoyfiU4BQCHmI4uUIRSRroApDcXlvnso,6349
|
|
3
|
+
django_bulk_hooks/constants.py,sha256=3x1H1fSUUNo0DZONN7GUVDuySZctTR-jtByBHmAIX5w,303
|
|
4
|
+
django_bulk_hooks/context.py,sha256=_NbGWTq9s66g0vbFIaqN4GlIHWQmFg3EQ44qY8YvvEg,1537
|
|
5
|
+
django_bulk_hooks/decorators.py,sha256=WD7Jn7QAvY8F4wOsYlIpjoM9-FdHXSKB7hH9ot-lkYQ,4896
|
|
6
|
+
django_bulk_hooks/engine.py,sha256=81NUQAppn2eCsY1Ao6SgvyXsowmxUnabYgY0GHWoo-o,3294
|
|
7
|
+
django_bulk_hooks/enums.py,sha256=1pKFHXq1iCceAUitCj9v6WM3PVJpYJx5ZNZD1RzCJUU,87
|
|
8
|
+
django_bulk_hooks/handler.py,sha256=sd5kPOZKwKQnxmQGir5u10ZkTHRdN4Hynl_oIWEIYzQ,5857
|
|
9
|
+
django_bulk_hooks/manager.py,sha256=nfWiwU5-yAoxdnQsUMohxtyCpkV0MBv6X3wmipr9eQY,3697
|
|
10
|
+
django_bulk_hooks/models.py,sha256=mj4f93L64CN1XBS29RlS02WnZjCNoUkai97vKqjgZQ8,4575
|
|
11
|
+
django_bulk_hooks/priority.py,sha256=EGFBbRmX_LhwRYFCKzM8I5m8NGCsUEVJp2pfNTcoHe4,378
|
|
12
|
+
django_bulk_hooks/queryset.py,sha256=L_C0ICEnD2TxOljn0RckX0W8xB-jLa_2995ZuKDRVJY,34448
|
|
13
|
+
django_bulk_hooks/registry.py,sha256=EJBNVDo6VOz2s5zsJbpppiM6JwRmmXLyfwSKXYaT4Fs,2802
|
|
14
|
+
django_bulk_hooks-0.1.228.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
15
|
+
django_bulk_hooks-0.1.228.dist-info/METADATA,sha256=FFM1S50BoNZtBfQyDGhJ4dtvQTsFY1OvNUmrbmHFk0Q,9743
|
|
16
|
+
django_bulk_hooks-0.1.228.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
17
|
+
django_bulk_hooks-0.1.228.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
django_bulk_hooks/__init__.py,sha256=uUgpnb9AWjIAcWNpCMqBcOewSnpJjJYH6cjPbQkzoNU,140
|
|
2
|
-
django_bulk_hooks/conditions.py,sha256=V_f3Di2uCVUjoyfiU4BQCHmI4uUIRSRroApDcXlvnso,6349
|
|
3
|
-
django_bulk_hooks/constants.py,sha256=3x1H1fSUUNo0DZONN7GUVDuySZctTR-jtByBHmAIX5w,303
|
|
4
|
-
django_bulk_hooks/context.py,sha256=_NbGWTq9s66g0vbFIaqN4GlIHWQmFg3EQ44qY8YvvEg,1537
|
|
5
|
-
django_bulk_hooks/decorators.py,sha256=WD7Jn7QAvY8F4wOsYlIpjoM9-FdHXSKB7hH9ot-lkYQ,4896
|
|
6
|
-
django_bulk_hooks/engine.py,sha256=wiO6HvZkBSHzt1Q3IpXmVppJl30zlsoTheuPPCrGqdU,3118
|
|
7
|
-
django_bulk_hooks/enums.py,sha256=Zo8_tJzuzZ2IKfVc7gZ-0tWPT8q1QhqZbAyoh9ZVJbs,381
|
|
8
|
-
django_bulk_hooks/handler.py,sha256=IRgJ6uoyD1NifqzZpL2YxOqInJ4MFlS3vZkAa5ZTIVo,5210
|
|
9
|
-
django_bulk_hooks/manager.py,sha256=nfWiwU5-yAoxdnQsUMohxtyCpkV0MBv6X3wmipr9eQY,3697
|
|
10
|
-
django_bulk_hooks/models.py,sha256=mj4f93L64CN1XBS29RlS02WnZjCNoUkai97vKqjgZQ8,4575
|
|
11
|
-
django_bulk_hooks/priority.py,sha256=HG_2D35nga68lBCZmSXTcplXrjFoRgZFRDOy4ROKonY,376
|
|
12
|
-
django_bulk_hooks/queryset.py,sha256=xgnuwl8Ha9IhVzKEvjvCX5jUuRZgc0tWYwaKreuDvpw,32478
|
|
13
|
-
django_bulk_hooks/registry.py,sha256=h59veo8Qh4Afj8ZP_4Jqr-2S6ebXjUJ7pJTdfqzfXkE,2572
|
|
14
|
-
django_bulk_hooks-0.1.227.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
15
|
-
django_bulk_hooks-0.1.227.dist-info/METADATA,sha256=rY5wk0lHKwxaqEdbVz3BLw3DnRBjMhXSN4syZLuWGX4,9743
|
|
16
|
-
django_bulk_hooks-0.1.227.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
17
|
-
django_bulk_hooks-0.1.227.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|