django-bulk-hooks 0.1.221__tar.gz → 0.1.223__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.221 → django_bulk_hooks-0.1.223}/PKG-INFO +3 -3
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/django_bulk_hooks/queryset.py +66 -42
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/pyproject.toml +1 -1
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/LICENSE +0 -0
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/README.md +0 -0
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/django_bulk_hooks/__init__.py +0 -0
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/django_bulk_hooks/conditions.py +0 -0
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/django_bulk_hooks/constants.py +0 -0
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/django_bulk_hooks/context.py +0 -0
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/django_bulk_hooks/decorators.py +0 -0
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/django_bulk_hooks/engine.py +0 -0
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/django_bulk_hooks/enums.py +0 -0
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/django_bulk_hooks/handler.py +0 -0
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/django_bulk_hooks/manager.py +0 -0
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/django_bulk_hooks/models.py +0 -0
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/django_bulk_hooks/priority.py +0 -0
- {django_bulk_hooks-0.1.221 → django_bulk_hooks-0.1.223}/django_bulk_hooks/registry.py +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: django-bulk-hooks
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.223
|
|
4
4
|
Summary: Hook-style hooks for Django bulk operations like bulk_create and bulk_update.
|
|
5
|
+
Home-page: https://github.com/AugendLimited/django-bulk-hooks
|
|
5
6
|
License: MIT
|
|
6
7
|
Keywords: django,bulk,hooks
|
|
7
8
|
Author: Konrad Beck
|
|
@@ -13,7 +14,6 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
13
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
16
|
Requires-Dist: Django (>=4.0)
|
|
16
|
-
Project-URL: Homepage, https://github.com/AugendLimited/django-bulk-hooks
|
|
17
17
|
Project-URL: Repository, https://github.com/AugendLimited/django-bulk-hooks
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
|
|
2
3
|
from django.db import models, transaction
|
|
3
4
|
from django.db.models import AutoField, Case, Field, Value, When
|
|
4
5
|
|
|
@@ -70,58 +71,77 @@ class HookQuerySetMixin:
|
|
|
70
71
|
for value in kwargs.values()
|
|
71
72
|
)
|
|
72
73
|
|
|
73
|
-
#
|
|
74
|
+
# Check if we're in a bulk operation context to prevent double hook execution
|
|
75
|
+
from django_bulk_hooks.context import get_bypass_hooks
|
|
76
|
+
|
|
77
|
+
current_bypass_hooks = get_bypass_hooks()
|
|
78
|
+
|
|
79
|
+
# Apply field updates to instances for all cases (needed for hook inspection)
|
|
74
80
|
for obj in instances:
|
|
75
81
|
for field, value in kwargs.items():
|
|
82
|
+
# For subquery fields, set the original Subquery object temporarily
|
|
83
|
+
# We'll resolve it after database update if needed
|
|
76
84
|
setattr(obj, field, value)
|
|
77
85
|
|
|
78
|
-
# Check if we're in a bulk operation context to prevent double hook execution
|
|
79
|
-
from django_bulk_hooks.context import get_bypass_hooks
|
|
80
|
-
current_bypass_hooks = get_bypass_hooks()
|
|
81
|
-
|
|
82
86
|
# If we're in a bulk operation context, skip hooks to prevent double execution
|
|
83
87
|
if current_bypass_hooks:
|
|
84
|
-
logger.debug("update: skipping hooks (bulk context)")
|
|
85
88
|
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
89
|
+
# For bulk operations without hooks, execute update
|
|
90
|
+
update_count = super().update(**kwargs)
|
|
86
91
|
else:
|
|
87
|
-
logger.debug("update: running hooks (standalone)")
|
|
88
92
|
ctx = HookContext(model_cls, bypass_hooks=False)
|
|
89
|
-
# Run validation hooks first
|
|
90
|
-
engine.run(model_cls, VALIDATE_UPDATE, instances, originals, ctx=ctx)
|
|
91
|
-
# Then run BEFORE_UPDATE hooks
|
|
92
|
-
engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
|
|
93
|
-
|
|
94
|
-
# Use Django's built-in update logic directly
|
|
95
|
-
# Call the base QuerySet implementation to avoid recursion
|
|
96
|
-
update_count = super().update(**kwargs)
|
|
97
|
-
|
|
98
|
-
# If we used Subquery objects, refresh the instances to get computed values
|
|
99
|
-
if has_subquery and instances:
|
|
100
|
-
# Simple refresh of model fields without fetching related objects
|
|
101
|
-
# Subquery updates only affect the model's own fields, not relationships
|
|
102
|
-
refreshed_instances = {
|
|
103
|
-
obj.pk: obj for obj in model_cls._base_manager.filter(pk__in=pks)
|
|
104
|
-
}
|
|
105
93
|
|
|
106
|
-
#
|
|
107
|
-
|
|
108
|
-
if
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
94
|
+
# For subquery cases, we need special handling
|
|
95
|
+
if has_subquery:
|
|
96
|
+
# Run validation hooks first with Subquery objects (if validation doesn't access them)
|
|
97
|
+
try:
|
|
98
|
+
engine.run(
|
|
99
|
+
model_cls, VALIDATE_UPDATE, instances, originals, ctx=ctx
|
|
100
|
+
)
|
|
101
|
+
except (TypeError, ValueError) as e:
|
|
102
|
+
# If validation fails due to Subquery comparison, skip validation for subquery updates
|
|
103
|
+
# This is a limitation - validation hooks cannot easily work with unresolved subqueries
|
|
104
|
+
logger.warning(
|
|
105
|
+
f"Skipping validation hooks for subquery update due to: {e}"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Execute the database update first to compute subquery values
|
|
109
|
+
update_count = super().update(**kwargs)
|
|
110
|
+
|
|
111
|
+
# Refresh instances to get computed subquery values BEFORE running BEFORE hooks
|
|
112
|
+
refreshed_instances = {
|
|
113
|
+
obj.pk: obj for obj in model_cls._base_manager.filter(pk__in=pks)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Update instances in memory with computed values
|
|
117
|
+
for instance in instances:
|
|
118
|
+
if instance.pk in refreshed_instances:
|
|
119
|
+
refreshed_instance = refreshed_instances[instance.pk]
|
|
120
|
+
# Update all fields except primary key with the computed values
|
|
121
|
+
for field in model_cls._meta.fields:
|
|
122
|
+
if field.name != "id":
|
|
123
|
+
setattr(
|
|
124
|
+
instance,
|
|
125
|
+
field.name,
|
|
126
|
+
getattr(refreshed_instance, field.name),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Now run BEFORE_UPDATE hooks with resolved values
|
|
130
|
+
# Note: This is a trade-off - BEFORE hooks run after DB update for subquery cases
|
|
131
|
+
engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
|
|
132
|
+
else:
|
|
133
|
+
# Normal case without subqueries - run hooks in proper order
|
|
134
|
+
# Run validation hooks first
|
|
135
|
+
engine.run(model_cls, VALIDATE_UPDATE, instances, originals, ctx=ctx)
|
|
136
|
+
# Then run BEFORE_UPDATE hooks
|
|
137
|
+
engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
|
|
138
|
+
|
|
139
|
+
# Execute update
|
|
140
|
+
update_count = super().update(**kwargs)
|
|
118
141
|
|
|
119
142
|
# Run AFTER_UPDATE hooks only for standalone updates
|
|
120
143
|
if not current_bypass_hooks:
|
|
121
|
-
logger.debug("update: running AFTER_UPDATE")
|
|
122
144
|
engine.run(model_cls, AFTER_UPDATE, instances, originals, ctx=ctx)
|
|
123
|
-
else:
|
|
124
|
-
logger.debug("update: skipping AFTER_UPDATE (bulk context)")
|
|
125
145
|
|
|
126
146
|
return update_count
|
|
127
147
|
|
|
@@ -180,12 +200,12 @@ class HookQuerySetMixin:
|
|
|
180
200
|
|
|
181
201
|
# Fire hooks before DB ops
|
|
182
202
|
if not bypass_hooks:
|
|
183
|
-
ctx = HookContext(model_cls, bypass_hooks=False)
|
|
203
|
+
ctx = HookContext(model_cls, bypass_hooks=False) # Pass bypass_hooks
|
|
184
204
|
if not bypass_validation:
|
|
185
205
|
engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
|
|
186
206
|
engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
|
|
187
207
|
else:
|
|
188
|
-
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
208
|
+
ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
|
|
189
209
|
logger.debug("bulk_create bypassed hooks")
|
|
190
210
|
|
|
191
211
|
# For MTI models, we need to handle them specially
|
|
@@ -241,7 +261,9 @@ class HookQuerySetMixin:
|
|
|
241
261
|
f"bulk_update expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
|
|
242
262
|
)
|
|
243
263
|
|
|
244
|
-
logger.debug(
|
|
264
|
+
logger.debug(
|
|
265
|
+
f"bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}"
|
|
266
|
+
)
|
|
245
267
|
|
|
246
268
|
# Check for MTI
|
|
247
269
|
is_mti = False
|
|
@@ -257,7 +279,9 @@ class HookQuerySetMixin:
|
|
|
257
279
|
else:
|
|
258
280
|
logger.debug("bulk_update: hooks bypassed")
|
|
259
281
|
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
260
|
-
originals = [None] * len(
|
|
282
|
+
originals = [None] * len(
|
|
283
|
+
objs
|
|
284
|
+
) # Ensure originals is defined for after_update call
|
|
261
285
|
|
|
262
286
|
# Handle auto_now fields like Django's update_or_create does
|
|
263
287
|
fields_set = set(fields)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "django-bulk-hooks"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.223"
|
|
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"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|