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

@@ -24,9 +24,8 @@ class HookQuerySet(models.QuerySet):
24
24
  objs = list(self)
25
25
  if not objs:
26
26
  return 0
27
- # Use our bulk_delete method to handle hooks properly
28
- self.bulk_delete(objs)
29
- return len(objs)
27
+ # Call the base QuerySet implementation to avoid recursion
28
+ return super().bulk_delete(objs)
30
29
 
31
30
  @transaction.atomic
32
31
  def update(self, **kwargs):
@@ -34,28 +33,31 @@ class HookQuerySet(models.QuerySet):
34
33
  if not instances:
35
34
  return 0
36
35
 
36
+ model_cls = self.model
37
+ pks = [obj.pk for obj in instances]
38
+
39
+ # Load originals for hook comparison and ensure they match the order of instances
40
+ # Use the base manager to avoid recursion
41
+ original_map = {obj.pk: obj for obj in model_cls._base_manager.filter(pk__in=pks)}
42
+ originals = [original_map.get(obj.pk) for obj in instances]
43
+
37
44
  # Apply field updates to instances
38
45
  for obj in instances:
39
46
  for field, value in kwargs.items():
40
47
  setattr(obj, field, value)
41
48
 
42
- # Use our bulk_update method to handle hooks properly
43
- self.bulk_update(instances, list(kwargs.keys()))
44
- return len(instances)
49
+ # Run BEFORE_UPDATE hooks
50
+ ctx = HookContext(model_cls)
51
+ engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
45
52
 
46
- def save(self, obj):
47
- """
48
- Save a single object using the appropriate bulk operation.
49
- """
50
- if obj.pk:
51
- # Use bulk_update for existing objects
52
- self.bulk_update(
53
- [obj], [field.name for field in obj._meta.fields if field.name != "id"]
54
- )
55
- else:
56
- # Use bulk_create for new objects
57
- self.bulk_create([obj])
58
- return obj
53
+ # Use Django's built-in update logic directly
54
+ # Call the base QuerySet implementation to avoid recursion
55
+ update_count = super().update(**kwargs)
56
+
57
+ # Run AFTER_UPDATE hooks
58
+ engine.run(model_cls, AFTER_UPDATE, instances, originals, ctx=ctx)
59
+
60
+ return update_count
59
61
 
60
62
  @transaction.atomic
