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.

@@ -1,136 +1,34 @@
1
1
  import logging
2
- import threading
3
2
  from collections.abc import Callable
4
- from typing import Dict, List, Optional, Tuple, Union
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
- # 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()
9
+ _hooks: dict[tuple[type, str], list[tuple[type, str, Callable, int]]] = {}
16
10
 
17
11
 
18
12
  def register_hook(
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:
26
- """
27
- Register a hook for a specific model and event.
28
-
29
- Args:
30
- model: The Django model class
31
- event: The hook event (e.g., 'before_create', 'after_update')
32
- handler_cls: The hook handler class
33
- method_name: The method name in the handler class
34
- condition: Optional condition for when the hook should run
35
- priority: Hook execution priority (higher numbers execute first)
36
- """
37
- if not model or not event or not handler_cls or not method_name:
38
- logger.warning("Invalid hook registration parameters")
39
- return
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.230
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('balance'))
63
- def _notify_balance_change(self, new_records, old_records, **kwargs):
64
- for new_record, old_record in zip(new_records, old_records):
65
- if old_record and new_record.balance != old_record.balance:
66
- print(f"Balance changed from {old_record.balance} to {new_record.balance}")
67
- ```
68
-
69
- ### Bulk Operations with Hooks
70
-
71
- ```python
72
- # For complete hook execution, use the update() method
73
- accounts = Account.objects.filter(active=True)
74
- accounts.update(balance=1000) # Runs all hooks automatically
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,,