django-bulk-hooks 0.1.280__py3-none-any.whl → 0.2.1__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,41 +1,288 @@
1
+ """
2
+ Central registry for hook handlers.
3
+
4
+ Provides thread-safe registration and lookup of hooks with
5
+ deterministic priority ordering.
6
+ """
7
+
1
8
  import logging
9
+ import threading
2
10
  from collections.abc import Callable
3
- from typing import Union
11
+ from typing import Dict, List, Optional, Tuple, Type, Union
4
12
 
5
- from django_bulk_hooks.priority import Priority
13
+ from django_bulk_hooks.enums import Priority
6
14
 
7
15
  logger = logging.getLogger(__name__)
8
16
 
9
- _hooks: dict[tuple[type, str], list[tuple[type, str, Callable, int]]] = {}
17
+ # Type alias for hook info tuple
18
+ HookInfo = Tuple[Type, str, Optional[Callable], int]
19
+
20
+
21
+ class HookRegistry:
22
+ """
23
+ Central registry for all hook handlers.
24
+
25
+ Manages registration, lookup, and lifecycle of hooks with
26
+ thread-safe operations and deterministic ordering by priority.
27
+
28
+ This is a singleton - use get_registry() to access the instance.
29
+ """
30
+
31
+ def __init__(self):
32
+ """Initialize an empty registry with thread-safe storage."""
33
+ self._hooks: Dict[Tuple[Type, str], List[HookInfo]] = {}
34
+ self._lock = threading.RLock()
35
+
36
+ def register(
37
+ self,
38
+ model: Type,
39
+ event: str,
40
+ handler_cls: Type,
41
+ method_name: str,
42
+ condition: Optional[Callable],
43
+ priority: Union[int, Priority],
44
+ ) -> None:
45
+ """
46
+ Register a hook handler for a model and event.
47
+
48
+ Args:
49
+ model: Django model class
50
+ event: Event name (e.g., 'after_update', 'before_create')
51
+ handler_cls: Hook handler class
52
+ method_name: Name of the method to call on handler
53
+ condition: Optional condition to filter records
54
+ priority: Execution priority (lower values execute first)
55
+ """
56
+ with self._lock:
57
+ key = (model, event)
58
+ hooks = self._hooks.setdefault(key, [])
59
+
60
+ # Check for duplicates before adding
61
+ hook_info = (handler_cls, method_name, condition, priority)
62
+ if hook_info not in hooks:
63
+ hooks.append(hook_info)
64
+ # Sort by priority (lower values first)
65
+ hooks.sort(key=lambda x: x[3])
66
+ logger.debug(
67
+ f"Registered {handler_cls.__name__}.{method_name} "
68
+ f"for {model.__name__}.{event} (priority={priority})"
69
+ )
70
+ else:
71
+ logger.debug(
72
+ f"Hook {handler_cls.__name__}.{method_name} "
73
+ f"already registered for {model.__name__}.{event}"
74
+ )
75
+
76
+ def get_hooks(self, model: Type, event: str) -> List[HookInfo]:
77
+ """
78
+ Get all hooks for a model and event.
79
+
80
+ Args:
81
+ model: Django model class
82
+ event: Event name
83
+
84
+ Returns:
85
+ List of hook info tuples (handler_cls, method_name, condition, priority)
86
+ sorted by priority (lower values first)
87
+ """
88
+ with self._lock:
89
+ key = (model, event)
90
+ hooks = self._hooks.get(key, [])
91
+
92
+ # Only log when hooks are found or for specific events to reduce noise
93
+ if hooks or event in [
94
+ "after_update",
95
+ "before_update",
96
+ "after_create",
97
+ "before_create",
98
+ ]:
99
+ logger.debug(
100
+ f"get_hooks {model.__name__}.{event} found {len(hooks)} hooks"
101
+ )
102
+
103
+ return hooks
104
+
105
+ def unregister(
106
+ self, model: Type, event: str, handler_cls: Type, method_name: str
107
+ ) -> None:
108
+ """
109
+ Unregister a specific hook handler.
110
+
111
+ Used when child classes override parent hook methods.
112
+
113
+ Args:
114
+ model: Django model class
115
+ event: Event name
116
+ handler_cls: Hook handler class to remove
117
+ method_name: Method name to remove
118
+ """
119
+ with self._lock:
120
+ key = (model, event)
121
+ if key not in self._hooks:
122
+ return
123
+
124
+ hooks = self._hooks[key]
125
+ # Filter out the specific hook
126
+ self._hooks[key] = [
127
+ (h_cls, m_name, cond, pri)
128
+ for h_cls, m_name, cond, pri in hooks
129
+ if not (h_cls == handler_cls and m_name == method_name)
130
+ ]
131
+
132
+ # Clean up empty hook lists
133
+ if not self._hooks[key]:
134
+ del self._hooks[key]
135
+
136
+ logger.debug(
137
+ f"Unregistered {handler_cls.__name__}.{method_name} "
138
+ f"for {model.__name__}.{event}"
139
+ )
140
+
141
+ def clear(self) -> None:
142
+ """
143
+ Clear all registered hooks.
144
+
145
+ Useful for testing to ensure clean state between tests.
146
+ """
147
+ with self._lock:
148
+ self._hooks.clear()
149
+
150
+ # Also clear HookMeta state to ensure complete reset
151
+ from django_bulk_hooks.handler import HookMeta
152
+
153
+ HookMeta._registered.clear()
154
+ HookMeta._class_hook_map.clear()
155
+
156
+ logger.debug("Cleared all registered hooks")
10
157
 
