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

@@ -1,6 +1,6 @@
1
1
  import logging
2
2
 
3
- from django.db import models, transaction
3
+ from django.db import models, transaction, connections
4
4
  from django.db.models import AutoField, Case, Value, When
5
5
  from django_bulk_hooks import engine
6
6
  from django_bulk_hooks.constants import (
@@ -26,7 +26,7 @@ class HookQuerySetMixin:
26
26
  """
27
27
 
28
28
  @transaction.atomic
29
- def delete(self):
29
+ def delete(self) -> int:
30
30
  """
31
31
  Delete objects from the database with complete hook support.
32
32
 
@@ -61,7 +61,7 @@ class HookQuerySetMixin:
61
61
  return result
62
62
 
63
63
  @transaction.atomic
64
- def update(self, **kwargs):
64
+ def update(self, **kwargs) -> int:
65
65
  """
66
66
  Update objects with field values and run complete hook cycle.
67
67
 
@@ -72,6 +72,7 @@ class HookQuerySetMixin:
72
72
  """
73
73
  # Extract custom parameters
74
74
  bypass_hooks = kwargs.pop('bypass_hooks', False)
75
+ bypass_validation = kwargs.pop('bypass_validation', False)
75
76
 
76
77
  instances = list(self)
77
78
  if not instances:
@@ -86,41 +87,97 @@ class HookQuerySetMixin:
86
87
  }
87
88
  originals = [original_map.get(obj.pk) for obj in instances]
88
89
 
89
- # Check if any of the update values are complex database expressions
90
- has_subquery = any(
91
- (hasattr(value, "query") and hasattr(value.query, "model"))
92
- or (hasattr(value, "get_source_expressions") and value.get_source_expressions())
93
- for value in kwargs.values()
94
- )
90
+ # Identify complex database expressions (Subquery, Case, F, CombinedExpression, etc.)
91
+ complex_fields = {}
92
+ simple_fields = {}
93
+ for field_name, value in kwargs.items():
94
+ is_complex = (
95
+ (hasattr(value, "query") and hasattr(value.query, "model"))
96
+ or (
97
+ hasattr(value, "get_source_expressions")
98
+ and value.get_source_expressions()
99
+ )
100
+ )
101
+ if is_complex:
102
+ complex_fields[field_name] = value
103
+ else:
104
+ simple_fields[field_name] = value
105
+ has_subquery = bool(complex_fields)
95
106
 
96
107
  # Run hooks only if not bypassed
97
108
  if not bypass_hooks:
109
+ ctx = HookContext(model_cls)
98
110
  # Run VALIDATE_UPDATE hooks
99
- engine.run(model_cls, VALIDATE_UPDATE, instances, originals, HookContext(model_cls))
111
+ if not bypass_validation:
112
+ engine.run(model_cls, VALIDATE_UPDATE, instances, originals, ctx=ctx)
113
+
114
+ # Resolve complex expressions in one shot per field and apply values
115
+ if has_subquery:
116
+ # Build annotations for complex fields
117
+ annotations = {f"__computed_{name}": expr for name, expr in complex_fields.items()}
118
+ annotation_aliases = list(annotations.keys())
119
+ if annotations:
120
+ computed_rows = (
121
+ model_cls._base_manager.filter(pk__in=pks)
122
+ .annotate(**annotations)
123
+ .values("pk", *annotation_aliases)
124
+ )
125
+ computed_map = {}
126
+ for row in computed_rows:
127
+ pk = row["pk"]
128
+ field_values = {}
129
+ for fname in complex_fields.keys():
130
+ alias = f"__computed_{fname}"
131
+ field_values[fname] = row.get(alias)
132
+ computed_map[pk] = field_values
133
+
134
+ for instance in instances:
135
+ values_for_instance = computed_map.get(instance.pk, {})
136
+ for fname, fval in values_for_instance.items():
137
+ setattr(instance, fname, fval)
138
+
139
+ # Apply simple values directly
140
+ if simple_fields:
141
+ for obj in instances:
142
+ for field, value in simple_fields.items():
143
+ setattr(obj, field, value)
100
144
 
