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

@@ -3,55 +3,24 @@ from django.db import models
3
3
  from django_bulk_hooks.queryset import HookQuerySet, HookQuerySetMixin
4
4
 
5
5
 
6
- def inject_bulk_hook_behavior(queryset):
7
- """
8
- Dynamically inject bulk hook behavior into any queryset.
9
- This follows the industry-standard pattern for cooperative queryset extensions.
10
-
11
- Args:
12
- queryset: Any Django QuerySet instance
13
-
14
- Returns:
15
- A new queryset instance with bulk hook functionality added
16
- """
17
- if isinstance(queryset, HookQuerySetMixin):
18
- # Already has hook functionality, return as-is
19
- return queryset
20
-
21
- # Create a new class that inherits from both HookQuerySetMixin and the queryset's class
22
- HookedQuerySetClass = type(
23
- "HookedQuerySet",
24
- (HookQuerySetMixin, queryset.__class__),
25
- {
26
- '__module__': 'django_bulk_hooks.queryset',
27
- '__doc__': f'Dynamically created queryset with bulk hook functionality for {queryset.__class__.__name__}'
28
- }
29
- )
30
-
31
- # Create a new instance with the same parameters
32
- new_queryset = HookedQuerySetClass(
33
- model=queryset.model,
34
- query=queryset.query,
35
- using=queryset._db,
36
- hints=queryset._hints
37
- )
38
-
39
- # Copy any additional attributes that might be important
40
- for attr, value in queryset.__dict__.items():
41
- if not hasattr(new_queryset, attr):
42
- setattr(new_queryset, attr, value)
43
-
44
- return new_queryset
45
-
46
-
47
6
  class BulkHookManager(models.Manager):
48
7
  def get_queryset(self):
49
8
  # Use super().get_queryset() to let Django and MRO build the queryset
50
9
  # This ensures cooperation with other managers
51
10
  base_queryset = super().get_queryset()
52
11
 
53
- # Inject our bulk hook behavior into the queryset
54
- return inject_bulk_hook_behavior(base_queryset)
12
+ # If the base queryset already has hook functionality, return it as-is
13
+ if isinstance(base_queryset, HookQuerySetMixin):
14
+ return base_queryset
15
+
16
+ # Otherwise, create a new HookQuerySet with the same parameters
17
+ # This is much simpler and avoids dynamic class creation issues
18
+ return HookQuerySet(
19
+ model=base_queryset.model,
20
+ query=base_queryset.query,
21
+ using=base_queryset._db,
22
+ hints=base_queryset._hints
23
+ )
55
24
 