158
+ def list_all(self) -> Dict[Tuple[Type, str], List[HookInfo]]:
159
+ """
160
+ Get all registered hooks for debugging.
11
161
 
162
+ Returns:
163
+ Dictionary mapping (model, event) tuples to lists of hook info
164
+ """
165
+ with self._lock:
166
+ return dict(self._hooks)
167
+
168
+ def count_hooks(
169
+ self, model: Optional[Type] = None, event: Optional[str] = None
170
+ ) -> int:
171
+ """
172
+ Count registered hooks, optionally filtered by model and/or event.
173
+
174
+ Args:
175
+ model: Optional model class to filter by
176
+ event: Optional event name to filter by
177
+
178
+ Returns:
179
+ Number of matching hooks
180
+ """
181
+ with self._lock:
182
+ if model is None and event is None:
183
+ # Count all hooks
184
+ return sum(len(hooks) for hooks in self._hooks.values())
185
+ elif model is not None and event is not None:
186
+ # Count hooks for specific model and event
187
+ return len(self._hooks.get((model, event), []))
188
+ elif model is not None:
189
+ # Count all hooks for a model
190
+ return sum(
191
+ len(hooks)
192
+ for (m, _), hooks in self._hooks.items()
193
+ if m == model
194
+ )
195
+ else: # event is not None
196
+ # Count all hooks for an event
197
+ return sum(
198
+ len(hooks)
199
+ for (_, e), hooks in self._hooks.items()
200
+ if e == event
201
+ )
202
+
203
+
204
+ # Global singleton registry
205
+ _registry: Optional[HookRegistry] = None
206
+ _registry_lock = threading.Lock()
207
+
208
+
209
+ def get_registry() -> HookRegistry:
210
+ """
211
+ Get the global hook registry instance.
212
+
213
+ Creates the registry on first access (singleton pattern).
214
+ Thread-safe initialization.
215
+
216
+ Returns:
217
+ HookRegistry singleton instance
218
+ """
219
+ global _registry
220
+
221
+ if _registry is None:
222
+ with _registry_lock:
223
+ # Double-checked locking
224
+ if _registry is None:
225
+ _registry = HookRegistry()
226
+
227
+ return _registry
228
+
229
+
230
+ # Backward-compatible module-level functions
12
231
  def register_hook(
13
- model, event, handler_cls, method_name, condition, priority: Union[int, Priority]
14
- ):
15
- key = (model, event)
16
- hooks = _hooks.setdefault(key, [])
17
- hooks.append((handler_cls, method_name, condition, priority))
18
- # Sort by priority (lower values first)
19
- hooks.sort(key=lambda x: x[3])
20
- logger.debug(f"Registered {handler_cls.__name__}.{method_name} for {model.__name__}.{event}")
21
-
22
-
23
- def get_hooks(model, event):
24
- key = (model, event)
25
- hooks = _hooks.get(key, [])
26
- # Only log when hooks are found or for specific events to reduce noise
27
- if hooks or event in ['after_update', 'before_update', 'after_create', 'before_create']:
28
- logger.debug(f"get_hooks {model.__name__}.{event} found {len(hooks)} hooks")
29
- return hooks
30
-
31
-
32
- def clear_hooks():
33
- """Clear all registered hooks. Useful for testing."""
34
- global _hooks
35
- _hooks.clear()
36
- logger.debug("Cleared all registered hooks")
37
-
38
-
39
- def list_all_hooks():
40
- """Debug function to list all registered hooks"""
41
- return _hooks
232
+ model: Type,
233
+ event: str,
234
+ handler_cls: Type,
235
+ method_name: str,
236
+ condition: Optional[Callable],
237
+ priority: Union[int, Priority],
238
+ ) -> None:
239
+ """
240
+ Register a hook handler (backward-compatible function).
241
+
242
+ Delegates to the global registry instance.
243
+ """
244
+ registry = get_registry()
245
+ registry.register(model, event, handler_cls, method_name, condition, priority)
246
+
247
+
248
+ def get_hooks(model: Type, event: str) -> List[HookInfo]:
249
+ """
250
+ Get hooks for a model and event (backward-compatible function).
251
+
252
+ Delegates to the global registry instance.
253
+ """
254
+ registry = get_registry()
255
+ return registry.get_hooks(model, event)
256
+
257
+
258
+ def unregister_hook(
259
+ model: Type, event: str, handler_cls: Type, method_name: str
260
+ ) -> None:
261
+ """
262
+ Unregister a hook handler (backward-compatible function).
263
+
264
+ Delegates to the global registry instance.
265
+ """
266
+ registry = get_registry()
267
+ registry.unregister(model, event, handler_cls, method_name)
268
+
269
+
270
+ def clear_hooks() -> None:
271
+ """
272
+ Clear all registered hooks (backward-compatible function).
273
+
274
+ Delegates to the global registry instance.
275
+ Useful for testing.
276
+ """
277
+ registry = get_registry()
278
+ registry.clear()
279
+
280
+
281
+ def list_all_hooks() -> Dict[Tuple[Type, str], List[HookInfo]]:
282
+ """
283
+ List all registered hooks (backward-compatible function).
284
+
285
+ Delegates to the global registry instance.
286
+ """
287
+ registry = get_registry()
288
+ return registry.list_all()
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: django-bulk-hooks
3
- Version: 0.1.280
3
+ Version: 0.2.1
4
4
  Summary: Hook-style hooks for Django bulk operations like bulk_create and bulk_update.