101
- # Run BEFORE_UPDATE hooks
102
- engine.run(model_cls, BEFORE_UPDATE, instances, originals, HookContext(model_cls))
145
+ # Run BEFORE_UPDATE hooks with updated instances
146
+ engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
103
147
 
104
- if has_subquery:
105
- # For complex expressions, use Django's native update
106
- # This handles Subquery, Case, F expressions, etc. correctly
107
- result = super().update(**kwargs)
108
- else:
109
- # For simple field updates, apply changes to instances first
110
- for obj in instances:
111
- for field, value in kwargs.items():
112
- setattr(obj, field, value)
148
+ # Determine if model uses MTI
149
+ def _is_mti(m):
150
+ for parent in m._meta.all_parents:
151
+ if parent._meta.concrete_model is not m._meta.concrete_model:
152
+ return True
153
+ return False
113
154
 
114
- # Perform database update using Django's native bulk_update
115
- # We use the base manager to avoid recursion
116
- base_manager = model_cls._base_manager
155
+ is_mti = _is_mti(model_cls)
156
+
157
+ if is_mti:
158
+ # Use MTI-aware bulk update across tables
117
159
  fields_to_update = list(kwargs.keys())
118
- base_manager.bulk_update(instances, fields_to_update)
119
- result = len(instances)
160
+ result = self._mti_bulk_update(instances, fields_to_update)
161
+ else:
162
+ if has_subquery:
163
+ # For complex expressions on single-table models, use Django's native update
164
+ result = super().update(**kwargs)
165
+ if not bypass_hooks:
166
+ # Reload instances to ensure we have DB-final values
167
+ updated_instances = list(model_cls._base_manager.filter(pk__in=pks))
168
+ updated_map = {obj.pk: obj for obj in updated_instances}
169
+ instances = [updated_map.get(obj.pk, obj) for obj in instances]
170
+ else:
171
+ # Simple updates on single-table models
172
+ base_manager = model_cls._base_manager
173
+ fields_to_update = list(kwargs.keys())
174
+ base_manager.bulk_update(instances, fields_to_update)
175
+ result = len(instances)
120
176
 
121
177
  # Run AFTER_UPDATE hooks only if not bypassed
122
178
  if not bypass_hooks:
123
- engine.run(model_cls, AFTER_UPDATE, instances, originals, HookContext(model_cls))
179
+ ctx = HookContext(model_cls)
180
+ engine.run(model_cls, AFTER_UPDATE, instances, originals, ctx=ctx)
124
181
 
125
182
  return result
126
183
 
@@ -135,7 +192,7 @@ class HookQuerySetMixin:
135
192
  unique_fields=None,
136
193
  bypass_hooks=False,
137
194
  bypass_validation=False,
138
- ):
195
+ ) -> list:
139
196
  """
140
197
  Insert each of the instances into the database with complete hook support.
141
198
 
@@ -223,7 +280,7 @@ class HookQuerySetMixin:
223
280
  @transaction.atomic
224
281
  def bulk_update(
225
282
  self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs
226
- ):
283
+ ) -> int:
227
284
  """
228
285
  Bulk update objects in the database with complete hook support.
229
286
 
@@ -273,29 +330,43 @@ class HookQuerySetMixin:
273
330
 
274
331
  # Run VALIDATE_UPDATE hooks
275
332
  if not bypass_validation:
333
+ ctx = HookContext(model_cls)
276
334
  engine.run(
277
- model_cls, VALIDATE_UPDATE, objs, originals, HookContext(model_cls)
335
+ model_cls, VALIDATE_UPDATE, objs, originals, ctx=ctx
278
336
  )
279
337
 
280
338
  # Run BEFORE_UPDATE hooks
281
339
  if not bypass_hooks:
340
+ ctx = HookContext(model_cls)
282
341
  engine.run(
283
- model_cls, BEFORE_UPDATE, objs, originals, HookContext(model_cls)
342
+ model_cls, BEFORE_UPDATE, objs, originals, ctx=ctx
284
343
  )
