django-bulk-hooks 0.1.184__py3-none-any.whl → 0.1.187__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.

@@ -178,9 +178,17 @@ class HookQuerySet(models.QuerySet):
178
178
  """
179
179
  Bulk update objects in the database with MTI support.
180
180
  """
181
+ print(f"\n=== BULK UPDATE DEBUG ===")
182
+ print(f"Model: {self.model.__name__}")
183
+ print(f"Number of objects: {len(objs)}")
184
+ print(f"Fields: {fields}")
185
+ print(f"Bypass hooks: {bypass_hooks}")
186
+ print(f"Bypass validation: {bypass_validation}")
187
+
181
188
  model_cls = self.model
182
189
 
183
190
  if not objs:
191
+ print("No objects to update")
184
192
  return []
185
193
 
186
194
  if any(not isinstance(obj, model_cls) for obj in objs):
@@ -194,6 +202,11 @@ class HookQuerySet(models.QuerySet):
194
202
  if parent._meta.concrete_model is not model_cls._meta.concrete_model:
195
203
  is_mti = True
196
204
  break
205
+
206
+ print(f"Is MTI: {is_mti}")
207
+ print(f"Model concrete model: {model_cls._meta.concrete_model.__name__}")
208
+ for parent in model_cls._meta.all_parents:
209
+ print(f" Parent {parent.__name__}: concrete_model = {parent._meta.concrete_model.__name__}")
197
210
 
198
211
  if not bypass_hooks:
199
212
  # Load originals for hook comparison
@@ -220,11 +233,14 @@ class HookQuerySet(models.QuerySet):
220
233
  fields_set = set(fields)
221
234
  fields_set.update(modified_fields)
222
235
  fields = list(fields_set)
236
+ print(f"Modified fields detected: {modified_fields}")
223
237
 
224
238
  # Handle MTI models differently
225
239
  if is_mti:
240
+ print("Using MTI bulk update logic")
226
241
  result = self._mti_bulk_update(objs, fields, **kwargs)
227
242
  else:
243
+ print("Using standard Django bulk_update")
228
244
  # For single-table models, use Django's built-in bulk_update