5
- Home-page: https://github.com/AugendLimited/django-bulk-hooks
6
5
  License: MIT
7
6
  Keywords: django,bulk,hooks
8
7
  Author: Konrad Beck
@@ -14,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.11
14
13
  Classifier: Programming Language :: Python :: 3.12
15
14
  Classifier: Programming Language :: Python :: 3.13
16
15
  Requires-Dist: django (>=5.2.0,<6.0.0)
16
+ Project-URL: Homepage, https://github.com/AugendLimited/django-bulk-hooks
17
17
  Project-URL: Repository, https://github.com/AugendLimited/django-bulk-hooks
18
18
  Description-Content-Type: text/markdown
19
19
 
@@ -84,39 +84,39 @@ class AccountHooks(Hook):
84
84
 
85
85
  ### Individual Model Operations
86
86
 
87
- The `HookModelMixin` automatically triggers hooks for individual model operations:
87
+ The `HookModelMixin` automatically hooks hooks for individual model operations:
88
88
 
89
89
  ```python
90
- # These will trigger BEFORE_CREATE and AFTER_CREATE hooks
90
+ # These will hook BEFORE_CREATE and AFTER_CREATE hooks
91
91
  account = Account.objects.create(balance=100.00)
92
92
  account.save() # for new instances
93
93
 
94
- # These will trigger BEFORE_UPDATE and AFTER_UPDATE hooks
94
+ # These will hook BEFORE_UPDATE and AFTER_UPDATE hooks
95
95
  account.balance = 200.00
96
96
  account.save() # for existing instances
97
97
 
98
- # This will trigger BEFORE_DELETE and AFTER_DELETE hooks
98
+ # This will hook BEFORE_DELETE and AFTER_DELETE hooks
99
99
  account.delete()
100
100
  ```
101
101
 
102
102
  ### Bulk Operations
103
103
 
104
- Bulk operations also trigger the same hooks:
104
+ Bulk operations also hook the same hooks:
105
105
 
