django-bulk-hooks 0.1.173__py3-none-any.whl → 0.1.175__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,8 +24,23 @@ class HookQuerySet(models.QuerySet):
24
24
  objs = list(self)
25
25
  if not objs:
26
26
  return 0
27
- # Call the base QuerySet implementation to avoid recursion
28
- return super().bulk_delete(objs)
27
+
28
+ model_cls = self.model
29
+ ctx = HookContext(model_cls)
30
+
31
+ # Run validation hooks first
32
+ engine.run(model_cls, VALIDATE_DELETE, objs, ctx=ctx)
33
+
34
+ # Then run business logic hooks
35
+ engine.run(model_cls, BEFORE_DELETE, objs, ctx=ctx)
36
+
37
+ # Use Django's standard delete() method
38
+ result = super().delete()
39
+
40
+ # Run AFTER_DELETE hooks
41
+ engine.run(model_cls, AFTER_DELETE, objs, ctx=ctx)
42
+
43
+ return result
29
44
 
30
45
  @transaction.atomic
31
46
  def update(self, **kwargs):
@@ -38,7 +53,9 @@ class HookQuerySet(models.QuerySet):
38
53
 
39
54
  # Load originals for hook comparison and ensure they match the order of instances
40
55
  # Use the base manager to avoid recursion
41
- original_map = {obj.pk: obj for obj in model_cls._base_manager.filter(pk__in=pks)}
56
+ original_map = {
57
+ obj.pk: obj for obj in model_cls._base_manager.filter(pk__in=pks)
58
+ }
42
59
  originals = [original_map.get(obj.pk) for obj in instances]
43
60
 
44
61
  # Apply field updates to instances
@@ -114,35 +131,30 @@ class HookQuerySet(models.QuerySet):
114
131
 
115
132
  # Fire hooks before DB ops
116
133
  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)}")
122
134
  ctx = HookContext(model_cls)
123
135
  if not bypass_validation:
124
- print(f"DEBUG: Running VALIDATE_CREATE hooks")
125
136
  engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
126
- print(f"DEBUG: Running BEFORE_CREATE hooks")
127
137
  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}")
130
138
 
131
139
  # For MTI models, we need to handle them specially
132
140
  if is_mti:
133
141
  # Use our MTI-specific logic
134
142
  # Filter out custom parameters that Django's bulk_create doesn't accept
135
143
  mti_kwargs = {
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,
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,
141
149
  }
142
150
  # Remove custom hook kwargs if present in self.bulk_create signature
143
151
  result = self._mti_bulk_create(
144
152
  objs,
145
- **{k: v for k, v in mti_kwargs.items() if k not in ['bypass_hooks', 'bypass_validation']}
153
+ **{
154
+ k: v
155
+ for k, v in mti_kwargs.items()
156
+ if k not in ["bypass_hooks", "bypass_validation"]
157
+ },
146
158
  )
147
159
  else:
148
160
  # For single-table models, use Django's built-in bulk_create
@@ -151,6 +163,7 @@ class HookQuerySet(models.QuerySet):
151
163
 
152
164
  # Use Django's original QuerySet to avoid recursive calls
153
165
  from django.db.models import QuerySet
166
+
154
167
  original_qs = QuerySet(model_cls, using=self.db)
155
168
  result = original_qs.bulk_create(
156
169
  objs,
@@ -163,37 +176,19 @@ class HookQuerySet(models.QuerySet):
163
176
 
164
177
  # Fire AFTER_CREATE hooks
165
178
  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)}")
170
179
  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}")
173
180
 
174
181
  return result
175
182
 
176
183
  @transaction.atomic
177
- def bulk_update(self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs):
184
+ def bulk_update(
185
+ self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs
186
+ ):
178
187
  """
179
188
  Bulk update objects in the database.
180
189
  """
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
-
191
190
  model_cls = self.model
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
-
191
+
197
192
  if not objs:
198
193
  return []
199
194
 
@@ -207,7 +202,9 @@ class HookQuerySet(models.QuerySet):
207
202
  # Use the base manager to avoid recursion
208
203
  original_map = {
209
204
  obj.pk: obj
210
- for obj in model_cls._base_manager.filter(pk__in=[obj.pk for obj in objs])
205
+ for obj in model_cls._base_manager.filter(
206
+ pk__in=[obj.pk for obj in objs]
207
+ )
211
208
  }
212
209
  originals = [original_map.get(obj.pk) for obj in objs]
213
210
 
@@ -219,12 +216,7 @@ class HookQuerySet(models.QuerySet):
219
216
 
220
217
  # Then run business logic hooks
221
218
  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]}")
225
219
  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}")
228
220
 
229
221
  # Automatically detect fields that were modified during BEFORE_UPDATE hooks
230
222
  modified_fields = self._detect_modified_fields(objs, originals)
@@ -239,50 +231,15 @@ class HookQuerySet(models.QuerySet):
239
231
 
240
232
  # Call the base implementation to avoid re-triggering this method
241
233
  # Filter out custom parameters that Django's bulk_update doesn't accept
