django-bulk-hooks 0.2.74__tar.gz → 0.2.76__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 (27) hide show
  1. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/PKG-INFO +1 -1
  2. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/__init__.py +0 -3
  3. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/dispatcher.py +49 -41
  4. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/pyproject.toml +1 -1
  5. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/LICENSE +0 -0
  6. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/README.md +0 -0
  7. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/changeset.py +0 -0
  8. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/conditions.py +0 -0
  9. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/constants.py +0 -0
  10. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/context.py +0 -0
  11. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/decorators.py +0 -0
  12. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/enums.py +0 -0
  13. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/factory.py +0 -0
  14. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/handler.py +0 -0
  15. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/helpers.py +0 -0
  16. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/manager.py +0 -0
  17. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/models.py +0 -0
  18. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/operations/__init__.py +0 -0
  19. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/operations/analyzer.py +0 -0
  20. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/operations/bulk_executor.py +0 -0
  21. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/operations/coordinator.py +0 -0
  22. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/operations/field_utils.py +0 -0
  23. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/operations/mti_handler.py +0 -0
  24. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/operations/mti_plans.py +0 -0
  25. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/operations/record_classifier.py +0 -0
  26. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/queryset.py +0 -0
  27. {django_bulk_hooks-0.2.74 → django_bulk_hooks-0.2.76}/django_bulk_hooks/registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-bulk-hooks
3
- Version: 0.2.74
3
+ Version: 0.2.76
4
4
  Summary: Hook-style hooks for Django bulk operations like bulk_create and bulk_update.
5
5
  License: MIT
6
6
  Keywords: django,bulk,hooks
@@ -25,9 +25,6 @@ from django_bulk_hooks.operations import BulkOperationCoordinator
25
25
  from django_bulk_hooks.operations import ModelAnalyzer
26
26
  from django_bulk_hooks.operations import MTIHandler
27
27
 
28
- # Add NullHandler to prevent logging messages if the application doesn't configure logging
29
- logging.getLogger(__name__).addHandler(logging.NullHandler())
30
-
31
28
  __all__ = [
32
29
  "BulkHookManager",
33
30
  "HookClass",
@@ -191,7 +191,55 @@ class HookDispatcher:
191
191
  changeset: ChangeSet with all record changes
192
192
  event: The hook event (e.g., 'before_create')
193
193
  """
194
- # Preload relationships needed for condition evaluation (skip if already done upfront)
194
+ # Use DI factory to create handler instance EARLY to access method decorators
195
+ from django_bulk_hooks.factory import create_hook_instance
196
+
197
+ handler = create_hook_instance(handler_cls)
198
+ method = getattr(handler, method_name)
199
+
200
+ # PRELOAD @select_related RELATIONSHIPS BEFORE CONDITION EVALUATION
201
+ # This ensures both conditions and hook methods have access to preloaded relationships
202
+
203
+ # Check if method has @select_related decorator
204
+ preload_func = getattr(method, "_select_related_preload", None)
205
+ if preload_func:
206
+ # Preload relationships to prevent N+1 queries in both conditions and hook methods
207
+ try:
208
+ model_cls_override = getattr(handler, "model_cls", None)
209
+
210
+ # Get FK fields being updated to avoid preloading conflicting relationships
211
+ skip_fields = changeset.operation_meta.get("fk_fields_being_updated", set())
212
+
213
+ # Preload for new_records (needed for condition evaluation and hook execution)
214
+ if changeset.new_records:
215
+ preload_func(
216
+ changeset.new_records,
217
+ model_cls=model_cls_override,
218
+ skip_fields=skip_fields,
219
+ )
220
+
221
+ # Also preload for old_records (for conditions that check previous values)
222
+ if changeset.old_records:
223
+ preload_func(
224
+ changeset.old_records,
225
+ model_cls=model_cls_override,
226
+ skip_fields=skip_fields,
227
+ )
228
+
229
+ # Mark that relationships have been preloaded to avoid duplicate condition preloading
230
+ changeset.operation_meta['relationships_preloaded'] = True
231
+ logger.debug(f"🔗 @select_related: Preloaded relationships for {handler_cls.__name__}.{method_name}")
232
+
233
+ except Exception as e:
234
+ logger.warning(f"Failed to preload relationships for {handler_cls.__name__}.{method_name}: {e}")
235
+
236
+ # SPECIAL HANDLING: Explicit @select_related support for BEFORE_CREATE hooks
237
+ # (This can stay for additional BEFORE_CREATE-specific logic if needed)
238
+ select_related_fields = getattr(method, "_select_related_fields", None)
239
+ if select_related_fields and event == "before_create" and changeset.new_records:
240
+ self._preload_select_related_for_before_create(changeset, select_related_fields)
241
+
242
+ # NOW condition evaluation is safe - relationships are preloaded
195
243
  if condition:
196
244
  # Skip per-hook preloading if relationships were already preloaded upfront
197
245
  if not changeset.operation_meta.get('relationships_preloaded', False):
@@ -226,46 +274,6 @@ class HookDispatcher:
226
274
  # No condition, use full changeset
227
275
  filtered_changeset = changeset
228
276
 
229
- # Use DI factory to create handler instance
230
- from django_bulk_hooks.factory import create_hook_instance
231
-
232
- handler = create_hook_instance(handler_cls)
233
- method = getattr(handler, method_name)
234
-
235
- # SPECIAL HANDLING: Explicit @select_related support for BEFORE_CREATE hooks
236
- # This provides guaranteed bulk preloading to eliminate N+1 queries
237
- select_related_fields = getattr(method, "_select_related_fields", None)
238
- if select_related_fields and event == "before_create" and filtered_changeset.new_records:
239
- self._preload_select_related_for_before_create(filtered_changeset, select_related_fields)
240
-
241
- # Check if method has @select_related decorator (fallback for other cases)
242
- preload_func = getattr(method, "_select_related_preload", None)
243
- if preload_func:
244
- # Preload relationships to prevent N+1 queries
245
- try:
246
- model_cls_override = getattr(handler, "model_cls", None)
247
-
248
- # Get FK fields being updated to avoid preloading conflicting relationships
249
- skip_fields = changeset.operation_meta.get("fk_fields_being_updated", set())
250
-
251
- # Preload for new_records
252
- if filtered_changeset.new_records:
253
- preload_func(
254
- filtered_changeset.new_records,
255
- model_cls=model_cls_override,
256
- skip_fields=skip_fields,
257
- )
258
-
259
- # Also preload for old_records (for conditions that check previous values)
260
- if filtered_changeset.old_records:
261
- preload_func(
262
- filtered_changeset.old_records,
263
- model_cls=model_cls_override,
264
- skip_fields=skip_fields,
265
- )
266
- except Exception:
267
- pass # Preload failed, continue without it
268
-
269
277
  # Execute hook with ChangeSet
270
278
  #
271
279
  # ARCHITECTURE NOTE: Hook Contract
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-bulk-hooks"
3
- version = "0.2.74"
3
+ version = "0.2.76"
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"