106
106
  ```python
107
- # Bulk create - triggers BEFORE_CREATE and AFTER_CREATE hooks
107
+ # Bulk create - hooks BEFORE_CREATE and AFTER_CREATE hooks
108
108
  accounts = [
109
109
  Account(balance=100.00),
110
110
  Account(balance=200.00),
111
111
  ]
112
112
  Account.objects.bulk_create(accounts)
113
113
 
114
- # Bulk update - triggers BEFORE_UPDATE and AFTER_UPDATE hooks
114
+ # Bulk update - hooks BEFORE_UPDATE and AFTER_UPDATE hooks
115
115
  for account in accounts:
116
116
  account.balance *= 1.1
117
117
  Account.objects.bulk_update(accounts) # fields are auto-detected
118
118
 
119
- # Bulk delete - triggers BEFORE_DELETE and AFTER_DELETE hooks
119
+ # Bulk delete - hooks BEFORE_DELETE and AFTER_DELETE hooks
120
120
  Account.objects.bulk_delete(accounts)
121
121
  ```
122
122
 
@@ -125,10 +125,10 @@ Account.objects.bulk_delete(accounts)
125
125
  Queryset operations are also supported:
126
126
 
127
127
  ```python
128
- # Queryset update - triggers BEFORE_UPDATE and AFTER_UPDATE hooks
128
+ # Queryset update - hooks BEFORE_UPDATE and AFTER_UPDATE hooks
129
129
  Account.objects.update(balance=0.00)
130
130
 
131
- # Queryset delete - triggers BEFORE_DELETE and AFTER_DELETE hooks
131
+ # Queryset delete - hooks BEFORE_DELETE and AFTER_DELETE hooks
132
132
  Account.objects.delete()
133
133
  ```
134
134
 
@@ -180,7 +180,7 @@ Django's `bulk_` methods bypass signals and `save()`. This package fills that ga
180
180
  - **NEW**: Individual model lifecycle hooks that work with `save()` and `delete()`
181
181
  - Scalable performance via chunking (default 200)
182
182
  - Support for `@hook` decorators and centralized hook classes
183
- - **NEW**: Automatic hook triggering for admin operations and other Django features
183
+ - **NEW**: Automatic hook hooking for admin operations and other Django features
184
184
  - **NEW**: Proper ordering guarantees for old/new record pairing in hooks (Salesforce-like behavior)
185
185
 
186
186
  ## 📦 Usage Examples
@@ -188,7 +188,7 @@ Django's `bulk_` methods bypass signals and `save()`. This package fills that ga
188
188
  ### Individual Model Operations
189
189
 
190
190
  ```python
191
- # These automatically trigger hooks
191
+ # These automatically hook hooks
192
192
  account = Account.objects.create(balance=100.00)
193
193
  account.balance = 200.00
194
194
  account.save()
@@ -198,7 +198,7 @@ account.delete()
198
198
  ### Bulk Operations
199
199
 
200
200
  ```python
201
- # These also trigger hooks
201
+ # These also hook hooks
202
202
  Account.objects.bulk_create(accounts)
203
203
  Account.objects.bulk_update(accounts) # fields are auto-detected
204
204
  Account.objects.bulk_delete(accounts)
@@ -256,6 +256,13 @@ class MyManager(BulkHookManager, QueryablePropertiesManager):
256
256
 
257
257
  This approach uses the industry-standard injection pattern, similar to how `QueryablePropertiesManager` works, ensuring both functionalities work seamlessly together without any framework-specific knowledge.
258
258
 
259
+ Framework needs to:
260
+ Register these methods
261
+ Know when to execute them (BEFORE_UPDATE, AFTER_UPDATE)
262
+ Execute them in priority order
263
+ Pass ChangeSet to them
264
+ Handle errors (rollback on failure)
265
+
259
266
  ## 📝 License
260
267
 
261
268
  MIT © 2024 Augend / Konrad Beck