229
245
  django_kwargs = {
230
246
  k: v
@@ -236,6 +252,7 @@ class HookQuerySet(models.QuerySet):
236
252
  if not bypass_hooks:
237
253
  engine.run(model_cls, AFTER_UPDATE, objs, originals, ctx=ctx)
238
254
 
255
+ print(f"Bulk update result: {result}")
239
256
  return result
240
257
 
241
258
  def _detect_modified_fields(self, new_instances, original_instances):
@@ -280,18 +297,29 @@ class HookQuerySet(models.QuerySet):
280
297
  Get the complete inheritance chain from root parent to current model.
281
298
  Returns list of model classes in order: [RootParent, Parent, Child]
282
299
  """
300
+ print(f"\n=== GET INHERITANCE CHAIN DEBUG ===")
301
+ print(f"Current model: {self.model.__name__}")
302
+
283
303
  chain = []
284
304
  current_model = self.model
285
305
  while current_model:
306
+ print(f"Processing model: {current_model.__name__}")
286
307
  if not current_model._meta.proxy:
287
308
  chain.append(current_model)
309
+ print(f" Added to chain: {current_model.__name__}")
310
+ else:
311
+ print(f" Skipped proxy model: {current_model.__name__}")
312
+
288
313
  parents = [
289
314
  parent
290
315
  for parent in current_model._meta.parents.keys()
291
316
  if not parent._meta.proxy
292
317
  ]
318
+ print(f" Parents: {[p.__name__ for p in parents]}")
293
319
  current_model = parents[0] if parents else None
320
+
294
321
  chain.reverse()
322
+ print(f"Final inheritance chain: {[m.__name__ for m in chain]}")
295
323
  return chain
296
324
 
297
325
  def _mti_bulk_create(self, objs, inheritance_chain=None, **kwargs):
@@ -321,13 +349,13 @@ class HookQuerySet(models.QuerySet):
321
349
  with transaction.atomic(using=self.db, savepoint=False):
322
350
  for i in range(0, len(objs), batch_size):
323
351
  batch = objs[i : i + batch_size]
324
- batch_result = self._process_mti_batch(
352
+ batch_result = self._process_mti_bulk_create_batch(
325
353
  batch, inheritance_chain, **django_kwargs
326
354
  )
327
355
  created_objects.extend(batch_result)
328
356
  return created_objects
329
357
 
330
- def _process_mti_batch(self, batch, inheritance_chain, **kwargs):
358
+ def _process_mti_bulk_create_batch(self, batch, inheritance_chain, **kwargs):
331
359
  """
332
360
  Process a single batch of objects through the inheritance chain.
333
361
  Implements Django's suggested workaround #2: O(n) normal inserts into parent
@@ -534,8 +562,27 @@ class HookQuerySet(models.QuerySet):
534
562
  Custom bulk update implementation for MTI models.
535
563
  Updates each table in the inheritance chain efficiently using Django's batch_size.
536
564
  """
565
+ print(f"\n=== MTI BULK UPDATE DEBUG ===")
566
+ print(f"Model: {self.model.__name__}")
567
+ print(f"Number of objects: {len(objs)}")
568
+ print(f"Fields to update: {fields}")
569
+
537
570
  model_cls = self.model
538
571
  inheritance_chain = self._get_inheritance_chain()
572
+ print(f"Inheritance chain: {[m.__name__ for m in inheritance_chain]}")
573
+
574
+ # Remove custom hook kwargs before passing to Django internals
575
+ django_kwargs = {
576
+ k: v
577
+ for k, v in kwargs.items()
578
+ if k not in ["bypass_hooks", "bypass_validation"]
579
+ }
580
+
581
+ # Safety check to prevent infinite recursion
582
+ if len(inheritance_chain) > 10: # Arbitrary limit to prevent infinite loops
583
+ raise ValueError(
584
+ "Inheritance chain too deep - possible infinite recursion detected"
585
+ )
539
586
 
540
587
  # Group fields by model in the inheritance chain
541
588
  field_groups = {}
@@ -547,12 +594,47 @@ class HookQuerySet(models.QuerySet):
547
594
  if model not in field_groups:
548
595
  field_groups[model] = []
549
596
  field_groups[model].append(field_name)
597
+ print(f"Field '{field_name}' belongs to model '{model.__name__}'")
550
598
  break
599
+
600
+ print(f"Field groups: {field_groups}")
551
601
 
552
- # Update each table in the inheritance chain
602
+ # Process in batches
603
+ batch_size = django_kwargs.get("batch_size") or len(objs)
553
604
  total_updated = 0
605
+
606
+ print(f"Processing in batches of size: {batch_size}")
607
+
608
+ with transaction.atomic(using=self.db, savepoint=False):
609
+ for i in range(0, len(objs), batch_size):
610
+ batch = objs[i : i + batch_size]
611
+ print(f"\n--- Processing batch {i//batch_size + 1} ({len(batch)} objects) ---")
612
+ batch_result = self._process_mti_bulk_update_batch(
613
+ batch, field_groups, inheritance_chain, **django_kwargs
614
+ )
615
+ total_updated += batch_result
616
+ print(f"Batch {i//batch_size + 1} updated {batch_result} rows")
617
+
618
+ print(f"\n=== TOTAL UPDATED: {total_updated} ===")
619
+ return total_updated
620
+
621
+ def _process_mti_bulk_update_batch(self, batch, field_groups, inheritance_chain, **kwargs):
622
+ """
623
+ Process a single batch of objects for MTI bulk update.
624
+ Updates each table in the inheritance chain for the batch.
625
+ """
626
+ total_updated = 0
627
+
628
+ print(f"Processing batch with {len(batch)} objects")
629
+ print(f"Field groups: {field_groups}")
630
+
631
+ # Update each table in the inheritance chain
554
632
  for model, model_fields in field_groups.items():
633
+ print(f"\n--- Updating model: {model.__name__} ---")
634
+ print(f"Fields to update: {model_fields}")
635
+
555
636
  if not model_fields:
637
+ print("No fields to update, skipping")
556
638
  continue
557
639
 
558
640
  # For MTI, we need to handle parent links correctly
@@ -560,8 +642,9 @@ class HookQuerySet(models.QuerySet):
560
642
  # Child models use the parent link to reference the root PK
561
643
  if model == inheritance_chain[0]:
562
644
  # Root model - use primary keys directly
563
- pks = [obj.pk for obj in objs]
645
+ pks = [obj.pk for obj in batch]
564
646
  filter_field = 'pk'
647
+ print(f"Root model - using PKs: {pks}")
565
648
  else:
566
649
  # Child model - use parent link field
567
650
  parent_link = None
@@ -571,30 +654,52 @@ class HookQuerySet(models.QuerySet):
571
654
  break
572
655
 
573
656
  if parent_link is None:
574
- # This shouldn't happen in proper MTI, but handle gracefully
657
+ print(f"No parent link found for {model.__name__}, skipping")
575
658
  continue
576
659
 
660
+ print(f"Parent link field: {parent_link.name} ({parent_link.attname})")
661
+
577
662
  # Get the parent link values (these should be the same as the root PKs)
578
- pks = [getattr(obj, parent_link.attname) for obj in objs]
663
+ pks = [getattr(obj, parent_link.attname) for obj in batch]
579
664
  filter_field = parent_link.attname
665
+ print(f"Child model - using parent link values: {pks}")
580
666
 
581
667
  if pks:
582
668
  base_qs = model._base_manager.using(self.db)
669
+ print(f"Filter field: {filter_field}")
670
+ print(f"PKs to filter by: {pks}")
671
+
672
+ # Check if records exist
673
+ existing_count = base_qs.filter(**{f"{filter_field}__in": pks}).count()
674
+ print(f"Existing records with these PKs: {existing_count}")
583
675
 
584
- # Build CASE statements for each field
676
+ # Build CASE statements for each field to perform a single bulk update
585
677
  case_statements = {}
586
678
  for field_name in model_fields:
587
679
  field = model._meta.get_field(field_name)
588
680
  when_statements = []
589
681
 
590
- for pk, obj in zip(pks, objs):
682
+ print(f"Building CASE statement for field: {field_name}")
683
+ for pk, obj in zip(pks, batch):
591
684
  value = getattr(obj, field_name)
685
+ print(f" PK {pk}: {field_name} = {value}")
592
686
  when_statements.append(When(**{filter_field: pk}, then=Value(value, output_field=field)))
593
687
 
594
688
  case_statements[field_name] = Case(*when_statements, output_field=field)
595
689
 
596
- # Execute the update using the appropriate filter field
597
- updated_count = base_qs.filter(**{f"{filter_field}__in": pks}).update(**case_statements)
598
- total_updated += updated_count
690
+ print(f"Case statements built: {list(case_statements.keys())}")
691
+
692
+ # Execute a single bulk update for all objects in this model
693
+ try:
694
+ updated_count = base_qs.filter(**{f"{filter_field}__in": pks}).update(**case_statements)
695
+ print(f"UPDATE QUERY EXECUTED - Updated {updated_count} rows")
696
+ total_updated += updated_count
697
+ except Exception as e:
698
+ print(f"ERROR during update: {e}")
699
+ import traceback
700
+ traceback.print_exc()
701
+ else:
702
+ print("No PKs found, skipping update")
599
703
 
704
+ print(f"Batch total updated: {total_updated}")
600
705
  return total_updated
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-bulk-hooks
3
- Version: 0.1.184
3
+ Version: 0.1.187
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
@@ -9,9 +9,9 @@ django_bulk_hooks/handler.py,sha256=xZt8iNdYF-ACz-MnKMY0co6scWINU5V5wC1lyDn844k,
9
9
  django_bulk_hooks/manager.py,sha256=KLEjpQRt4WlzgBAf_X3XOAPUQM8Jmc1fIt8yr62FPQc,3044
10
10
  django_bulk_hooks/models.py,sha256=7fnx5xd4HWXfLVlFhhiRzR92JRWFEuxgk6aSWLEsyJg,3996
11
11
  django_bulk_hooks/priority.py,sha256=HG_2D35nga68lBCZmSXTcplXrjFoRgZFRDOy4ROKonY,376
12
- django_bulk_hooks/queryset.py,sha256=gQvwa506wYi206B13I-lwkZi5Ua9nWcKctwCCgLGuaQ,25614
12
+ django_bulk_hooks/queryset.py,sha256=DYt_oK8g7D1oHGIT9gzE--F9qdXKaMEgPJq5x-t9cSw,30664
13
13
  django_bulk_hooks/registry.py,sha256=-mQBizJ06nz_tajZBinViKx_uP2Tbc1tIpTEMv7lwKA,705
14
- django_bulk_hooks-0.1.184.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
- django_bulk_hooks-0.1.184.dist-info/METADATA,sha256=zj7hawgW0Z19ZUhnTcTMcx-pMXy_PvepfzUdstynZjU,6951
16
- django_bulk_hooks-0.1.184.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
17
- django_bulk_hooks-0.1.184.dist-info/RECORD,,
14
+ django_bulk_hooks-0.1.187.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
+ django_bulk_hooks-0.1.187.dist-info/METADATA,sha256=TgXRIiryaRvkIg_IJ5l9xRaGCwkQqsAahFpNff_eymQ,6951
16
+ django_bulk_hooks-0.1.187.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
17
+ django_bulk_hooks-0.1.187.dist-info/RECORD,,