61
63
  def bulk_create(
@@ -112,60 +114,86 @@ class HookQuerySet(models.QuerySet):
112
114
 
113
115
  # Fire hooks before DB ops
114
116
  if not bypass_hooks:
117
+ print(f"DEBUG: Firing BEFORE_CREATE hooks for {model_cls}")
118
+ print(f"DEBUG: Number of objects: {len(objs)}")
119
+ print(f"DEBUG: Object types: {[type(obj) for obj in objs]}")
120
+ print(f"DEBUG: QuerySet type: {type(self)}")
121
+ print(f"DEBUG: Is this HookQuerySet? {isinstance(self, HookQuerySet)}")
115
122
  ctx = HookContext(model_cls)
116
123
  if not bypass_validation:
124
+ print(f"DEBUG: Running VALIDATE_CREATE hooks")
117
125
  engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
126
+ print(f"DEBUG: Running BEFORE_CREATE hooks")
118
127
  engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
128
+ else:
129
+ print(f"DEBUG: Skipping hooks due to bypass_hooks=True for {model_cls}")
119
130
 
120
131
  # For MTI models, we need to handle them specially
121
132
  if is_mti:
122
133
  # Use our MTI-specific logic
123
134
  # Filter out custom parameters that Django's bulk_create doesn't accept
124
135
  mti_kwargs = {
125
- "batch_size": batch_size,
126
- "ignore_conflicts": ignore_conflicts,
127
- "update_conflicts": update_conflicts,
128
- "update_fields": update_fields,
129
- "unique_fields": unique_fields,
136
+ 'batch_size': batch_size,
137
+ 'ignore_conflicts': ignore_conflicts,
138
+ 'update_conflicts': update_conflicts,
139
+ 'update_fields': update_fields,
140
+ 'unique_fields': unique_fields,
130
141
  }
131
142
  # Remove custom hook kwargs if present in self.bulk_create signature
132
143
  result = self._mti_bulk_create(
133
144
  objs,
134
- **{
135
- k: v
136
- for k, v in mti_kwargs.items()
137
- if k not in ["bypass_hooks", "bypass_validation"]
138
- },
145
+ **{k: v for k, v in mti_kwargs.items() if k not in ['bypass_hooks', 'bypass_validation']}
139
146
  )
140
147
  else:
141
148
  # For single-table models, use Django's built-in bulk_create
149
+ # but we need to call it on the base manager to avoid recursion
142
150
  # Filter out custom parameters that Django's bulk_create doesn't accept
143
- django_kwargs = {
144
- "batch_size": batch_size,
145
- "ignore_conflicts": ignore_conflicts,
146
- "update_conflicts": update_conflicts,
147
- "update_fields": update_fields,
148
- "unique_fields": unique_fields,
149
- }
150
- # Remove custom hook kwargs if present
151
- django_kwargs = {k: v for k, v in django_kwargs.items() if v is not None}
152
- result = super().bulk_create(objs, **django_kwargs)
151
+
152
+ # Use Django's original QuerySet to avoid recursive calls
153
+ from django.db.models import QuerySet
154
+ original_qs = QuerySet(model_cls, using=self.db)
155
+ result = original_qs.bulk_create(
156
+ objs,
157
+ batch_size=batch_size,
158
+ ignore_conflicts=ignore_conflicts,
159
+ update_conflicts=update_conflicts,
160
+ update_fields=update_fields,
161
+ unique_fields=unique_fields,
162
+ )
153
163
 
154
164
  # Fire AFTER_CREATE hooks
155
165
  if not bypass_hooks:
166
+ print(f"DEBUG: Firing AFTER_CREATE hooks for {model_cls}")
167
+ print(f"DEBUG: Number of objects: {len(objs)}")
168
+ print(f"DEBUG: QuerySet type: {type(self)}")
169
+ print(f"DEBUG: Is this HookQuerySet? {isinstance(self, HookQuerySet)}")
156
170
  engine.run(model_cls, AFTER_CREATE, objs, ctx=ctx)
171
+ else:
172
+ print(f"DEBUG: Skipping AFTER_CREATE hooks due to bypass_hooks=True for {model_cls}")
157
173
 
158
174
  return result
159
175
 
160
176
  @transaction.atomic
161
- def bulk_update(
162
- self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs
163
- ):
177
+ def bulk_update(self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs):
164
178
  """
165
179
  Bulk update objects in the database.
166
180
  """
181
+ import inspect
182
+ print(f"DEBUG: QuerySet.bulk_update called with:")
183
+ print(f" - self: {type(self)}")
184
+ print(f" - objs: {type(objs)}")
185
+ print(f" - fields: {fields}")
186
+ print(f" - bypass_hooks: {bypass_hooks}")
187
+ print(f" - bypass_validation: {bypass_validation}")
188
+ print(f" - kwargs: {kwargs}")
189
+ print(f"DEBUG: Method signature: {inspect.signature(self.bulk_update)}")
190
+
167
191
  model_cls = self.model
168
-
192
+ print(f"DEBUG: Model class: {model_cls}")
193
+ print(f"DEBUG: bypass_hooks value: {bypass_hooks}")
194
+ print(f"DEBUG: QuerySet type: {type(self)}")
195
+ print(f"DEBUG: Is this HookQuerySet? {isinstance(self, HookQuerySet)}")
196
+
169
197
  if not objs:
170
198
  return []
171
199
 
@@ -179,9 +207,7 @@ class HookQuerySet(models.QuerySet):
179
207
  # Use the base manager to avoid recursion
180
208
  original_map = {
181
209
  obj.pk: obj
182
- for obj in model_cls._base_manager.filter(
183
- pk__in=[obj.pk for obj in objs]
184
- )
210
+ for obj in model_cls._base_manager.filter(pk__in=[obj.pk for obj in objs])
185
211
  }
186
212
  originals = [original_map.get(obj.pk) for obj in objs]
187
213
 
@@ -193,7 +219,12 @@ class HookQuerySet(models.QuerySet):
193
219
 
194
220
  # Then run business logic hooks
195
221
  if not bypass_hooks:
222
+ print(f"DEBUG: Firing BEFORE_UPDATE hooks for {model_cls}")
223
+ print(f"DEBUG: Number of objects: {len(objs)}")
224
+ print(f"DEBUG: Object types: {[type(obj) for obj in objs]}")
196
225
  engine.run(model_cls, BEFORE_UPDATE, objs, originals, ctx=ctx)
226
+ else:
227
+ print(f"DEBUG: Skipping hooks due to bypass_hooks=True for {model_cls}")
197
228
 
198
229
  # Automatically detect fields that were modified during BEFORE_UPDATE hooks
199
230
  modified_fields = self._detect_modified_fields(objs, originals)
@@ -208,15 +239,15 @@ class HookQuerySet(models.QuerySet):
208
239
 
209
240
  # Call the base implementation to avoid re-triggering this method
210
241
  # Filter out custom parameters that Django's bulk_update doesn't accept
211
- django_kwargs = {
212
- k: v
213
- for k, v in kwargs.items()
214
- if k not in ["bypass_hooks", "bypass_validation"]
215
- }
242
+ django_kwargs = {k: v for k, v in kwargs.items() if k not in ['bypass_hooks', 'bypass_validation']}
216
243
  super().bulk_update(chunk, fields, **django_kwargs)
217
244
 
218
245
  if not bypass_hooks:
246
+ print(f"DEBUG: Firing AFTER_UPDATE hooks for {model_cls}")
247
+ print(f"DEBUG: Number of objects: {len(objs)}")
219
248
  engine.run(model_cls, AFTER_UPDATE, objs, originals, ctx=ctx)
249
+ else:
250
+ print(f"DEBUG: Skipping AFTER_UPDATE hooks due to bypass_hooks=True for {model_cls}")
220
251
 
221
252
  return objs
222
253
 
@@ -319,11 +350,7 @@ class HookQuerySet(models.QuerySet):
319
350
  Sets auto_now_add/auto_now fields for each model in the chain.
320
351
  """
321
352
  # Remove custom hook kwargs before passing to Django internals
322
- django_kwargs = {
323
- k: v
324
- for k, v in kwargs.items()
325
- if k not in ["bypass_hooks", "bypass_validation"]
326
- }
353
+ django_kwargs = {k: v for k, v in kwargs.items() if k not in ['bypass_hooks', 'bypass_validation']}
327
354
  if inheritance_chain is None:
328
355
  inheritance_chain = self._get_inheritance_chain()
329
356
 
@@ -356,9 +383,9 @@ class HookQuerySet(models.QuerySet):
356
383
 
357
384
  # Step 1: Do O(n) normal inserts into parent tables to get primary keys back
358
385
  # Get bypass_hooks from kwargs
359
- bypass_hooks = kwargs.get("bypass_hooks", False)
360
- bypass_validation = kwargs.get("bypass_validation", False)
361
-
386
+ bypass_hooks = kwargs.get('bypass_hooks', False)
387
+ bypass_validation = kwargs.get('bypass_validation', False)
388
+
362
389
  for obj in batch:
363
390
  parent_instances = {}
364
391
  current_parent = None
@@ -366,35 +393,32 @@ class HookQuerySet(models.QuerySet):
366
393
  parent_obj = self._create_parent_instance(
367
394
  obj, model_class, current_parent
368
395
  )
369
-
396
+
370
397
  # Fire parent hooks if not bypassed
371
398
  if not bypass_hooks:
372
399
  ctx = HookContext(model_class)
373
400
  if not bypass_validation:
374
401
  engine.run(model_class, VALIDATE_CREATE, [parent_obj], ctx=ctx)
375
402
  engine.run(model_class, BEFORE_CREATE, [parent_obj], ctx=ctx)
376
-
403
+
377
404
  # Use Django's base manager to create the object and get PKs back
378
405
  # This bypasses hooks and the MTI exception
379
406
  field_values = {
380
407
  field.name: getattr(parent_obj, field.name)
381
408
  for field in model_class._meta.local_fields
382
- if hasattr(parent_obj, field.name)
383
- and getattr(parent_obj, field.name) is not None
409
+ if hasattr(parent_obj, field.name) and getattr(parent_obj, field.name) is not None
384
410
  }
385
- created_obj = model_class._base_manager.using(self.db).create(
386
- **field_values
387
- )
388
-
411
+ created_obj = model_class._base_manager.using(self.db).create(**field_values)
412
+
389
413
  # Update the parent_obj with the created object's PK
390
414
  parent_obj.pk = created_obj.pk
391
415
  parent_obj._state.adding = False
392
416
  parent_obj._state.db = self.db
393
-
417
+
394
418
  # Fire AFTER_CREATE hooks for parent
395
419
  if not bypass_hooks:
396
420
  engine.run(model_class, AFTER_CREATE, [parent_obj], ctx=ctx)
397
-
421
+
398
422
  parent_instances[model_class] = parent_obj
399
423
  current_parent = parent_obj
400
424
  parent_objects_map[id(obj)] = parent_instances
@@ -412,10 +436,10 @@ class HookQuerySet(models.QuerySet):
412
436
  if all_child_objects:
413
437
  # Get the base manager's queryset
414
438
  base_qs = child_model._base_manager.using(self.db)
415
-
439
+
416
440
  # Use Django's exact approach: call _prepare_for_bulk_create then partition
417
441
  base_qs._prepare_for_bulk_create(all_child_objects)
418
-
442
+
419
443
  # Implement our own partition since itertools.partition might not be available
420
444
  objs_without_pk, objs_with_pk = [], []
421
445
  for obj in all_child_objects:
@@ -423,50 +447,44 @@ class HookQuerySet(models.QuerySet):
423
447
  objs_with_pk.append(obj)
424
448
  else:
425
449
  objs_without_pk.append(obj)
426
-
450
+
427
451
  # Use Django's internal _batched_insert method
428
452
  opts = child_model._meta
429
453
  # For child models in MTI, we need to include the foreign key to the parent
430
454
  # but exclude the primary key since it's inherited
431
-
455
+
432
456
  # Include all local fields except generated ones
433
457
  # We need to include the foreign key to the parent (business_ptr)
434
458
  fields = [f for f in opts.local_fields if not f.generated]
435
-
459
+
436
460
  with transaction.atomic(using=self.db, savepoint=False):
437
- if objs_with_pk:
438
- returned_columns = base_qs._batched_insert(
439
- objs_with_pk,
440
- fields,
441
- batch_size=len(objs_with_pk), # Use actual batch size
442
- )
443
- for obj_with_pk, results in zip(objs_with_pk, returned_columns):
444
- for result, field in zip(results, opts.db_returning_fields):
445
- if field != opts.pk:
446
- setattr(obj_with_pk, field.attname, result)
447
- for obj_with_pk in objs_with_pk:
448
- obj_with_pk._state.adding = False
449
- obj_with_pk._state.db = self.db
450
-
451
- if objs_without_pk:
452
- # For objects without PK, we still need to exclude primary key fields
453
- fields = [
454
- f
455
- for f in fields
456
- if not isinstance(f, AutoField) and not f.primary_key
457
- ]
458
- returned_columns = base_qs._batched_insert(
459
- objs_without_pk,
460
- fields,
461
- batch_size=len(objs_without_pk), # Use actual batch size
462
- )
463
- for obj_without_pk, results in zip(
464
- objs_without_pk, returned_columns
465
- ):
466
- for result, field in zip(results, opts.db_returning_fields):
467
- setattr(obj_without_pk, field.attname, result)
468
- obj_without_pk._state.adding = False
469
- obj_without_pk._state.db = self.db
461
+ if objs_with_pk:
462
+ returned_columns = base_qs._batched_insert(
463
+ objs_with_pk,
464
+ fields,
465
+ batch_size=len(objs_with_pk), # Use actual batch size
466
+ )
467
+ for obj_with_pk, results in zip(objs_with_pk, returned_columns):
468
+ for result, field in zip(results, opts.db_returning_fields):
469
+ if field != opts.pk:
470
+ setattr(obj_with_pk, field.attname, result)
471
+ for obj_with_pk in objs_with_pk:
472
+ obj_with_pk._state.adding = False
473
+ obj_with_pk._state.db = self.db
474
+
475
+ if objs_without_pk:
476
+ # For objects without PK, we still need to exclude primary key fields
477
+ fields = [f for f in fields if not isinstance(f, AutoField) and not f.primary_key]
478
+ returned_columns = base_qs._batched_insert(
479
+ objs_without_pk,
480
+ fields,
481
+ batch_size=len(objs_without_pk), # Use actual batch size
482
+ )
483
+ for obj_without_pk, results in zip(objs_without_pk, returned_columns):
484
+ for result, field in zip(results, opts.db_returning_fields):
485
+ setattr(obj_without_pk, field.attname, result)
486
+ obj_without_pk._state.adding = False
487
+ obj_without_pk._state.db = self.db
470
488
 
471
489
  # Step 3: Update original objects with generated PKs and state
472
490
  pk_field_name = child_model._meta.pk.name
@@ -519,19 +537,15 @@ class HookQuerySet(models.QuerySet):
519
537
  value = getattr(source_obj, field.name, None)
520
538
  if value is not None:
521
539
  setattr(child_obj, field.name, value)
522
-
540
+
523
541
  # Set parent links for MTI
524
542
  for parent_model, parent_instance in parent_instances.items():
525
543
  parent_link = child_model._meta.get_ancestor_link(parent_model)
526
544
  if parent_link:
527
545
  # Set both the foreign key value (the ID) and the object reference
528
546
  # This follows Django's pattern in _set_pk_val
529
- setattr(
530
- child_obj, parent_link.attname, parent_instance.pk
531
- ) # Set the foreign key value
532
- setattr(
533
- child_obj, parent_link.name, parent_instance
534
- ) # Set the object reference
547
+ setattr(child_obj, parent_link.attname, parent_instance.pk) # Set the foreign key value
548
+ setattr(child_obj, parent_link.name, parent_instance) # Set the object reference
535
549
 
536
550
  # Handle auto_now_add and auto_now fields like Django does
537
551
  for field in child_model._meta.local_fields:
@@ -544,4 +558,4 @@ class HookQuerySet(models.QuerySet):
544
558
  elif hasattr(field, "auto_now") and field.auto_now:
545
559
  field.pre_save(child_obj, add=True)
546
560
 
547
- return child_obj
561
+ return child_obj
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-bulk-hooks
3
- Version: 0.1.172
3
+ Version: 0.1.173
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
@@ -9,9 +9,9 @@ django_bulk_hooks/handler.py,sha256=xZt8iNdYF-ACz-MnKMY0co6scWINU5V5wC1lyDn844k,
9
9
  django_bulk_hooks/manager.py,sha256=OSzW8eVzknLV1WCvZcBkWMz9x_Vjq4bJM8raVXKiZvI,5085
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=yGzVFWWbg7mjJxH9Gx1hXw9Mf82H0XmMlfKZn47WMJw,23222
12
+ django_bulk_hooks/queryset.py,sha256=83LqDkcLuNKi05p-W9qnJ_qmNrf2JuOaoPNYnEfwcSQ,25455
13
13
  django_bulk_hooks/registry.py,sha256=-mQBizJ06nz_tajZBinViKx_uP2Tbc1tIpTEMv7lwKA,705
14
- django_bulk_hooks-0.1.172.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
- django_bulk_hooks-0.1.172.dist-info/METADATA,sha256=JQDexi4jxTyskLLwCDmRemDvHPeihDdkNZOJSohsLjk,6939
16
- django_bulk_hooks-0.1.172.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
17
- django_bulk_hooks-0.1.172.dist-info/RECORD,,
14
+ django_bulk_hooks-0.1.173.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
+ django_bulk_hooks-0.1.173.dist-info/METADATA,sha256=yUU38QGdgYzCwOo6OJVDHsWp6UDyHM1A3qJoTgwtrng,6939
16
+ django_bulk_hooks-0.1.173.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
17
+ django_bulk_hooks-0.1.173.dist-info/RECORD,,