285
344
 
286
- # Perform database update using Django's native bulk_update
287
- # We use the base manager to avoid recursion
288
- base_manager = model_cls._base_manager
289
- result = base_manager.bulk_update(objs, fields, **kwargs)
345
+ # Determine if model uses MTI
346
+ def _is_mti(m):
347
+ for parent in m._meta.all_parents:
348
+ if parent._meta.concrete_model is not m._meta.concrete_model:
349
+ return True
350
+ return False
351
+
352
+ if _is_mti(model_cls):
353
+ # Use MTI-aware bulk update across tables
354
+ result = self._mti_bulk_update(objs, fields, **kwargs)
355
+ else:
356
+ # Perform database update using Django's native bulk_update
357
+ # We use the base manager to avoid recursion
358
+ base_manager = model_cls._base_manager
359
+ result = base_manager.bulk_update(objs, fields, **kwargs)
290
360
 
291
361
  # Run AFTER_UPDATE hooks
292
362
  if not bypass_hooks:
293
- engine.run(model_cls, AFTER_UPDATE, objs, originals, HookContext(model_cls))
363
+ ctx = HookContext(model_cls)
364
+ engine.run(model_cls, AFTER_UPDATE, objs, originals, ctx=ctx)
294
365
 
295
366
  return result
296
367
 
297
368
  @transaction.atomic
