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.
- django_bulk_hooks/__init__.py +57 -1
- django_bulk_hooks/changeset.py +230 -0
- django_bulk_hooks/conditions.py +49 -11
- django_bulk_hooks/constants.py +4 -0
- django_bulk_hooks/context.py +30 -43
- django_bulk_hooks/debug_utils.py +145 -0
- django_bulk_hooks/decorators.py +158 -103
- django_bulk_hooks/dispatcher.py +235 -0
- django_bulk_hooks/factory.py +565 -0
- django_bulk_hooks/handler.py +86 -159
- django_bulk_hooks/helpers.py +99 -0
- django_bulk_hooks/manager.py +25 -7
- django_bulk_hooks/models.py +39 -78
- django_bulk_hooks/operations/__init__.py +18 -0
- django_bulk_hooks/operations/analyzer.py +208 -0
- django_bulk_hooks/operations/bulk_executor.py +151 -0
- django_bulk_hooks/operations/coordinator.py +369 -0
- django_bulk_hooks/operations/mti_handler.py +103 -0
- django_bulk_hooks/queryset.py +113 -2129
- django_bulk_hooks/registry.py +279 -32
- {django_bulk_hooks-0.1.280.dist-info → django_bulk_hooks-0.2.1.dist-info}/METADATA +23 -16
- django_bulk_hooks-0.2.1.dist-info/RECORD +25 -0
- {django_bulk_hooks-0.1.280.dist-info → django_bulk_hooks-0.2.1.dist-info}/WHEEL +1 -1
- django_bulk_hooks/engine.py +0 -78
- django_bulk_hooks/priority.py +0 -16
- django_bulk_hooks-0.1.280.dist-info/RECORD +0 -17
- {django_bulk_hooks-0.1.280.dist-info → django_bulk_hooks-0.2.1.dist-info}/LICENSE +0 -0
django_bulk_hooks/registry.py
CHANGED
|
@@ -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.
|
|
13
|
+
from django_bulk_hooks.enums import Priority
|
|
6
14
|
|
|
7
15
|
logger = logging.getLogger(__name__)
|
|
8
16
|
|
|
9
|
-
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
|
|
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
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: django-bulk-hooks
|
|
3
|
-
Version: 0.1
|
|
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
|
|
87
|
+
The `HookModelMixin` automatically hooks hooks for individual model operations:
|
|
88
88
|
|
|
89
89
|
```python
|
|
90
|
-
# These will
|
|
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
|
|
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
|
|
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
|
|
104
|
+
Bulk operations also hook the same hooks:
|
|
105
105
|
|
|
106
106
|
```python
|
|
107
|
-
# Bulk create -
|
|
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 -
|
|
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 -
|
|
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 -
|
|
128
|
+
# Queryset update - hooks BEFORE_UPDATE and AFTER_UPDATE hooks
|
|
129
129
|
Account.objects.update(balance=0.00)
|
|
130
130
|
|
|
131
|
-
# Queryset delete -
|
|
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
|
|
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
|
|
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
|
|
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,,
|
django_bulk_hooks/engine.py
DELETED
|
@@ -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
|
django_bulk_hooks/priority.py
DELETED
|
@@ -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,,
|
|
File without changes
|