56
25
  def bulk_create(
57
26
  self,
@@ -183,17 +183,9 @@ class HookQuerySetMixin:
183
183
  """
184
184
  Bulk update objects in the database with MTI support.
185
185
  """
186
- print(f"\n=== BULK UPDATE DEBUG ===")
187
- print(f"Model: {self.model.__name__}")
188
- print(f"Number of objects: {len(objs)}")
189
- print(f"Fields: {fields}")
190
- print(f"Bypass hooks: {bypass_hooks}")
191
- print(f"Bypass validation: {bypass_validation}")
192
-
193
186
  model_cls = self.model
194
187
 
195
188
  if not objs:
196
- print("No objects to update")
197
189
  return []
198
190
 
199
191
  if any(not isinstance(obj, model_cls) for obj in objs):
@@ -207,11 +199,6 @@ class HookQuerySetMixin:
207
199
  if parent._meta.concrete_model is not model_cls._meta.concrete_model:
208
200
  is_mti = True
209
201
  break
210
-
211
- print(f"Is MTI: {is_mti}")
212
- print(f"Model concrete model: {model_cls._meta.concrete_model.__name__}")
213
- for parent in model_cls._meta.all_parents:
214
- print(f" Parent {parent.__name__}: concrete_model = {parent._meta.concrete_model.__name__}")
215
202
 
216
203
  if not bypass_hooks:
217
204
  # Load originals for hook comparison
@@ -238,14 +225,11 @@ class HookQuerySetMixin:
238
225
  fields_set = set(fields)
239
226
  fields_set.update(modified_fields)
240
227
  fields = list(fields_set)
241
- print(f"Modified fields detected: {modified_fields}")
242
228
 
243
229
  # Handle MTI models differently
244
230
  if is_mti:
245
- print("Using MTI bulk update logic")
246
231
  result = self._mti_bulk_update(objs, fields, **kwargs)
247
232
  else:
248
- print("Using standard Django bulk_update")
249
233
  # For single-table models, use Django's built-in bulk_update
250
234
  django_kwargs = {
251
235
  k: v
@@ -257,7 +241,6 @@ class HookQuerySetMixin:
257
241
  if not bypass_hooks:
258
242
  engine.run(model_cls, AFTER_UPDATE, objs, originals, ctx=ctx)
259
243
 
260
- print(f"Bulk update result: {result}")
261
244
  return result
262
245
 
263
246
  def _detect_modified_fields(self, new_instances, original_instances):
@@ -302,29 +285,20 @@ class HookQuerySetMixin:
302
285
  Get the complete inheritance chain from root parent to current model.
303
286
  Returns list of model classes in order: [RootParent, Parent, Child]
304
287
  """
305
- print(f"\n=== GET INHERITANCE CHAIN DEBUG ===")
306
- print(f"Current model: {self.model.__name__}")
307
-
308
288
  chain = []
309
289
  current_model = self.model
310
290
  while current_model:
311
- print(f"Processing model: {current_model.__name__}")
312
291
  if not current_model._meta.proxy:
313
292
  chain.append(current_model)
314
- print(f" Added to chain: {current_model.__name__}")
315
- else:
316
- print(f" Skipped proxy model: {current_model.__name__}")
317
293
 
318
294
  parents = [
319
295
  parent
320
296
  for parent in current_model._meta.parents.keys()
321
297
  if not parent._meta.proxy
322
298
  ]
323
- print(f" Parents: {[p.__name__ for p in parents]}")
324
299
  current_model = parents[0] if parents else None
325
300
 
326
301
  chain.reverse()
327
- print(f"Final inheritance chain: {[m.__name__ for m in chain]}")
328
302
  return chain
329
303
 
330
304
  def _mti_bulk_create(self, objs, inheritance_chain=None, **kwargs):
@@ -567,14 +541,8 @@ class HookQuerySetMixin:
567
541
  Custom bulk update implementation for MTI models.
568
542
  Updates each table in the inheritance chain efficiently using Django's batch_size.
569
543
  """
570
- print(f"\n=== MTI BULK UPDATE DEBUG ===")
571
- print(f"Model: {self.model.__name__}")
572
- print(f"Number of objects: {len(objs)}")
573
- print(f"Fields to update: {fields}")
574
-
575
544
  model_cls = self.model
576
545
  inheritance_chain = self._get_inheritance_chain()
577
- print(f"Inheritance chain: {[m.__name__ for m in inheritance_chain]}")
578
546
 
579
547
  # Remove custom hook kwargs before passing to Django internals
580
548
  django_kwargs = {
@@ -599,28 +567,20 @@ class HookQuerySetMixin:
599
567
  if model not in field_groups:
600
568
  field_groups[model] = []
601
569
  field_groups[model].append(field_name)
602
- print(f"Field '{field_name}' belongs to model '{model.__name__}'")
603
570
  break
604
-
605
- print(f"Field groups: {field_groups}")
606
571
 
607
572
  # Process in batches
608
573
  batch_size = django_kwargs.get("batch_size") or len(objs)
609
574
  total_updated = 0
610
575
 
611
- print(f"Processing in batches of size: {batch_size}")
612
-
613
576
  with transaction.atomic(using=self.db, savepoint=False):
614
577
  for i in range(0, len(objs), batch_size):
615
578
  batch = objs[i : i + batch_size]
616
- print(f"\n--- Processing batch {i//batch_size + 1} ({len(batch)} objects) ---")
617
579
  batch_result = self._process_mti_bulk_update_batch(
618
580
  batch, field_groups, inheritance_chain, **django_kwargs
619
581
  )
620
582
  total_updated += batch_result
621
- print(f"Batch {i//batch_size + 1} updated {batch_result} rows")
622
583
 
623
- print(f"\n=== TOTAL UPDATED: {total_updated} ===")
624
584
  return total_updated
625
585
 
626
586
  def _process_mti_bulk_update_batch(self, batch, field_groups, inheritance_chain, **kwargs):
@@ -630,9 +590,6 @@ class HookQuerySetMixin:
630
590
  """
631
591
  total_updated = 0
632
592
 
633
- print(f"Processing batch with {len(batch)} objects")
634
- print(f"Field groups: {field_groups}")
635
-
636
593
  # For MTI, we need to handle parent links correctly
637
594
  # The root model (first in chain) has its own PK
638
595
  # Child models use the parent link to reference the root PK
@@ -649,31 +606,21 @@ class HookQuerySetMixin:
649
606
 
650
607
  if pk_value is not None:
651
608
  root_pks.append(pk_value)
652
- print(f"Found PK {pk_value} for object {obj}")
653
609
  else:
654
- print(f"WARNING: Object {obj} has no primary key (pk={getattr(obj, 'pk', None)}, id={getattr(obj, 'id', None)})")
655
610
  continue
656
611
 
657
- print(f"Root PKs to update: {root_pks}")
658
-
659
612
  if not root_pks:
660
- print("No valid primary keys found, skipping update")
661
613
  return 0
662
614
 
663
615
  # Update each table in the inheritance chain
664
616
  for model, model_fields in field_groups.items():
665
- print(f"\n--- Updating model: {model.__name__} ---")
666
- print(f"Fields to update: {model_fields}")
667
-
668
617
  if not model_fields:
669
- print("No fields to update, skipping")
670
618
  continue
671
619
 
672
620
  if model == inheritance_chain[0]:
673
621
  # Root model - use primary keys directly
674
622
  pks = root_pks
675
623
  filter_field = 'pk'
676
- print(f"Root model - using PKs: {pks}")
677
624
  else:
678
625
  # Child model - use parent link field
679
626
  parent_link = None
@@ -683,27 +630,19 @@ class HookQuerySetMixin:
683
630
  break
684
631
 
685
632
  if parent_link is None:
686
- print(f"No parent link found for {model.__name__}, skipping")
687
633
  continue
688
634
 
689
- print(f"Parent link field: {parent_link.name} ({parent_link.attname})")
690
-
691
635
  # For child models, the parent link values should be the same as root PKs
692
636
  pks = root_pks
693
637
  filter_field = parent_link.attname
694
- print(f"Child model - using parent link values: {pks}")
695
638
 
696
639
  if pks:
697
640
  base_qs = model._base_manager.using(self.db)
698
- print(f"Filter field: {filter_field}")
699
- print(f"PKs to filter by: {pks}")
700
641
 
701
642
  # Check if records exist
702
643
  existing_count = base_qs.filter(**{f"{filter_field}__in": pks}).count()
703
- print(f"Existing records with these PKs: {existing_count}")
704
644
 
705
645
  if existing_count == 0:
706
- print("WARNING: No existing records found with these PKs!")
707
646
  continue
708
647
 
709
648
  # Build CASE statements for each field to perform a single bulk update
@@ -712,7 +651,6 @@ class HookQuerySetMixin:
712
651
  field = model._meta.get_field(field_name)
713
652
  when_statements = []
714
653
 
715
- print(f"Building CASE statement for field: {field_name}")
716
654
  for pk, obj in zip(pks, batch):
717
655
  # Check both pk and id attributes for the object
718
656
  obj_pk = getattr(obj, 'pk', None)
@@ -722,26 +660,18 @@ class HookQuerySetMixin:
722
660
  if obj_pk is None:
723
661
  continue
724
662
  value = getattr(obj, field_name)
725
- print(f" PK {pk}: {field_name} = {value}")
726
663
  when_statements.append(When(**{filter_field: pk}, then=Value(value, output_field=field)))
727
664
 
728
665
  case_statements[field_name] = Case(*when_statements, output_field=field)
729
666
 
730
- print(f"Case statements built: {list(case_statements.keys())}")
731
-
732
667
  # Execute a single bulk update for all objects in this model
733
668
  try:
734
669
  updated_count = base_qs.filter(**{f"{filter_field}__in": pks}).update(**case_statements)
735
- print(f"UPDATE QUERY EXECUTED - Updated {updated_count} rows")
736
670
  total_updated += updated_count
737
671
  except Exception as e:
738
- print(f"ERROR during update: {e}")
739
672
  import traceback
740
673
  traceback.print_exc()
741
- else:
742
- print("No PKs found, skipping update")
743
674
 
744
- print(f"Batch total updated: {total_updated}")
745
675
  return total_updated
746
676
 
747
677
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-bulk-hooks
3
- Version: 0.1.190
3
+ Version: 0.1.192
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
@@ -6,12 +6,12 @@ django_bulk_hooks/decorators.py,sha256=WD7Jn7QAvY8F4wOsYlIpjoM9-FdHXSKB7hH9ot-lk
6
6
  django_bulk_hooks/engine.py,sha256=nA5PU9msk_Ju5Gf_sTd7GqPscuTxEW5itCDAoSScYGI,1645
7
7
  django_bulk_hooks/enums.py,sha256=Zo8_tJzuzZ2IKfVc7gZ-0tWPT8q1QhqZbAyoh9ZVJbs,381
8
8
  django_bulk_hooks/handler.py,sha256=xZt8iNdYF-ACz-MnKMY0co6scWINU5V5wC1lyDn844k,4854
9
- django_bulk_hooks/manager.py,sha256=YvkwOix_740hxahCtDg1sK3eTHTAZnUSyF1T09VNBaY,4671
9
+ django_bulk_hooks/manager.py,sha256=nfWiwU5-yAoxdnQsUMohxtyCpkV0MBv6X3wmipr9eQY,3697
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=eaeSmYW3VrJjhom_wXKzndmEkrKm-xaG0qflYhKSgIA,31663
12
+ django_bulk_hooks/queryset.py,sha256=G8tzhFM8tirPXLJi0KwGWuybugboJoix21z7j_lPqJ4,27908
13
13
  django_bulk_hooks/registry.py,sha256=-mQBizJ06nz_tajZBinViKx_uP2Tbc1tIpTEMv7lwKA,705
14
- django_bulk_hooks-0.1.190.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
- django_bulk_hooks-0.1.190.dist-info/METADATA,sha256=SskcUNxKkMSOb_OG2APxuDQf-pocG6dgPlXIBCg7PeY,7418
16
- django_bulk_hooks-0.1.190.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
17
- django_bulk_hooks-0.1.190.dist-info/RECORD,,
14
+ django_bulk_hooks-0.1.192.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
+ django_bulk_hooks-0.1.192.dist-info/METADATA,sha256=4QTA06jIRiiURXxuvwnQVLf6qTURJD48Zadn9PufPAk,7418
16
+ django_bulk_hooks-0.1.192.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
17
+ django_bulk_hooks-0.1.192.dist-info/RECORD,,