@@ -0,0 +1,25 @@
1
+ django_bulk_hooks/__init__.py,sha256=4QlWY5rqR9o2ddrNB-ypM4s1GtNJ1ZvL2ABhybaPpio,1823
2
+ django_bulk_hooks/changeset.py,sha256=WALeiWDcjOBNdCKeidVKOPKAySKj9ZOvUJ-kWaVZYhM,7444
3
+ django_bulk_hooks/conditions.py,sha256=tNnQZvcR-jOB8ZzpoVd03PVIy8rjiFdzmufD5mP7fTg,8107
4
+ django_bulk_hooks/constants.py,sha256=PxpEETaO6gdENcTPoXS586lerGKVP3nmjpDvOkmhYxI,509
5
+ django_bulk_hooks/context.py,sha256=mqaC5-yESDTA5ruI7fuXlt8qSgKuOFp0mjq7h1-4HdQ,1926
6
+ django_bulk_hooks/debug_utils.py,sha256=6T32E_Pms6gbCl94A55fJAe_ynFsK_CJBTaPcsG8tik,4578
7
+ django_bulk_hooks/decorators.py,sha256=UXvQ_tlEbHpmafLG2LZ9WsrvvoQlVACs8aNe8UrtPFE,9601
8
+ django_bulk_hooks/dispatcher.py,sha256=L5_hSqENuKXDftJOdMetfjdZkiakUgkheqU8HpWKaOI,8214
9
+ django_bulk_hooks/enums.py,sha256=Zo8_tJzuzZ2IKfVc7gZ-0tWPT8q1QhqZbAyoh9ZVJbs,381
10
+ django_bulk_hooks/factory.py,sha256=JmjQiJPfAnytXrO6r6qOadX5yX0-sfpbZ9V8nwX3MAg,20013
11
+ django_bulk_hooks/handler.py,sha256=2-k0GPWGSQ6acfvV0qJgDH8aa0z51DqdpX5vSJ6Uawk,4759
12
+ django_bulk_hooks/helpers.py,sha256=Yopvl588VbKOi2kHEsQcEcI5jw5jiNA2MuF6Ce1VP0c,3174
13
+ django_bulk_hooks/manager.py,sha256=3mFzB0ZzHHeXWdKGObZD_H0NlskHJc8uYBF69KKdAXU,4068
14
+ django_bulk_hooks/models.py,sha256=62tn5wL55EjJVOsZofMluhEJB8bH7CzBvH0vd214_RY,2570
15
+ django_bulk_hooks/operations/__init__.py,sha256=5L5NnwiFw8Yn5WO6-38eGdCYBkA0URpwyDcAdeYfc5w,550
16
+ django_bulk_hooks/operations/analyzer.py,sha256=S9qcLRM_VBR6Cy_ObUq0Mok8bp07ALLPDF_S0Yypi2k,6507
17
+ django_bulk_hooks/operations/bulk_executor.py,sha256=Xxv-BuLfX14-daSRPBkrMQgwgXBXbC0dcWTcMNlNjXs,4737
18
+ django_bulk_hooks/operations/coordinator.py,sha256=HMJyvntKXo4aAOwElrvS0F05zoOllfPvYakdAr6JCkk,12326
19
+ django_bulk_hooks/operations/mti_handler.py,sha256=9QLpQCrtaq2sDg-Bb6B-1iVHgSRxe7p8YfbJDxbdpwE,2980
20
+ django_bulk_hooks/queryset.py,sha256=ody4MXrRREL27Ts2ey1UpS0tb5Dxnw-6kN3unxPQ3zY,5860
21
+ django_bulk_hooks/registry.py,sha256=UPerNhtVz_9tKZqrYSZD2LhjAcs4F6hVUuk8L5oOeHc,8821
22
+ django_bulk_hooks-0.2.1.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
23
+ django_bulk_hooks-0.2.1.dist-info/METADATA,sha256=n1Ji7-lnk8Q0HC6ojG_uwyo_3qcv4_3HbXh0UM0Bcl8,9264
24
+ django_bulk_hooks-0.2.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
25
+ django_bulk_hooks-0.2.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.1
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,78 +0,0 @@
1
- import logging
2
-
3
- from django.core.exceptions import ValidationError
4
-
5
- from django_bulk_hooks.registry import get_hooks
6
-
7
- logger = logging.getLogger(__name__)
8
-
9
-
10
- def run(model_cls, event, new_records, old_records=None, ctx=None):
11
- """
12
- Run hooks for a given model, event, and records.
13
- """
14
- if not new_records:
15
- return
16
-
17
- # Get hooks for this model and event
18
- hooks = get_hooks(model_cls, event)
19
-
20
- if not hooks:
21
- return
22
-
23
- import traceback
24
-
25
- stack = traceback.format_stack()
26
- # Safely get model name, fallback to str representation if __name__ not available
27
- model_name = getattr(model_cls, '__name__', str(model_cls))
28
- logger.debug(f"engine.run {model_name}.{event} {len(new_records)} records")
29
-
30
- # Check if we're in a bypass context
31
- if ctx and hasattr(ctx, 'bypass_hooks') and ctx.bypass_hooks:
32
- logger.debug("engine.run bypassed")
33
- return
34
-
35
- # For BEFORE_* events, run model.clean() first for validation
36
- if event.lower().startswith("before_"):
37
- for instance in new_records:
38
- try:
39
- instance.clean()
40
- except ValidationError as e:
41
- logger.error("Validation failed for %s: %s", instance, e)
42
- raise
43
-
44
- # Process hooks
45
- for handler_cls, method_name, condition, priority in hooks:
46
- # Safely get handler class name
47
- handler_name = getattr(handler_cls, '__name__', str(handler_cls))
48
- logger.debug(f"Processing {handler_name}.{method_name}")
49
- handler_instance = handler_cls()
50
- func = getattr(handler_instance, method_name)
51
-
52
- to_process_new = []
53
- to_process_old = []
54
-
55
- for new, original in zip(
56
- new_records,
57
- old_records or [None] * len(new_records),
58
- strict=True,
59
- ):
60
- if not condition:
61
- to_process_new.append(new)
62
- to_process_old.append(original)
63
- else:
64
- condition_result = condition.check(new, original)
65
- if condition_result:
66
- to_process_new.append(new)
67
- to_process_old.append(original)
68
-
69
- if to_process_new:
70
- logger.debug(f"Executing {handler_name}.{method_name} for {len(to_process_new)} records")
71
- try:
72
- func(
73
- new_records=to_process_new,
74
- old_records=to_process_old if any(to_process_old) else None,
75
- )
76
- except Exception as e:
77
- logger.debug(f"Hook execution failed: {e}")
78
- raise
@@ -1,16 +0,0 @@
1
- from enum import IntEnum
2
-
3
-
4
- class Priority(IntEnum):
5
- """
6
- Named priorities for django-bulk-hooks hooks.
7
-
8
- Lower values run earlier (higher priority).
9
- Hooks are sorted in ascending order.
10
- """
11
-
12
- HIGHEST = 0 # runs first
13
- HIGH = 25 # runs early
14
- NORMAL = 50 # default ordering
15
- LOW = 75 # runs later
16
- LOWEST = 100 # runs last
@@ -1,17 +0,0 @@
1
- django_bulk_hooks/__init__.py,sha256=6afmyFwRwC4E9CSWyQdONFJUPl5PeXp3ZuTebd7Ic0Y,158
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=jlLsqGZbj__J0-iBUp1D6jTrlDEiX3qIo0XlywW4D9I,2244
5
- django_bulk_hooks/decorators.py,sha256=k70-BzWwS3wZu_uph5B5qXd6YpwXLQ9hMpOzPUy6iMI,8256
6
- django_bulk_hooks/engine.py,sha256=M3b7Rcb65PYAZTLfWrIRi99BUBPgSLCryL3MSjMVlfQ,2663
7
- django_bulk_hooks/enums.py,sha256=Zo8_tJzuzZ2IKfVc7gZ-0tWPT8q1QhqZbAyoh9ZVJbs,381
8
- django_bulk_hooks/handler.py,sha256=e_GACTQT-pFF-zL7POeo232MgOikUoCLcxDVInAUiBw,6207
9
- django_bulk_hooks/manager.py,sha256=3jNWL-EkvGScsliNc7mW-ozQCG6HyaEevI1u1BFS4AA,3836
10
- django_bulk_hooks/models.py,sha256=WtSfc4GBOG_oOt8n37cVvid0MtFIGze9JYKSixil2y0,4370
11
- django_bulk_hooks/priority.py,sha256=HG_2D35nga68lBCZmSXTcplXrjFoRgZFRDOy4ROKonY,376
12
- django_bulk_hooks/queryset.py,sha256=0yTN0PApX5xAIatUEfceU1bRRs71tUp4qFsJz26FpBY,99765
13
- django_bulk_hooks/registry.py,sha256=GRUTGVQEO2sdkC9OaZ9Q3U7mM-3Ix83uTyvrlTtpatw,1317
14
- django_bulk_hooks-0.1.280.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
- django_bulk_hooks-0.1.280.dist-info/METADATA,sha256=alNdArO3XON_hhjY7i7GVtkpt1Y7fWFv99AvcDMp8SA,9103
16
- django_bulk_hooks-0.1.280.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
17
- django_bulk_hooks-0.1.280.dist-info/RECORD,,