298
- def bulk_delete(self, objs, **kwargs):
369
+ def bulk_delete(self, objs, **kwargs) -> int:
299
370
  """
300
371
  Delete the given objects from the database with complete hook support.
301
372
 
@@ -308,7 +379,7 @@ class HookQuerySetMixin:
308
379
  model_cls = self.model
309
380
 
310
381
  # Extract custom kwargs
311
- bypass_hooks = kwargs.pop("bypass_hooks", False)
382
+ kwargs.pop("bypass_hooks", False)
312
383
 
313
384
  # Validate inputs
314
385
  if not isinstance(objs, (list, tuple)):
@@ -433,50 +504,83 @@ class HookQuerySetMixin:
433
504
  # Then we can use Django's bulk_create for the child objects
434
505
  parent_objects_map = {}
435
506
 
436
- # Step 1: Do O(n) normal inserts into parent tables to get primary keys back
437
- # Get bypass_hooks from kwargs
507
+ # Step 1: Insert into parent tables to get primary keys back
438
508
  bypass_hooks = kwargs.get("bypass_hooks", False)
439
509
  bypass_validation = kwargs.get("bypass_validation", False)
440
510
 
441
- for obj in batch:
442
- parent_instances = {}
443
- current_parent = None
511
+ # If DB supports returning rows from bulk insert, batch per parent model
512
+ supports_returning = connections[self.db].features.can_return_rows_from_bulk_insert
513
+
514
+ if supports_returning:
515
+ # For each parent level in the chain, create instances in batch preserving order
516
+ current_parents_per_obj = {id(obj): None for obj in batch}
444
517
  for model_class in inheritance_chain[:-1]:
445
- parent_obj = self._create_parent_instance(
446
- obj, model_class, current_parent
447
- )
518
+ parent_objs = [
519
+ self._create_parent_instance(obj, model_class, current_parents_per_obj[id(obj)])
520
+ for obj in batch
521
+ ]
448
522
 
449
- # Fire parent hooks if not bypassed
450
523
  if not bypass_hooks:
451
524
  ctx = HookContext(model_class)
452
525
  if not bypass_validation:
453
- engine.run(model_class, VALIDATE_CREATE, [parent_obj], ctx=ctx)
454
- engine.run(model_class, BEFORE_CREATE, [parent_obj], ctx=ctx)
455
-
456
- # Use Django's base manager to create the object and get PKs back
457
- # This bypasses hooks and the MTI exception
458
- field_values = {
459
- field.name: getattr(parent_obj, field.name)
460
- for field in model_class._meta.local_fields
461
- if hasattr(parent_obj, field.name)
462
- and getattr(parent_obj, field.name) is not None
463
- }
464
- created_obj = model_class._base_manager.using(self.db).create(
465
- **field_values
466
- )
526
+ engine.run(model_class, VALIDATE_CREATE, parent_objs, ctx=ctx)
527
+ engine.run(model_class, BEFORE_CREATE, parent_objs, ctx=ctx)
467
528
 
468
- # Update the parent_obj with the created object's PK
469
- parent_obj.pk = created_obj.pk
470
- parent_obj._state.adding = False
471
- parent_obj._state.db = self.db
529
+ # Bulk insert parents using base manager to avoid hook recursion
530
+ created_parents = model_class._base_manager.using(self.db).bulk_create(
531
+ parent_objs, batch_size=len(parent_objs)
532
+ )
472
533
 
473
- # Fire AFTER_CREATE hooks for parent
534
+ # After create hooks
474
535
  if not bypass_hooks:
475
- engine.run(model_class, AFTER_CREATE, [parent_obj], ctx=ctx)
536
+ engine.run(model_class, AFTER_CREATE, created_parents, ctx=ctx)
537
+
538
+ # Update maps and state for next parent level
539
+ for obj, parent_obj in zip(batch, created_parents):
540
+ # Ensure state reflects saved
541
+ parent_obj._state.adding = False
542
+ parent_obj._state.db = self.db
543
+ # Record for this object and level
544
+ if id(obj) not in parent_objects_map:
545
+ parent_objects_map[id(obj)] = {}
546
+ parent_objects_map[id(obj)][model_class] = parent_obj
547
+ current_parents_per_obj[id(obj)] = parent_obj
548
+ else:
549
+ # Fallback: per-row parent inserts (original behavior)
550
+ for obj in batch:
551
+ parent_instances = {}
552
+ current_parent = None
553
+ for model_class in inheritance_chain[:-1]:
554
+ parent_obj = self._create_parent_instance(
555
+ obj, model_class, current_parent
556
+ )
557
+
558
+ if not bypass_hooks:
559
+ ctx = HookContext(model_class)
560
+ if not bypass_validation:
561
+ engine.run(model_class, VALIDATE_CREATE, [parent_obj], ctx=ctx)
562
+ engine.run(model_class, BEFORE_CREATE, [parent_obj], ctx=ctx)
563
+
564
+ field_values = {
565
+ field.name: getattr(parent_obj, field.name)
566
+ for field in model_class._meta.local_fields
567
+ if hasattr(parent_obj, field.name)
568
+ and getattr(parent_obj, field.name) is not None
569
+ }
570
+ created_obj = model_class._base_manager.using(self.db).create(
571
+ **field_values
572
+ )
573
+
574
+ parent_obj.pk = created_obj.pk
575
+ parent_obj._state.adding = False
576
+ parent_obj._state.db = self.db
577
+
578
+ if not bypass_hooks:
579
+ engine.run(model_class, AFTER_CREATE, [parent_obj], ctx=ctx)
476
580
 
477
- parent_instances[model_class] = parent_obj
478
- current_parent = parent_obj
479
- parent_objects_map[id(obj)] = parent_instances
581
+ parent_instances[model_class] = parent_obj
582
+ current_parent = parent_obj
583
+ parent_objects_map[id(obj)] = parent_instances
480
584
 
481
585
  # Step 2: Create all child objects and do single bulk insert into childmost table
482
586
  child_model = inheritance_chain[-1]
@@ -702,7 +806,8 @@ class HookQuerySetMixin:
702
806
  # For MTI, we need to handle parent links correctly
703
807
  # The root model (first in chain) has its own PK
704
808
  # Child models use the parent link to reference the root PK
705
- root_model = inheritance_chain[0]
809
+ # Root model (first in chain) has its own PK; kept for clarity
810
+ # root_model = inheritance_chain[0]
706
811
 
707
812
  # Get the primary keys from the objects
708
813
  # If objects have pk set but are not loaded from DB, use those PKs
@@ -786,7 +891,7 @@ class HookQuerySetMixin:
786
891
  **{f"{filter_field}__in": pks}
787
892
  ).update(**case_statements)
788
893
  total_updated += updated_count
789
- except Exception as e:
894
+ except Exception:
790
895
  import traceback
791
896
 
792
897
  traceback.print_exc()
@@ -1,17 +1,28 @@
1
1
  import logging
2
+ import threading
2
3
  from collections.abc import Callable
3
- from typing import Union
4
+ from typing import Dict, List, Optional, Tuple, Union
4
5
 
5
6
  from django_bulk_hooks.priority import Priority
6
7
 
7
8
  logger = logging.getLogger(__name__)
8
9
 
9
- _hooks: dict[tuple[type, str], list[tuple[type, str, Callable, int]]] = {}
10
+ # Key: (ModelClass, event)
11
+ # Value: list of tuples (handler_cls, method_name, condition_callable, priority)
12
+ _hooks: Dict[Tuple[type, str], List[Tuple[type, str, Callable, int]]] = {}
13
+
14
+ # Registry lock for thread-safety during registration and clearing
15
+ _lock = threading.RLock()
10
16
 
11
17
 
12
18
  def register_hook(
13
- model, event, handler_cls, method_name, condition, priority: Union[int, Priority]
14
- ):
19
+ model: type,
20
+ event: str,
21
+ handler_cls: type,
22
+ method_name: str,
23
+ condition: Optional[Callable],
24
+ priority: Union[int, Priority],
25
+ ) -> None:
15
26
  """
