django-bulk-hooks 0.1.242__tar.gz → 0.1.244__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.

Files changed (17) hide show
  1. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/PKG-INFO +1 -1
  2. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/django_bulk_hooks/queryset.py +39 -34
  3. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/pyproject.toml +1 -1
  4. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/LICENSE +0 -0
  5. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/README.md +0 -0
  6. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/django_bulk_hooks/__init__.py +0 -0
  7. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/django_bulk_hooks/conditions.py +0 -0
  8. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/django_bulk_hooks/constants.py +0 -0
  9. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/django_bulk_hooks/context.py +0 -0
  10. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/django_bulk_hooks/decorators.py +0 -0
  11. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/django_bulk_hooks/engine.py +0 -0
  12. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/django_bulk_hooks/enums.py +0 -0
  13. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/django_bulk_hooks/handler.py +0 -0
  14. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/django_bulk_hooks/manager.py +0 -0
  15. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/django_bulk_hooks/models.py +0 -0
  16. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/django_bulk_hooks/priority.py +0 -0
  17. {django_bulk_hooks-0.1.242 → django_bulk_hooks-0.1.244}/django_bulk_hooks/registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-bulk-hooks
3
- Version: 0.1.242
3
+ Version: 0.1.244
4
4
  Summary: Hook-style hooks for Django bulk operations like bulk_create and bulk_update.
5
5
  Home-page: https://github.com/AugendLimited/django-bulk-hooks
6
6
  License: MIT
@@ -125,41 +125,53 @@ class HookQuerySetMixin:
125
125
  # to in-memory instances before running BEFORE_UPDATE hooks. Hooks must not
126
126
  # receive unresolved expression objects.
127
127
  per_object_values = get_bulk_update_value_map()
128
- for obj in instances:
129
- if per_object_values and obj.pk in per_object_values:
130
- for field, value in per_object_values[obj.pk].items():
131
- setattr(obj, field, value)
132
- else:
133
- for field, value in kwargs.items():
134
- # Skip assigning expression-like objects (they will be handled at DB level)
135
- is_subquery = isinstance(value, Subquery)
136
- is_expression_like = hasattr(value, "resolve_expression")
137
- if is_subquery or is_expression_like:
138
- # Special-case Value() which can be unwrapped safely
139
- if isinstance(value, Value):
140
- try:
141
- setattr(obj, field, value.value)
142
- except Exception:
143
- # If Value cannot be unwrapped for any reason, skip assignment
128
+
129
+ # For Subquery updates, skip all in-memory field assignments to prevent
130
+ # expression objects from reaching hooks
131
+ if has_subquery:
132
+ logger.debug(
133
+ "Skipping in-memory field assignments due to Subquery detection"
134
+ )
135
+ else:
136
+ for obj in instances:
137
+ if per_object_values and obj.pk in per_object_values:
138
+ for field, value in per_object_values[obj.pk].items():
139
+ setattr(obj, field, value)
140
+ else:
141
+ for field, value in kwargs.items():
142
+ # Skip assigning expression-like objects (they will be handled at DB level)
143
+ is_expression_like = hasattr(value, "resolve_expression")
144
+ if is_expression_like:
145
+ # Special-case Value() which can be unwrapped safely
146
+ if isinstance(value, Value):
147
+ try:
148
+ setattr(obj, field, value.value)
149
+ except Exception:
150
+ # If Value cannot be unwrapped for any reason, skip assignment
151
+ continue
152
+ else:
153
+ # Do not assign unresolved expressions to in-memory objects
154
+ logger.debug(
155
+ f"Skipping assignment of expression {type(value).__name__} to field {field}"
156
+ )
144
157
  continue
145
158
  else:
146
- # Do not assign unresolved expressions to in-memory objects
147
- continue
148
- else:
149
- setattr(obj, field, value)
159
+ setattr(obj, field, value)
150
160
 
151
- # Check if we're in a bulk operation context to prevent double hook execution
161
+ # Salesforce-style trigger behavior: Always run hooks, rely on Django's stack overflow protection
152
162
  from django_bulk_hooks.context import get_bypass_hooks
153
163
 
154
164
  current_bypass_hooks = get_bypass_hooks()
155
165
 
156
- # If we're in a bulk operation context, skip hooks to prevent double execution
166
+ # Only skip hooks if explicitly bypassed (not for recursion prevention)
157
167
  if current_bypass_hooks:
158
- logger.debug("update: skipping hooks (bulk context)")
168
+ logger.debug("update: hooks explicitly bypassed")
159
169
  ctx = HookContext(model_cls, bypass_hooks=True)
160
170
  else:
161
- logger.debug("update: running hooks (standalone)")
171
+ # Always run hooks - Django will handle stack overflow protection
172
+ logger.debug("update: running hooks with Salesforce-style behavior")
162
173
  ctx = HookContext(model_cls, bypass_hooks=False)
174
+
163
175
  # Run validation hooks first
164
176
  engine.run(model_cls, VALIDATE_UPDATE, instances, originals, ctx=ctx)
165
177
  # Then run BEFORE_UPDATE hooks
@@ -385,19 +397,12 @@ class HookQuerySetMixin:
385
397
  getattr(refreshed_instance, field.name),
386
398
  )
387
399
 
388
- # Run AFTER_UPDATE hooks for standalone updates or subquery operations
389
- # For subquery operations, we need to run hooks even if we're in a bulk context
390
- # because subqueries bypass the normal object-level update flow
391
- should_run_hooks = (
392
- not current_bypass_hooks
393
- or has_subquery # Always run hooks for subquery operations
394
- )
395
-
396
- if should_run_hooks:
400
+ # Salesforce-style: Always run AFTER_UPDATE hooks unless explicitly bypassed
401
+ if not current_bypass_hooks:
397
402
  logger.debug("update: running AFTER_UPDATE")
398
403
  engine.run(model_cls, AFTER_UPDATE, instances, originals, ctx=ctx)
399
404
  else:
400
- logger.debug("update: skipping AFTER_UPDATE (bulk context)")
405
+ logger.debug("update: AFTER_UPDATE explicitly bypassed")
401
406
 
402
407
  return update_count
403
408
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-bulk-hooks"
3
- version = "0.1.242"
3
+ version = "0.1.244"
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"