242
- django_kwargs = {k: v for k, v in kwargs.items() if k not in ['bypass_hooks', 'bypass_validation']}
234
+ django_kwargs = {
235
+ k: v
236
+ for k, v in kwargs.items()
237
+ if k not in ["bypass_hooks", "bypass_validation"]
238
+ }
243
239
  super().bulk_update(chunk, fields, **django_kwargs)
244
240
 
245
241
  if not bypass_hooks:
246
- print(f"DEBUG: Firing AFTER_UPDATE hooks for {model_cls}")
247
- print(f"DEBUG: Number of objects: {len(objs)}")
248
242
  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}")
251
-
252
- return objs
253
-
254
- @transaction.atomic
255
- def bulk_delete(
256
- self, objs, batch_size=None, bypass_hooks=False, bypass_validation=False
257
- ):
258
- if not objs:
259
- return []
260
-
261
- model_cls = self.model
262
-
263
- if any(not isinstance(obj, model_cls) for obj in objs):
264
- raise TypeError(
265
- f"bulk_delete expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
266
- )
267
-
268
- ctx = HookContext(model_cls)
269
-
270
- if not bypass_hooks:
271
- # Run validation hooks first
272
- if not bypass_validation:
273
- engine.run(model_cls, VALIDATE_DELETE, objs, ctx=ctx)
274
-
275
- # Then run business logic hooks
276
- engine.run(model_cls, BEFORE_DELETE, objs, ctx=ctx)
277
-
278
- pks = [obj.pk for obj in objs if obj.pk is not None]
279
-
280
- # Call the base QuerySet implementation to avoid recursion
281
- # The hooks have already been fired above, so we don't need them again
282
- super().bulk_delete(objs, batch_size=batch_size)
283
-
284
- if not bypass_hooks:
285
- engine.run(model_cls, AFTER_DELETE, objs, ctx=ctx)
286
243
 
287
244
  return objs
288
245
 
@@ -350,7 +307,11 @@ class HookQuerySet(models.QuerySet):
350
307
  Sets auto_now_add/auto_now fields for each model in the chain.