16
27
  Register a hook for a specific model and event.
17
28
 
@@ -26,32 +37,47 @@ def register_hook(
26
37
  if not model or not event or not handler_cls or not method_name:
27
38
  logger.warning("Invalid hook registration parameters")
28
39
  return
29
-
30
- key = (model, event)
31
- hooks = _hooks.setdefault(key, [])
32
-
33
- # Check for duplicate registrations
34
- existing = [h for h in hooks if h[0] == handler_cls and h[1] == method_name]
35
- if existing:
36
- logger.warning(
37
- f"Hook {handler_cls.__name__}.{method_name} already registered "
38
- f"for {model.__name__}.{event}"
40
+
41
+ # Normalize event to str just in case enums are used upstream
42
+ event = str(event)
43
+
44
+ with _lock:
45
+ key = (model, event)
46
+ hooks = _hooks.setdefault(key, [])
47
+
48
+ # Check for duplicate registrations
49
+ duplicate = any(h[0] == handler_cls and h[1] == method_name for h in hooks)
50
+ if duplicate:
51
+ logger.warning(
52
+ "Hook %s.%s already registered for %s.%s",
53
+ handler_cls.__name__,
54
+ method_name,
55
+ model.__name__,
56
+ event,
57
+ )
58
+ return
59
+
60
+ # Add the hook
61
+ hooks.append((handler_cls, method_name, condition, priority))
62
+
63
+ # Sort by priority (highest numbers execute first)
64
+ def sort_key(hook_info: Tuple[type, str, Callable, int]) -> int:
65
+ p = hook_info[3]
66
+ return p.value if hasattr(p, "value") else int(p)
67
+
68
+ hooks.sort(key=sort_key, reverse=True)
69
+
70
+ logger.debug(
71
+ "Registered %s.%s for %s.%s with priority %s",
72
+ handler_cls.__name__,
73
+ method_name,
74
+ model.__name__,
75
+ event,
76
+ priority,
39
77
  )
40
- return
41
-
42
- # Add the hook
43
- hooks.append((handler_cls, method_name, condition, priority))
44
-
45
- # Sort by priority (lowest numbers execute first, matching engine expectation)
46
- hooks.sort(key=lambda x: x[3])
47
-
48
- logger.debug(
49
- f"Registered {handler_cls.__name__}.{method_name} "
50
- f"for {model.__name__}.{event} with priority {priority}"
51
- )
52
78
 
53
79
 
54
- def get_hooks(model, event):
80
+ def get_hooks(model: type, event: str):
55
81
  """
56
82
  Get all registered hooks for a specific model and event.
57
83
 
@@ -64,24 +90,47 @@ def get_hooks(model, event):
64
90
  """
65
91
  if not model or not event:
66
92
  return []
67
-
68
- key = (model, event)
69
- hooks = _hooks.get(key, [])
70
-
71
- # Log hook discovery for debugging
72
- if hooks:
73
- logger.debug(f"Found {len(hooks)} hooks for {model.__name__}.{event}")
74
-
75
- return hooks
76
93
 
94
+ event = str(event)
95
+
96
+ with _lock:
97
+ key = (model, event)
98
+ hooks = _hooks.get(key, [])
77
99
 
78
- def list_all_hooks():
79
- """Debug function to list all registered hooks."""
80
- return _hooks
100
+ # Log hook discovery for debugging
101
+ if hooks:
102
+ logger.debug("Found %d hooks for %s.%s", len(hooks), model.__name__, event)
103
+ for handler_cls, method_name, condition, priority in hooks:
104
+ logger.debug(" - %s.%s (priority: %s)", handler_cls.__name__, method_name, priority)
105
+ else:
106
+ logger.debug("No hooks found for %s.%s", model.__name__, event)
81
107
 
108
+ # Return a shallow copy to prevent external mutation of registry state
109
+ return list(hooks)
82
110
 
83
- def clear_hooks():
111
+
112
+ def list_all_hooks() -> Dict[Tuple[type, str], List[Tuple[type, str, Callable, int]]]:
113
+ """Debug function to list all registered hooks (shallow copy)."""
114
+ with _lock:
115
+ return {k: list(v) for k, v in _hooks.items()}
116
+
117
+
118
+ def clear_hooks() -> None:
84
119
  """Clear all registered hooks (mainly for testing)."""
85
- global _hooks
86
- _hooks.clear()
87
- logger.debug("All hooks cleared")
120
+ with _lock:
121
+ _hooks.clear()
122
+ logger.debug("All hooks cleared")
123
+
124
+
125
+ def unregister_hook(model: type, event: str, handler_cls: type, method_name: str) -> None:
126
+ """Unregister a previously registered hook (safe no-op if not present)."""
127
+ event = str(event)
128
+ with _lock:
129
+ key = (model, event)
130
+ if key not in _hooks:
131
+ return
132
+ _hooks[key] = [
133
+ h for h in _hooks[key] if not (h[0] == handler_cls and h[1] == method_name)
134
+ ]
135
+ if not _hooks[key]:
136
+ del _hooks[key]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-bulk-hooks
3
- Version: 0.1.227
3
+ Version: 0.1.229
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
@@ -0,0 +1,17 @@
1
+ django_bulk_hooks/__init__.py,sha256=uUgpnb9AWjIAcWNpCMqBcOewSnpJjJYH6cjPbQkzoNU,140
2
+ django_bulk_hooks/conditions.py,sha256=7C0enCYMgmIP0WO9Sf_rBbmdHC1yORMELAFoyuBcGgs,7169
3
+ django_bulk_hooks/constants.py,sha256=3x1H1fSUUNo0DZONN7GUVDuySZctTR-jtByBHmAIX5w,303
4
+ django_bulk_hooks/context.py,sha256=L95s1n9N5oBdfdrIA3_zoi0tf7GnDVrQxYaSXPwBkvg,2626
5
+ django_bulk_hooks/decorators.py,sha256=3x16zCsEFIvjg2ld1tUnweH0SWqT0SwrRKRdFs0nC4Y,8523
6
+ django_bulk_hooks/engine.py,sha256=HGAXa9XsG3ilWdoA9rExp6sQZpm02Pkt9CSlq_gRs0I,4974
7
+ django_bulk_hooks/enums.py,sha256=ZSYPwHcjlAMrISOHb9sqNjEfxyv4XupoDoe1hn87VJg,499
8
+ django_bulk_hooks/handler.py,sha256=BKthdeCrgzuLzvy_UUfGLjFFZipAHzuxoelaH6zJVWo,5468
9
+ django_bulk_hooks/manager.py,sha256=uqmvGlskkzKMERf3iexxXCAaQl0v3Wc45BeN-YmfTnE,4060
10
+ django_bulk_hooks/models.py,sha256=1m1POWtfAhWUBrr2uTUEPzBFJpBfAf8WlKUu8muB3z8,6298
11
+ django_bulk_hooks/priority.py,sha256=EGFBbRmX_LhwRYFCKzM8I5m8NGCsUEVJp2pfNTcoHe4,378
12
+ django_bulk_hooks/queryset.py,sha256=u8Abj_W5cABM9pi-vQG_6zGsekbPI6It3tA_zDHUDC4,37480
13
+ django_bulk_hooks/registry.py,sha256=eIjT6xolQYSGbakTOfRDrrc8J6gv0DnYI9rpHAo6XVk,4380
14
+ django_bulk_hooks-0.1.229.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
+ django_bulk_hooks-0.1.229.dist-info/METADATA,sha256=1gWbfBTL8ROM7ng-JOB8fawz9xj5v3X1chqLxuApgLM,9743
16
+ django_bulk_hooks-0.1.229.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
17
+ django_bulk_hooks-0.1.229.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- django_bulk_hooks/__init__.py,sha256=uUgpnb9AWjIAcWNpCMqBcOewSnpJjJYH6cjPbQkzoNU,140
2
- django_bulk_hooks/conditions.py,sha256=V_f3Di2uCVUjoyfiU4BQCHmI4uUIRSRroApDcXlvnso,6349
3
- django_bulk_hooks/constants.py,sha256=3x1H1fSUUNo0DZONN7GUVDuySZctTR-jtByBHmAIX5w,303
4
- django_bulk_hooks/context.py,sha256=_NbGWTq9s66g0vbFIaqN4GlIHWQmFg3EQ44qY8YvvEg,1537
5
- django_bulk_hooks/decorators.py,sha256=WD7Jn7QAvY8F4wOsYlIpjoM9-FdHXSKB7hH9ot-lkYQ,4896
6
- django_bulk_hooks/engine.py,sha256=wiO6HvZkBSHzt1Q3IpXmVppJl30zlsoTheuPPCrGqdU,3118
7
- django_bulk_hooks/enums.py,sha256=Zo8_tJzuzZ2IKfVc7gZ-0tWPT8q1QhqZbAyoh9ZVJbs,381
8
- django_bulk_hooks/handler.py,sha256=IRgJ6uoyD1NifqzZpL2YxOqInJ4MFlS3vZkAa5ZTIVo,5210
9
- django_bulk_hooks/manager.py,sha256=nfWiwU5-yAoxdnQsUMohxtyCpkV0MBv6X3wmipr9eQY,3697
10
- django_bulk_hooks/models.py,sha256=mj4f93L64CN1XBS29RlS02WnZjCNoUkai97vKqjgZQ8,4575
11
- django_bulk_hooks/priority.py,sha256=HG_2D35nga68lBCZmSXTcplXrjFoRgZFRDOy4ROKonY,376
12
- django_bulk_hooks/queryset.py,sha256=xgnuwl8Ha9IhVzKEvjvCX5jUuRZgc0tWYwaKreuDvpw,32478
13
- django_bulk_hooks/registry.py,sha256=h59veo8Qh4Afj8ZP_4Jqr-2S6ebXjUJ7pJTdfqzfXkE,2572
14
- django_bulk_hooks-0.1.227.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
- django_bulk_hooks-0.1.227.dist-info/METADATA,sha256=rY5wk0lHKwxaqEdbVz3BLw3DnRBjMhXSN4syZLuWGX4,9743
16
- django_bulk_hooks-0.1.227.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
17
- django_bulk_hooks-0.1.227.dist-info/RECORD,,