django-bulk-hooks 0.1.228__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,64 +87,92 @@ 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:
98
109
  ctx = HookContext(model_cls)
99
110
  # Run VALIDATE_UPDATE hooks
100
- engine.run(model_cls, VALIDATE_UPDATE, instances, originals, ctx=ctx)
101
-
102
- # For subqueries, we need to compute the values and apply them to instances
103
- # before running BEFORE_UPDATE hooks
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
104
115
  if has_subquery:
105
- # Create a temporary update to compute the values
106
- # We'll use a subquery to compute values without actually updating
107
- for field_name, value in kwargs.items():
108
- if (hasattr(value, "query") and hasattr(value.query, "model")) or \
109
- (hasattr(value, "get_source_expressions") and value.get_source_expressions()):
110
- # This is a complex expression - compute it for each instance
111
- for instance in instances:
112
- # Create a single-instance queryset to compute the value
113
- single_qs = model_cls._base_manager.filter(pk=instance.pk)
114
- computed_values = single_qs.annotate(computed_field=value).values_list('computed_field', flat=True)
115
- if computed_values:
116
- setattr(instance, field_name, computed_values[0])
117
- else:
118
- # For simple updates, apply the values directly
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:
119
141
  for obj in instances:
120
- for field, value in kwargs.items():
142
+ for field, value in simple_fields.items():
121
143
  setattr(obj, field, value)
122
144
 
123
145
  # Run BEFORE_UPDATE hooks with updated instances
124
146
  engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
125
147
 
126
- if has_subquery:
127
- # For complex expressions, use Django's native update
128
- # This handles Subquery, Case, F expressions, etc. correctly
129
- result = super().update(**kwargs)
130
-
131
- # After updating with complex expressions, we need to reload the instances
132
- # to get the computed values for the AFTER_UPDATE hooks
133
- if not bypass_hooks:
134
- # Reload instances to get computed values
135
- updated_instances = list(model_cls._base_manager.filter(pk__in=pks))
136
- # Maintain the original order
137
- updated_map = {obj.pk: obj for obj in updated_instances}
138
- instances = [updated_map.get(obj.pk, obj) for obj in instances]
139
- else:
140
- # For simple field updates, instances have already been updated in the hook section
141
- # Perform database update using Django's native bulk_update
142
- # We use the base manager to avoid recursion
143
- base_manager = model_cls._base_manager
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
154
+
155
+ is_mti = _is_mti(model_cls)
156
+
157
+ if is_mti:
158
+ # Use MTI-aware bulk update across tables
144
159
  fields_to_update = list(kwargs.keys())
145
- base_manager.bulk_update(instances, fields_to_update)
146
- 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)
147
176
 
148
177
  # Run AFTER_UPDATE hooks only if not bypassed
149
178
  if not bypass_hooks:
@@ -163,7 +192,7 @@ class HookQuerySetMixin:
163
192
  unique_fields=None,
164
193
  bypass_hooks=False,
165
194
  bypass_validation=False,
166
- ):
195
+ ) -> list:
167
196
  """
168
197
  Insert each of the instances into the database with complete hook support.
169
198
 
@@ -251,7 +280,7 @@ class HookQuerySetMixin:
251
280
  @transaction.atomic
252
281
  def bulk_update(
253
282
  self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs
254
- ):
283
+ ) -> int:
255
284
  """
256
285
  Bulk update objects in the database with complete hook support.
257
286
 
@@ -313,10 +342,21 @@ class HookQuerySetMixin:
313
342
  model_cls, BEFORE_UPDATE, objs, originals, ctx=ctx
314
343
  )
315
344
 
316
- # Perform database update using Django's native bulk_update
317
- # We use the base manager to avoid recursion
318
- base_manager = model_cls._base_manager
319
- 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)
320
360
 
321
361
  # Run AFTER_UPDATE hooks
322
362
  if not bypass_hooks:
@@ -326,7 +366,7 @@ class HookQuerySetMixin:
326
366
  return result
327
367
 
328
368
  @transaction.atomic
