django-bulk-hooks 0.2.72__py3-none-any.whl → 0.2.74__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/dispatcher.py +35 -10
- django_bulk_hooks/operations/coordinator.py +49 -0
- {django_bulk_hooks-0.2.72.dist-info → django_bulk_hooks-0.2.74.dist-info}/METADATA +1 -1
- {django_bulk_hooks-0.2.72.dist-info → django_bulk_hooks-0.2.74.dist-info}/RECORD +6 -6
- {django_bulk_hooks-0.2.72.dist-info → django_bulk_hooks-0.2.74.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.2.72.dist-info → django_bulk_hooks-0.2.74.dist-info}/WHEEL +0 -0
django_bulk_hooks/dispatcher.py
CHANGED
|
@@ -191,13 +191,17 @@ class HookDispatcher:
|
|
|
191
191
|
changeset: ChangeSet with all record changes
|
|
192
192
|
event: The hook event (e.g., 'before_create')
|
|
193
193
|
"""
|
|
194
|
-
#
|
|
194
|
+
# Preload relationships needed for condition evaluation (skip if already done upfront)
|
|
195
195
|
if condition:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
logger.info(f"
|
|
200
|
-
|
|
196
|
+
# Skip per-hook preloading if relationships were already preloaded upfront
|
|
197
|
+
if not changeset.operation_meta.get('relationships_preloaded', False):
|
|
198
|
+
condition_relationships = self._extract_condition_relationships(condition, changeset.model_cls)
|
|
199
|
+
logger.info(f"🔍 CONDITION: {handler_cls.__name__}.{method_name} has condition, extracted relationships: {condition_relationships}")
|
|
200
|
+
if condition_relationships:
|
|
201
|
+
logger.info(f"🔗 PRELOADING: Preloading condition relationships for {len(changeset.changes)} records")
|
|
202
|
+
self._preload_condition_relationships(changeset, condition_relationships)
|
|
203
|
+
else:
|
|
204
|
+
logger.debug(f"🔍 CONDITION: {handler_cls.__name__}.{method_name} has condition (relationships already preloaded)")
|
|
201
205
|
|
|
202
206
|
# Filter records based on condition (now safe - relationships are preloaded)
|
|
203
207
|
if condition:
|
|
@@ -405,17 +409,38 @@ class HookDispatcher:
|
|
|
405
409
|
if hasattr(preloaded_obj, rel):
|
|
406
410
|
setattr(obj, rel, getattr(preloaded_obj, rel))
|
|
407
411
|
|
|
408
|
-
# Handle unsaved new_records by preloading their FK targets
|
|
412
|
+
# Handle unsaved new_records by preloading their FK targets (bulk query to avoid N+1)
|
|
409
413
|
if changeset.new_records:
|
|
414
|
+
# Collect FK IDs for each relationship from unsaved records
|
|
415
|
+
field_ids_map = {rel: set() for rel in relationship_list}
|
|
416
|
+
|
|
410
417
|
for obj in changeset.new_records:
|
|
411
418
|
if obj.pk is None: # Unsaved object
|
|
412
419
|
for rel in relationship_list:
|
|
413
420
|
if hasattr(obj, f'{rel}_id'):
|
|
414
421
|
rel_id = getattr(obj, f'{rel}_id')
|
|
415
422
|
if rel_id:
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
423
|
+
field_ids_map[rel].add(rel_id)
|
|
424
|
+
|
|
425
|
+
# Bulk load relationships for unsaved records
|
|
426
|
+
field_objects_map = {}
|
|
427
|
+
for rel, ids in field_ids_map.items():
|
|
428
|
+
if not ids:
|
|
429
|
+
continue
|
|
430
|
+
try:
|
|
431
|
+
rel_model = getattr(changeset.model_cls._meta.get_field(rel).remote_field, 'model')
|
|
432
|
+
field_objects_map[rel] = rel_model.objects.in_bulk(ids)
|
|
433
|
+
except Exception:
|
|
434
|
+
field_objects_map[rel] = {}
|
|
435
|
+
|
|
436
|
+
# Attach relationships to unsaved records
|
|
437
|
+
for obj in changeset.new_records:
|
|
438
|
+
if obj.pk is None: # Unsaved object
|
|
439
|
+
for rel in relationship_list:
|
|
440
|
+
rel_id = getattr(obj, f'{rel}_id', None)
|
|
441
|
+
if rel_id and rel in field_objects_map:
|
|
442
|
+
rel_obj = field_objects_map[rel].get(rel_id)
|
|
443
|
+
if rel_obj:
|
|
419
444
|
setattr(obj, rel, rel_obj)
|
|
420
445
|
|
|
421
446
|
def _preload_select_related_for_before_create(self, changeset, select_related_fields):
|
|
@@ -711,6 +711,17 @@ class BulkOperationCoordinator:
|
|
|
711
711
|
models_in_chain = self._get_models_in_chain(changeset.model_cls)
|
|
712
712
|
logger.debug(f"🔗 MTI_CHAIN_START: {len(models_in_chain)} models in chain for {changeset.model_cls.__name__}")
|
|
713
713
|
|
|
714
|
+
# Extract and preload relationships needed by hook conditions upfront
|
|
715
|
+
# This prevents duplicate queries by avoiding per-hook preloading
|
|
716
|
+
condition_relationships = self._extract_condition_relationships_for_operation(changeset, models_in_chain)
|
|
717
|
+
if condition_relationships:
|
|
718
|
+
logger.info(f"🔗 BULK PRELOAD: Preloading {len(condition_relationships)} condition relationships for {changeset.model_cls.__name__} hooks")
|
|
719
|
+
self.dispatcher._preload_condition_relationships(changeset, condition_relationships)
|
|
720
|
+
# Mark that relationships have been preloaded to avoid per-hook duplication
|
|
721
|
+
changeset.operation_meta['relationships_preloaded'] = True
|
|
722
|
+
else:
|
|
723
|
+
logger.info(f"🔗 BULK PRELOAD: No condition relationships to preload for {changeset.model_cls.__name__} hooks")
|
|
724
|
+
|
|
714
725
|
# VALIDATE phase - for all models in chain
|
|
715
726
|
if not bypass_validation:
|
|
716
727
|
self._dispatch_hooks_for_models(models_in_chain, changeset, f"validate_{event_prefix}")
|
|
@@ -878,6 +889,8 @@ class BulkOperationCoordinator:
|
|
|
878
889
|
from django_bulk_hooks.helpers import build_changeset_for_create
|
|
879
890
|
|
|
880
891
|
create_changeset = build_changeset_for_create(self.model_cls, created_objects)
|
|
892
|
+
# Mark that relationships have been preloaded to avoid per-hook duplication
|
|
893
|
+
create_changeset.operation_meta['relationships_preloaded'] = True
|
|
881
894
|
|
|
882
895
|
self._dispatch_hooks_for_models(models_in_chain, create_changeset, "after_create", bypass_hooks=False)
|
|
883
896
|
|
|
@@ -895,12 +908,48 @@ class BulkOperationCoordinator:
|
|
|
895
908
|
update_kwargs={}, # Empty since we don't know specific fields
|
|
896
909
|
old_records_map=old_records_map,
|
|
897
910
|
)
|
|
911
|
+
# Mark that relationships have been preloaded to avoid per-hook duplication
|
|
912
|
+
update_changeset.operation_meta['relationships_preloaded'] = True
|
|
898
913
|
|
|
899
914
|
self._dispatch_hooks_for_models(models_in_chain, update_changeset, "after_update", bypass_hooks=False)
|
|
900
915
|
|
|
901
916
|
# Clean up temporary metadata
|
|
902
917
|
self._cleanup_upsert_metadata(result_objects)
|
|
903
918
|
|
|
919
|
+
def _extract_condition_relationships_for_operation(self, changeset, models_in_chain):
|
|
920
|
+
"""
|
|
921
|
+
Extract relationships needed by hook conditions for this specific operation.
|
|
922
|
+
|
|
923
|
+
This is different from _extract_hook_relationships which gets ALL possible relationships
|
|
924
|
+
for queryset operations. This method only gets relationships needed by hooks that will
|
|
925
|
+
actually run in this operation.
|
|
926
|
+
|
|
927
|
+
Args:
|
|
928
|
+
changeset: The changeset for this operation
|
|
929
|
+
models_in_chain: List of model classes in inheritance chain
|
|
930
|
+
|
|
931
|
+
Returns:
|
|
932
|
+
set: Set of relationship field names to preload
|
|
933
|
+
"""
|
|
934
|
+
relationships = set()
|
|
935
|
+
dispatcher = self.dispatcher
|
|
936
|
+
|
|
937
|
+
# Get the events that will run in this operation
|
|
938
|
+
event_prefix = changeset.operation_type
|
|
939
|
+
events_to_check = [f"validate_{event_prefix}", f"before_{event_prefix}", f"after_{event_prefix}"]
|
|
940
|
+
|
|
941
|
+
for model_cls in models_in_chain:
|
|
942
|
+
for event in events_to_check:
|
|
943
|
+
hooks = dispatcher.registry.get_hooks(model_cls, event)
|
|
944
|
+
|
|
945
|
+
for handler_cls, method_name, condition, priority in hooks:
|
|
946
|
+
# Only extract relationships from conditions (not @select_related)
|
|
947
|
+
if condition:
|
|
948
|
+
condition_relationships = dispatcher._extract_condition_relationships(condition, model_cls)
|
|
949
|
+
relationships.update(condition_relationships)
|
|
950
|
+
|
|
951
|
+
return relationships
|
|
952
|
+
|
|
904
953
|
def _extract_hook_relationships(self):
|
|
905
954
|
"""
|
|
906
955
|
Extract all relationship paths that hooks might access for this model and its MTI parents.
|
|
@@ -4,7 +4,7 @@ django_bulk_hooks/conditions.py,sha256=ar4pGjtxLKmgSIlO4S6aZFKmaBNchLtxMmWpkn4g9
|
|
|
4
4
|
django_bulk_hooks/constants.py,sha256=PxpEETaO6gdENcTPoXS586lerGKVP3nmjpDvOkmhYxI,509
|
|
5
5
|
django_bulk_hooks/context.py,sha256=mqaC5-yESDTA5ruI7fuXlt8qSgKuOFp0mjq7h1-4HdQ,1926
|
|
6
6
|
django_bulk_hooks/decorators.py,sha256=TdkO4FJyFrVU2zqK6Y_6JjEJ4v3nbKkk7aa22jN10sk,11994
|
|
7
|
-
django_bulk_hooks/dispatcher.py,sha256=
|
|
7
|
+
django_bulk_hooks/dispatcher.py,sha256=LfqSDE1NK-cgAUDwaN542_d4ohSxBeiOAhbOqklZfPo,23659
|
|
8
8
|
django_bulk_hooks/enums.py,sha256=Zo8_tJzuzZ2IKfVc7gZ-0tWPT8q1QhqZbAyoh9ZVJbs,381
|
|
9
9
|
django_bulk_hooks/factory.py,sha256=ezrVM5U023KZqOBbJXb6lYUP-pE7WJmi8Olh2Ew-7RA,18085
|
|
10
10
|
django_bulk_hooks/handler.py,sha256=SRCrMzgolrruTkvMnYBFmXLR-ABiw0JiH3605PEdCZM,4207
|
|
@@ -14,14 +14,14 @@ django_bulk_hooks/models.py,sha256=TWN_F-SsLGPx9jrkNT9pmJFR5VsZ0Z_QaVOZOmt7bpw,2
|
|
|
14
14
|
django_bulk_hooks/operations/__init__.py,sha256=BtJYjmRhe_sScivLsniDaZmBkm0ZLvcmzXFKL7QY2Xg,550
|
|
15
15
|
django_bulk_hooks/operations/analyzer.py,sha256=Pz8mc-EL8KDOfLQFYiRuN-r0OmINW3nIBhRJJCma-yo,10360
|
|
16
16
|
django_bulk_hooks/operations/bulk_executor.py,sha256=po8V_2H3ULiE0RYJ-wbaRIz52SKhss81UHwuQjlz3H8,26214
|
|
17
|
-
django_bulk_hooks/operations/coordinator.py,sha256=
|
|
17
|
+
django_bulk_hooks/operations/coordinator.py,sha256=J3lsxsN7lgBkeiSmu1Kw0tvU_M9sBTozdLZ7HO7Nb-w,42489
|
|
18
18
|
django_bulk_hooks/operations/field_utils.py,sha256=cQ9w4xdk-z3PrMLFvRzVV07Wc0D2qbpSepwoupqwQH8,7888
|
|
19
19
|
django_bulk_hooks/operations/mti_handler.py,sha256=x1uNvP8MkidifPp_AMp4nffsdW3dz3izV6SeaLJ0DaA,26247
|
|
20
20
|
django_bulk_hooks/operations/mti_plans.py,sha256=HIRJgogHPpm6MV7nZZ-sZhMLUnozpZPV2SzwQHLRzYc,3667
|
|
21
21
|
django_bulk_hooks/operations/record_classifier.py,sha256=It85hJC2K-UsEOLbTR-QBdY5UPV-acQIJ91TSGa7pYo,7053
|
|
22
22
|
django_bulk_hooks/queryset.py,sha256=tHt1U2O9Hvozg9sdn5MjzAk_I6wDU-LupuKFTfv1SiQ,7449
|
|
23
23
|
django_bulk_hooks/registry.py,sha256=4HxP1mVK2z4VzvlohbEw2359wM21UJZJYagJJ1komM0,7947
|
|
24
|
-
django_bulk_hooks-0.2.
|
|
25
|
-
django_bulk_hooks-0.2.
|
|
26
|
-
django_bulk_hooks-0.2.
|
|
27
|
-
django_bulk_hooks-0.2.
|
|
24
|
+
django_bulk_hooks-0.2.74.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
25
|
+
django_bulk_hooks-0.2.74.dist-info/METADATA,sha256=TKjaRj-VJoMK_WiZVl7TNPf9yay5he233hmsMAFmYJI,10555
|
|
26
|
+
django_bulk_hooks-0.2.74.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
27
|
+
django_bulk_hooks-0.2.74.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|