django-bulk-hooks 0.1.230__py3-none-any.whl → 0.1.232__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/conditions.py +30 -33
- django_bulk_hooks/context.py +15 -43
- django_bulk_hooks/decorators.py +8 -100
- django_bulk_hooks/engine.py +41 -94
- django_bulk_hooks/enums.py +13 -10
- django_bulk_hooks/handler.py +20 -33
- django_bulk_hooks/manager.py +101 -123
- django_bulk_hooks/models.py +11 -45
- django_bulk_hooks/priority.py +6 -6
- django_bulk_hooks/queryset.py +166 -308
- django_bulk_hooks/registry.py +24 -126
- {django_bulk_hooks-0.1.230.dist-info → django_bulk_hooks-0.1.232.dist-info}/METADATA +14 -30
- django_bulk_hooks-0.1.232.dist-info/RECORD +17 -0
- django_bulk_hooks-0.1.230.dist-info/RECORD +0 -17
- {django_bulk_hooks-0.1.230.dist-info → django_bulk_hooks-0.1.232.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.1.230.dist-info → django_bulk_hooks-0.1.232.dist-info}/WHEEL +0 -0
django_bulk_hooks/registry.py
CHANGED
|
@@ -1,136 +1,34 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import threading
|
|
3
2
|
from collections.abc import Callable
|
|
4
|
-
from typing import
|
|
3
|
+
from typing import Union
|
|
5
4
|
|
|
6
5
|
from django_bulk_hooks.priority import Priority
|
|
7
6
|
|
|
8
7
|
logger = logging.getLogger(__name__)
|
|
9
8
|
|
|
10
|
-
|
|
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()
|
|
9
|
+
_hooks: dict[tuple[type, str], list[tuple[type, str, Callable, int]]] = {}
|
|
16
10
|
|
|
17
11
|
|
|
18
12
|
def register_hook(
|
|
19
|
-
model:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
condition
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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,
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def get_hooks(model: type, event: str):
|
|
81
|
-
"""
|
|
82
|
-
Get all registered hooks for a specific model and event.
|
|
83
|
-
|
|
84
|
-
Args:
|
|
85
|
-
model: The Django model class
|
|
86
|
-
event: The hook event
|
|
87
|
-
|
|
88
|
-
Returns:
|
|
89
|
-
List of (handler_cls, method_name, condition, priority) tuples
|
|
90
|
-
"""
|
|
91
|
-
if not model or not event:
|
|
92
|
-
return []
|
|
93
|
-
|
|
94
|
-
event = str(event)
|
|
95
|
-
|
|
96
|
-
with _lock:
|
|
97
|
-
key = (model, event)
|
|
98
|
-
hooks = _hooks.get(key, [])
|
|
99
|
-
|
|
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)
|
|
107
|
-
|
|
108
|
-
# Return a shallow copy to prevent external mutation of registry state
|
|
109
|
-
return list(hooks)
|
|
110
|
-
|
|
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:
|
|
119
|
-
"""Clear all registered hooks (mainly for testing)."""
|
|
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]
|
|
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
|
+
# keep sorted by priority
|
|
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 list_all_hooks():
|
|
33
|
+
"""Debug function to list all registered hooks"""
|
|
34
|
+
return _hooks
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: django-bulk-hooks
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.232
|
|
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
|
|
@@ -59,37 +59,21 @@ from django_bulk_hooks.conditions import WhenFieldHasChanged
|
|
|
59
59
|
from .models import Account
|
|
60
60
|
|
|
61
61
|
class AccountHooks(Hook):
|
|
62
|
-
@hook(AFTER_UPDATE, condition=WhenFieldHasChanged(
|
|
63
|
-
def
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
# For bulk operations with hooks
|
|
77
|
-
accounts = Account.objects.filter(active=True)
|
|
78
|
-
instances = list(accounts)
|
|
79
|
-
|
|
80
|
-
# bulk_update now runs complete hook cycle by default
|
|
81
|
-
accounts.bulk_update(instances, ['balance']) # Runs VALIDATE → BEFORE → DB update → AFTER
|
|
82
|
-
|
|
83
|
-
# To skip hooks (for performance or when called from update())
|
|
84
|
-
accounts.bulk_update(instances, ['balance'], bypass_hooks=True)
|
|
62
|
+
@hook(AFTER_UPDATE, model=Account, condition=WhenFieldHasChanged("balance"))
|
|
63
|
+
def log_balance_change(self, new_records, old_records):
|
|
64
|
+
print("Accounts updated:", [a.pk for a in new_records])
|
|
65
|
+
|
|
66
|
+
@hook(BEFORE_CREATE, model=Account)
|
|
67
|
+
def before_create(self, new_records, old_records):
|
|
68
|
+
for account in new_records:
|
|
69
|
+
if account.balance < 0:
|
|
70
|
+
raise ValueError("Account cannot have negative balance")
|
|
71
|
+
|
|
72
|
+
@hook(AFTER_DELETE, model=Account)
|
|
73
|
+
def after_delete(self, new_records, old_records):
|
|
74
|
+
print("Accounts deleted:", [a.pk for a in old_records])
|
|
85
75
|
```
|
|
86
76
|
|
|
87
|
-
### Understanding Hook Execution
|
|
88
|
-
|
|
89
|
-
- **`update()` method**: Runs complete hook cycle (VALIDATE → BEFORE → DB update → AFTER)
|
|
90
|
-
- **`bulk_update()` method**: Runs complete hook cycle (VALIDATE → BEFORE → DB update → AFTER)
|
|
91
|
-
- **`bypass_hooks=True`**: Skips all hooks for performance or to prevent double execution
|
|
92
|
-
|
|
93
77
|
## 🛠 Supported Hook Events
|
|
94
78
|
|
|
95
79
|
- `BEFORE_CREATE`, `AFTER_CREATE`
|
|
@@ -0,0 +1,17 @@
|
|
|
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=t_kvgex6_iZEFc5LK-srBTZPe-1bdlYdip5LfWOc6lc,2411
|
|
7
|
+
django_bulk_hooks/enums.py,sha256=Zo8_tJzuzZ2IKfVc7gZ-0tWPT8q1QhqZbAyoh9ZVJbs,381
|
|
8
|
+
django_bulk_hooks/handler.py,sha256=xZt8iNdYF-ACz-MnKMY0co6scWINU5V5wC1lyDn844k,4854
|
|
9
|
+
django_bulk_hooks/manager.py,sha256=nfWiwU5-yAoxdnQsUMohxtyCpkV0MBv6X3wmipr9eQY,3697
|
|
10
|
+
django_bulk_hooks/models.py,sha256=exnXYVKEVbYAXhChCP8VdWTnKCnm9DiTcokEIBee1I0,4350
|
|
11
|
+
django_bulk_hooks/priority.py,sha256=HG_2D35nga68lBCZmSXTcplXrjFoRgZFRDOy4ROKonY,376
|
|
12
|
+
django_bulk_hooks/queryset.py,sha256=YSDCMAf24YLeLJrRKCDYwbZInP2mK_Tcuw3EHLDkv_w,32605
|
|
13
|
+
django_bulk_hooks/registry.py,sha256=8UuhniiH5ChSeOKV1UUbqTEiIu25bZXvcHmkaRbxmME,1131
|
|
14
|
+
django_bulk_hooks-0.1.232.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
15
|
+
django_bulk_hooks-0.1.232.dist-info/METADATA,sha256=f8DOScqSoznpOeAOoIYha3lTiWcDp0aEk8lRihqKjiM,9061
|
|
16
|
+
django_bulk_hooks-0.1.232.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
17
|
+
django_bulk_hooks-0.1.232.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
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=0Wd3PKTQbrEfAYe0i0pzAmY_MtUYSo3uUkMOXhrCBuo,4748
|
|
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.230.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
15
|
-
django_bulk_hooks-0.1.230.dist-info/METADATA,sha256=GM3fPWUU0Na_q490s_d95-3N-MMNaXinsicS8E0T5mw,9743
|
|
16
|
-
django_bulk_hooks-0.1.230.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
17
|
-
django_bulk_hooks-0.1.230.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|