351
308
  """
352
309
  # Remove custom hook kwargs before passing to Django internals
353
- django_kwargs = {k: v for k, v in kwargs.items() if k not in ['bypass_hooks', 'bypass_validation']}
310
+ django_kwargs = {
311
+ k: v
312
+ for k, v in kwargs.items()
313
+ if k not in ["bypass_hooks", "bypass_validation"]
314
+ }
354
315
  if inheritance_chain is None:
355
316
  inheritance_chain = self._get_inheritance_chain()
356
317
 
@@ -383,9 +344,9 @@ class HookQuerySet(models.QuerySet):
383
344
 
384
345
  # Step 1: Do O(n) normal inserts into parent tables to get primary keys back
385
346
  # Get bypass_hooks from kwargs
386
- bypass_hooks = kwargs.get('bypass_hooks', False)
387
- bypass_validation = kwargs.get('bypass_validation', False)
388
-
347
+ bypass_hooks = kwargs.get("bypass_hooks", False)
348
+ bypass_validation = kwargs.get("bypass_validation", False)
349
+
389
350
  for obj in batch:
390
351
  parent_instances = {}
391
352
  current_parent = None
@@ -393,32 +354,35 @@ class HookQuerySet(models.QuerySet):
393
354
  parent_obj = self._create_parent_instance(
394
355
  obj, model_class, current_parent
395
356
  )
396
-
357
+
397
358
  # Fire parent hooks if not bypassed
398
359
  if not bypass_hooks:
399
360
  ctx = HookContext(model_class)
400
361
  if not bypass_validation:
401
362
  engine.run(model_class, VALIDATE_CREATE, [parent_obj], ctx=ctx)
402
363
  engine.run(model_class, BEFORE_CREATE, [parent_obj], ctx=ctx)
403
-
364
+
404
365
  # Use Django's base manager to create the object and get PKs back
405
366
  # This bypasses hooks and the MTI exception
406
367
  field_values = {
407
368
  field.name: getattr(parent_obj, field.name)
408
369
  for field in model_class._meta.local_fields
409
- if hasattr(parent_obj, field.name) and getattr(parent_obj, field.name) is not None
370
+ if hasattr(parent_obj, field.name)
371
+ and getattr(parent_obj, field.name) is not None
410
372
  }
411
- created_obj = model_class._base_manager.using(self.db).create(**field_values)
412
-
373
+ created_obj = model_class._base_manager.using(self.db).create(
374
+ **field_values
375
+ )
376
+
413
377
  # Update the parent_obj with the created object's PK
414
378
  parent_obj.pk = created_obj.pk
415
379
  parent_obj._state.adding = False
416
380
  parent_obj._state.db = self.db
417
-
381
+
418
382
  # Fire AFTER_CREATE hooks for parent
419
383
  if not bypass_hooks:
420
384
  engine.run(model_class, AFTER_CREATE, [parent_obj], ctx=ctx)
421
-
385
+
422
386
  parent_instances[model_class] = parent_obj
423
387
  current_parent = parent_obj
424
388
  parent_objects_map[id(obj)] = parent_instances
@@ -436,10 +400,10 @@ class HookQuerySet(models.QuerySet):
436
400
  if all_child_objects:
437
401
  # Get the base manager's queryset
438
402
  base_qs = child_model._base_manager.using(self.db)
439
-
403
+
440
404
  # Use Django's exact approach: call _prepare_for_bulk_create then partition
441
405
  base_qs._prepare_for_bulk_create(all_child_objects)
442
-
406
+
443
407
  # Implement our own partition since itertools.partition might not be available
444
408
  objs_without_pk, objs_with_pk = [], []
445
409
  for obj in all_child_objects:
@@ -447,44 +411,50 @@ class HookQuerySet(models.QuerySet):
447
411
  objs_with_pk.append(obj)
448
412
  else:
449
413
  objs_without_pk.append(obj)
450
-
414
+
451
415
  # Use Django's internal _batched_insert method
452
416
  opts = child_model._meta
453
417
  # For child models in MTI, we need to include the foreign key to the parent
454
418
  # but exclude the primary key since it's inherited
455
-
419
+
456
420
  # Include all local fields except generated ones
457
421
  # We need to include the foreign key to the parent (business_ptr)
458
422
  fields = [f for f in opts.local_fields if not f.generated]
459
-
423
+
460
424
  with transaction.atomic(using=self.db, savepoint=False):
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
425
+ if objs_with_pk:
426
+ returned_columns = base_qs._batched_insert(
427
+ objs_with_pk,
428
+ fields,
429
+ batch_size=len(objs_with_pk), # Use actual batch size
430
+ )
431
+ for obj_with_pk, results in zip(objs_with_pk, returned_columns):
432
+ for result, field in zip(results, opts.db_returning_fields):
433
+ if field != opts.pk:
434
+ setattr(obj_with_pk, field.attname, result)
435
+ for obj_with_pk in objs_with_pk:
436
+ obj_with_pk._state.adding = False
437
+ obj_with_pk._state.db = self.db
438
+
439
+ if objs_without_pk:
440
+ # For objects without PK, we still need to exclude primary key fields
441
+ fields = [
442
+ f
443
+ for f in fields
444
+ if not isinstance(f, AutoField) and not f.primary_key
445
+ ]
446
+ returned_columns = base_qs._batched_insert(
447
+ objs_without_pk,
448
+ fields,
449
+ batch_size=len(objs_without_pk), # Use actual batch size
450
+ )
451
+ for obj_without_pk, results in zip(
452
+ objs_without_pk, returned_columns
453
+ ):
454
+ for result, field in zip(results, opts.db_returning_fields):
455
+ setattr(obj_without_pk, field.attname, result)
456
+ obj_without_pk._state.adding = False
457
+ obj_without_pk._state.db = self.db
488
458
 
489
459
  # Step 3: Update original objects with generated PKs and state
490
460
  pk_field_name = child_model._meta.pk.name
@@ -537,15 +507,19 @@ class HookQuerySet(models.QuerySet):
537
507
  value = getattr(source_obj, field.name, None)
538
508
  if value is not None:
539
509
  setattr(child_obj, field.name, value)
540
-
510
+
541
511
  # Set parent links for MTI
542
512
  for parent_model, parent_instance in parent_instances.items():
543
513
  parent_link = child_model._meta.get_ancestor_link(parent_model)
544
514
  if parent_link:
545
515
  # Set both the foreign key value (the ID) and the object reference
546
516
  # This follows Django's pattern in _set_pk_val
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
517
+ setattr(
518
+ child_obj, parent_link.attname, parent_instance.pk
519
+ ) # Set the foreign key value
520
+ setattr(
521
+ child_obj, parent_link.name, parent_instance
522
+ ) # Set the object reference
549
523
 
550
524
  # Handle auto_now_add and auto_now fields like Django does
551
525
  for field in child_model._meta.local_fields:
@@ -558,4 +532,4 @@ class HookQuerySet(models.QuerySet):
558
532
  elif hasattr(field, "auto_now") and field.auto_now:
559
533
  field.pre_save(child_obj, add=True)
560
534
 
561
- return child_obj
535
+ return child_obj
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-bulk-hooks
3
- Version: 0.1.173
3
+ Version: 0.1.175
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=83LqDkcLuNKi05p-W9qnJ_qmNrf2JuOaoPNYnEfwcSQ,25455
12
+ django_bulk_hooks/queryset.py,sha256=wP1PHzDJ-n_F6RtPzQUdB2j16aALabDl0gbaMJAE2eI,22812
13
13
  django_bulk_hooks/registry.py,sha256=-mQBizJ06nz_tajZBinViKx_uP2Tbc1tIpTEMv7lwKA,705
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,,
14
+ django_bulk_hooks-0.1.175.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
+ django_bulk_hooks-0.1.175.dist-info/METADATA,sha256=HmgU2xtMNjhwLejwg5ehchMkJPSYn6B_JRcRmn3EoXs,6939
16
+ django_bulk_hooks-0.1.175.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
17
+ django_bulk_hooks-0.1.175.dist-info/RECORD,,