329
- def bulk_delete(self, objs, **kwargs):
369
+ def bulk_delete(self, objs, **kwargs) -> int:
330
370
  """
331
371
  Delete the given objects from the database with complete hook support.
332
372
 
@@ -339,7 +379,7 @@ class HookQuerySetMixin:
339
379
  model_cls = self.model
340
380
 
341
381
  # Extract custom kwargs
342
- bypass_hooks = kwargs.pop("bypass_hooks", False)
382
+ kwargs.pop("bypass_hooks", False)
343
383
 
344
384
  # Validate inputs
345
385
  if not isinstance(objs, (list, tuple)):
@@ -464,50 +504,83 @@ class HookQuerySetMixin:
464
504
  # Then we can use Django's bulk_create for the child objects
465
505
  parent_objects_map = {}
466
506
 
467
- # Step 1: Do O(n) normal inserts into parent tables to get primary keys back
468
- # Get bypass_hooks from kwargs
507
+ # Step 1: Insert into parent tables to get primary keys back
469
508
  bypass_hooks = kwargs.get("bypass_hooks", False)
470
509
  bypass_validation = kwargs.get("bypass_validation", False)
471
510
 
472
- for obj in batch:
473
- parent_instances = {}
474
- 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}
475
517
  for model_class in inheritance_chain[:-1]:
476
- parent_obj = self._create_parent_instance(
477
- obj, model_class, current_parent
478
- )
518
+ parent_objs = [
519
+ self._create_parent_instance(obj, model_class, current_parents_per_obj[id(obj)])
520
+ for obj in batch
521
+ ]
479
522
 
480
- # Fire parent hooks if not bypassed
481
523
  if not bypass_hooks:
482
524
  ctx = HookContext(model_class)
483
525
  if not bypass_validation:
484
- engine.run(model_class, VALIDATE_CREATE, [parent_obj], ctx=ctx)
485
- engine.run(model_class, BEFORE_CREATE, [parent_obj], ctx=ctx)
486
-
487
- # Use Django's base manager to create the object and get PKs back
488
- # This bypasses hooks and the MTI exception
489
- field_values = {
490
- field.name: getattr(parent_obj, field.name)
491
- for field in model_class._meta.local_fields
492
- if hasattr(parent_obj, field.name)
493
- and getattr(parent_obj, field.name) is not None
494
- }
495
- created_obj = model_class._base_manager.using(self.db).create(
496
- **field_values
497
- )
526
+ engine.run(model_class, VALIDATE_CREATE, parent_objs, ctx=ctx)
527
+ engine.run(model_class, BEFORE_CREATE, parent_objs, ctx=ctx)
498
528
 
499
- # Update the parent_obj with the created object's PK
500
- parent_obj.pk = created_obj.pk
501
- parent_obj._state.adding = False
502
- 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
+ )
503
533
 
504
- # Fire AFTER_CREATE hooks for parent
534
+ # After create hooks
505
535
  if not bypass_hooks:
506
- 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)
507
580
 
508
- parent_instances[model_class] = parent_obj
509
- current_parent = parent_obj
510
- 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
511
584
 
512
585
  # Step 2: Create all child objects and do single bulk insert into childmost table
513
586
  child_model = inheritance_chain[-1]
@@ -733,7 +806,8 @@ class HookQuerySetMixin:
733
806
  # For MTI, we need to handle parent links correctly
734
807
  # The root model (first in chain) has its own PK
735
808
  # Child models use the parent link to reference the root PK
736
- root_model = inheritance_chain[0]
809
+ # Root model (first in chain) has its own PK; kept for clarity
810
+ # root_model = inheritance_chain[0]
737
811
 
738
812
  # Get the primary keys from the objects
739
813
  # If objects have pk set but are not loaded from DB, use those PKs
@@ -817,7 +891,7 @@ class HookQuerySetMixin:
817
891
  **{f"{filter_field}__in": pks}
818
892
  ).update(**case_statements)
819
893
  total_updated += updated_count
820
- except Exception as e:
894
+ except Exception:
821
895
  import traceback
822
896
 
823
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 (highest numbers execute first)
46
- hooks.sort(key=lambda x: x[3], reverse=True)
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,28 +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
- for handler_cls, method_name, condition, priority in hooks:
75
- logger.debug(f" - {handler_cls.__name__}.{method_name} (priority: {priority})")
76
- else:
77
- logger.debug(f"No hooks found for {model.__name__}.{event}")
78
-
79
- return hooks
80
93
 
94
+ event = str(event)
95
+
96
+ with _lock:
97
+ key = (model, event)
98
+ hooks = _hooks.get(key, [])
81
99
 
82
- def list_all_hooks():
83
- """Debug function to list all registered hooks."""
84
- 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)
85
107
 
108
+ # Return a shallow copy to prevent external mutation of registry state
109
+ return list(hooks)
86
110
 
87
- 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:
88
119
  """Clear all registered hooks (mainly for testing)."""
89
- global _hooks
90
- _hooks.clear()
91
- 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.228
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=81NUQAppn2eCsY1Ao6SgvyXsowmxUnabYgY0GHWoo-o,3294
7
- django_bulk_hooks/enums.py,sha256=1pKFHXq1iCceAUitCj9v6WM3PVJpYJx5ZNZD1RzCJUU,87
8
- django_bulk_hooks/handler.py,sha256=sd5kPOZKwKQnxmQGir5u10ZkTHRdN4Hynl_oIWEIYzQ,5857
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=EGFBbRmX_LhwRYFCKzM8I5m8NGCsUEVJp2pfNTcoHe4,378
12
- django_bulk_hooks/queryset.py,sha256=L_C0ICEnD2TxOljn0RckX0W8xB-jLa_2995ZuKDRVJY,34448
13
- django_bulk_hooks/registry.py,sha256=EJBNVDo6VOz2s5zsJbpppiM6JwRmmXLyfwSKXYaT4Fs,2802
14
- django_bulk_hooks-0.1.228.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
- django_bulk_hooks-0.1.228.dist-info/METADATA,sha256=FFM1S50BoNZtBfQyDGhJ4dtvQTsFY1OvNUmrbmHFk0Q,9743
16
- django_bulk_hooks-0.1.228.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
17
- django_bulk_hooks-0.1.228.dist-info/RECORD,,