django-bulk-hooks 0.1.68__tar.gz → 0.1.70__tar.gz
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-0.1.68 → django_bulk_hooks-0.1.70}/PKG-INFO +4 -4
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/README.md +3 -3
- django_bulk_hooks-0.1.70/django_bulk_hooks/__init__.py +4 -0
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/django_bulk_hooks/conditions.py +0 -17
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/django_bulk_hooks/constants.py +0 -2
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/django_bulk_hooks/engine.py +2 -37
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/django_bulk_hooks/handler.py +4 -18
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/django_bulk_hooks/manager.py +0 -14
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/django_bulk_hooks/registry.py +0 -19
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/pyproject.toml +1 -1
- django_bulk_hooks-0.1.68/django_bulk_hooks/__init__.py +0 -4
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/LICENSE +0 -0
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/django_bulk_hooks/context.py +0 -0
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/django_bulk_hooks/decorators.py +0 -0
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/django_bulk_hooks/enums.py +0 -0
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/django_bulk_hooks/models.py +0 -0
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/django_bulk_hooks/priority.py +0 -0
- {django_bulk_hooks-0.1.68 → django_bulk_hooks-0.1.70}/django_bulk_hooks/queryset.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: django-bulk-hooks
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.70
|
|
4
4
|
Summary: Hook-style hooks for Django bulk operations like bulk_create and bulk_update.
|
|
5
5
|
Home-page: https://github.com/AugendLimited/django-bulk-hooks
|
|
6
6
|
License: MIT
|
|
@@ -54,11 +54,11 @@ class Account(HookModelMixin):
|
|
|
54
54
|
### Create a Hook Handler
|
|
55
55
|
|
|
56
56
|
```python
|
|
57
|
-
from django_bulk_hooks import hook, AFTER_UPDATE,
|
|
57
|
+
from django_bulk_hooks import hook, AFTER_UPDATE, Hook
|
|
58
58
|
from django_bulk_hooks.conditions import WhenFieldHasChanged
|
|
59
59
|
from .models import Account
|
|
60
60
|
|
|
61
|
-
class
|
|
61
|
+
class AccountHooks(Hook):
|
|
62
62
|
@hook(AFTER_UPDATE, model=Account, condition=WhenFieldHasChanged("balance"))
|
|
63
63
|
def log_balance_change(self, new_records, old_records):
|
|
64
64
|
print("Accounts updated:", [a.pk for a in new_records])
|
|
@@ -166,7 +166,7 @@ Account.objects.bulk_delete(accounts)
|
|
|
166
166
|
### Advanced Hook Usage
|
|
167
167
|
|
|
168
168
|
```python
|
|
169
|
-
class
|
|
169
|
+
class AdvancedAccountHooks(Hook):
|
|
170
170
|
@hook(BEFORE_UPDATE, model=Account, condition=WhenFieldHasChanged("balance"))
|
|
171
171
|
def validate_balance_change(self, new_records, old_records):
|
|
172
172
|
for new_account, old_account in zip(new_records, old_records):
|
|
@@ -35,11 +35,11 @@ class Account(HookModelMixin):
|
|
|
35
35
|
### Create a Hook Handler
|
|
36
36
|
|
|
37
37
|
```python
|
|
38
|
-
from django_bulk_hooks import hook, AFTER_UPDATE,
|
|
38
|
+
from django_bulk_hooks import hook, AFTER_UPDATE, Hook
|
|
39
39
|
from django_bulk_hooks.conditions import WhenFieldHasChanged
|
|
40
40
|
from .models import Account
|
|
41
41
|
|
|
42
|
-
class
|
|
42
|
+
class AccountHooks(Hook):
|
|
43
43
|
@hook(AFTER_UPDATE, model=Account, condition=WhenFieldHasChanged("balance"))
|
|
44
44
|
def log_balance_change(self, new_records, old_records):
|
|
45
45
|
print("Accounts updated:", [a.pk for a in new_records])
|
|
@@ -147,7 +147,7 @@ Account.objects.bulk_delete(accounts)
|
|
|
147
147
|
### Advanced Hook Usage
|
|
148
148
|
|
|
149
149
|
```python
|
|
150
|
-
class
|
|
150
|
+
class AdvancedAccountHooks(Hook):
|
|
151
151
|
@hook(BEFORE_UPDATE, model=Account, condition=WhenFieldHasChanged("balance"))
|
|
152
152
|
def validate_balance_change(self, new_records, old_records):
|
|
153
153
|
for new_account, old_account in zip(new_records, old_records):
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
|
-
logger = logging.getLogger(__name__)
|
|
4
|
-
|
|
5
|
-
|
|
6
1
|
def resolve_dotted_attr(instance, dotted_path):
|
|
7
2
|
"""
|
|
8
3
|
Recursively resolve a dotted attribute path, e.g., "type.category".
|
|
@@ -39,12 +34,6 @@ class IsNotEqual(HookCondition):
|
|
|
39
34
|
|
|
40
35
|
def check(self, instance, original_instance=None):
|
|
41
36
|
current = resolve_dotted_attr(instance, self.field)
|
|
42
|
-
logger.debug(
|
|
43
|
-
"%s current=%r, original=%r",
|
|
44
|
-
self.field,
|
|
45
|
-
current,
|
|
46
|
-
resolve_dotted_attr(original_instance, self.field) if original_instance else None,
|
|
47
|
-
)
|
|
48
37
|
if self.only_on_change:
|
|
49
38
|
if original_instance is None:
|
|
50
39
|
return False
|
|
@@ -62,12 +51,6 @@ class IsEqual(HookCondition):
|
|
|
62
51
|
|
|
63
52
|
def check(self, instance, original_instance=None):
|
|
64
53
|
current = resolve_dotted_attr(instance, self.field)
|
|
65
|
-
logger.debug(
|
|
66
|
-
"%s current=%r, original=%r",
|
|
67
|
-
self.field,
|
|
68
|
-
current,
|
|
69
|
-
resolve_dotted_attr(original_instance, self.field) if original_instance else None,
|
|
70
|
-
)
|
|
71
54
|
if self.only_on_change:
|
|
72
55
|
if original_instance is None:
|
|
73
56
|
return False
|
|
@@ -4,8 +4,6 @@ BEFORE_UPDATE = "before_update"
|
|
|
4
4
|
AFTER_UPDATE = "after_update"
|
|
5
5
|
BEFORE_DELETE = "before_delete"
|
|
6
6
|
AFTER_DELETE = "after_delete"
|
|
7
|
-
|
|
8
|
-
# Validation constants - run during clean() for admin/form validation
|
|
9
7
|
VALIDATE_CREATE = "validate_create"
|
|
10
8
|
VALIDATE_UPDATE = "validate_update"
|
|
11
9
|
VALIDATE_DELETE = "validate_delete"
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
|
|
2
3
|
from django.core.exceptions import ValidationError
|
|
4
|
+
|
|
3
5
|
from django_bulk_hooks.registry import get_hooks
|
|
4
6
|
|
|
5
7
|
logger = logging.getLogger(__name__)
|
|
@@ -8,22 +10,11 @@ logger = logging.getLogger(__name__)
|
|
|
8
10
|
def run(model_cls, event, new_instances, original_instances=None, ctx=None):
|
|
9
11
|
hooks = get_hooks(model_cls, event)
|
|
10
12
|
|
|
11
|
-
logger.info(
|
|
12
|
-
"Executing engine.run: model=%s, event=%s, #new_instances=%d, #original_instances=%d, #hooks=%d",
|
|
13
|
-
model_cls.__name__,
|
|
14
|
-
event,
|
|
15
|
-
len(new_instances),
|
|
16
|
-
len(original_instances or []),
|
|
17
|
-
len(hooks),
|
|
18
|
-
)
|
|
19
|
-
|
|
20
13
|
if not hooks:
|
|
21
|
-
logger.info("No hooks found for model=%s, event=%s", model_cls.__name__, event)
|
|
22
14
|
return
|
|
23
15
|
|
|
24
16
|
# For BEFORE_* events, run model.clean() first for validation
|
|
25
17
|
if event.startswith("before_"):
|
|
26
|
-
logger.debug("Running model.clean() for %d instances", len(new_instances))
|
|
27
18
|
for instance in new_instances:
|
|
28
19
|
try:
|
|
29
20
|
instance.clean()
|
|
@@ -35,14 +26,6 @@ def run(model_cls, event, new_instances, original_instances=None, ctx=None):
|
|
|
35
26
|
handler_instance = handler_cls()
|
|
36
27
|
func = getattr(handler_instance, method_name)
|
|
37
28
|
|
|
38
|
-
logger.info(
|
|
39
|
-
"Executing hook %s for %s.%s with priority=%s",
|
|
40
|
-
func.__name__,
|
|
41
|
-
model_cls.__name__,
|
|
42
|
-
event,
|
|
43
|
-
priority,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
29
|
to_process_new = []
|
|
47
30
|
to_process_old = []
|
|
48
31
|
|
|
@@ -51,28 +34,10 @@ def run(model_cls, event, new_instances, original_instances=None, ctx=None):
|
|
|
51
34
|
original_instances or [None] * len(new_instances),
|
|
52
35
|
strict=True,
|
|
53
36
|
):
|
|
54
|
-
logger.debug(
|
|
55
|
-
" considering instance: new=%r, original=%r",
|
|
56
|
-
new,
|
|
57
|
-
original,
|
|
58
|
-
)
|
|
59
|
-
|
|
60
37
|
if not condition or condition.check(new, original):
|
|
61
38
|
to_process_new.append(new)
|
|
62
39
|
to_process_old.append(original)
|
|
63
|
-
logger.debug(" -> will process (passed condition)")
|
|
64
|
-
else:
|
|
65
|
-
logger.debug(" -> skipped (condition returned False)")
|
|
66
40
|
|
|
67
41
|
if to_process_new:
|
|
68
|
-
logger.info(
|
|
69
|
-
"Calling %s on %d instance(s): %r",
|
|
70
|
-
func.__name__,
|
|
71
|
-
len(to_process_new),
|
|
72
|
-
to_process_new,
|
|
73
|
-
)
|
|
74
|
-
|
|
75
42
|
# Call the function with direct arguments
|
|
76
43
|
func(to_process_new, to_process_old if any(to_process_old) else None)
|
|
77
|
-
else:
|
|
78
|
-
logger.debug("No instances to process for hook %s", func.__name__)
|
|
@@ -64,7 +64,7 @@ class HookContextState:
|
|
|
64
64
|
Hook = HookContextState()
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
class
|
|
67
|
+
class HookMeta(type):
|
|
68
68
|
_registered = set()
|
|
69
69
|
|
|
70
70
|
def __new__(mcs, name, bases, namespace):
|
|
@@ -73,14 +73,7 @@ class HookHandlerMeta(type):
|
|
|
73
73
|
if hasattr(method, "hooks_hooks"):
|
|
74
74
|
for model_cls, event, condition, priority in method.hooks_hooks:
|
|
75
75
|
key = (model_cls, event, cls, method_name)
|
|
76
|
-
if key not in
|
|
77
|
-
logger.info(
|
|
78
|
-
"Registering hook via HookHandlerMeta: model=%s, event=%s, handler_cls=%s, method_name=%s",
|
|
79
|
-
model_cls.__name__,
|
|
80
|
-
event,
|
|
81
|
-
cls.__name__,
|
|
82
|
-
method_name,
|
|
83
|
-
)
|
|
76
|
+
if key not in HookMeta._registered:
|
|
84
77
|
register_hook(
|
|
85
78
|
model=model_cls,
|
|
86
79
|
event=event,
|
|
@@ -89,11 +82,11 @@ class HookHandlerMeta(type):
|
|
|
89
82
|
condition=condition,
|
|
90
83
|
priority=priority,
|
|
91
84
|
)
|
|
92
|
-
|
|
85
|
+
HookMeta._registered.add(key)
|
|
93
86
|
return cls
|
|
94
87
|
|
|
95
88
|
|
|
96
|
-
class
|
|
89
|
+
class Hook(metaclass=HookMeta):
|
|
97
90
|
@classmethod
|
|
98
91
|
def handle(
|
|
99
92
|
cls,
|
|
@@ -131,7 +124,6 @@ class HookHandler(metaclass=HookHandlerMeta):
|
|
|
131
124
|
hook_vars.model = model
|
|
132
125
|
|
|
133
126
|
hooks = sorted(get_hooks(model, event), key=lambda x: x[3])
|
|
134
|
-
logger.debug("Processing %d hooks for %s.%s", len(hooks), model.__name__, event)
|
|
135
127
|
|
|
136
128
|
def _execute():
|
|
137
129
|
new_local = new_records or []
|
|
@@ -150,12 +142,6 @@ class HookHandler(metaclass=HookHandlerMeta):
|
|
|
150
142
|
handler = handler_cls()
|
|
151
143
|
method = getattr(handler, method_name)
|
|
152
144
|
|
|
153
|
-
logger.info(
|
|
154
|
-
"Running hook %s.%s on %d items",
|
|
155
|
-
handler_cls.__name__,
|
|
156
|
-
method_name,
|
|
157
|
-
len(new_local),
|
|
158
|
-
)
|
|
159
145
|
try:
|
|
160
146
|
method(
|
|
161
147
|
new_records=new_local,
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
1
|
from django.db import models, transaction
|
|
4
|
-
|
|
5
2
|
from django_bulk_hooks import engine
|
|
6
3
|
from django_bulk_hooks.constants import (
|
|
7
4
|
AFTER_CREATE,
|
|
@@ -17,8 +14,6 @@ from django_bulk_hooks.constants import (
|
|
|
17
14
|
from django_bulk_hooks.context import HookContext
|
|
18
15
|
from django_bulk_hooks.queryset import HookQuerySet
|
|
19
16
|
|
|
20
|
-
logger = logging.getLogger(__name__)
|
|
21
|
-
|
|
22
17
|
|
|
23
18
|
class BulkHookManager(models.Manager):
|
|
24
19
|
CHUNK_SIZE = 200
|
|
@@ -56,10 +51,6 @@ class BulkHookManager(models.Manager):
|
|
|
56
51
|
fields_set = set(fields)
|
|
57
52
|
fields_set.update(modified_fields)
|
|
58
53
|
fields = list(fields_set)
|
|
59
|
-
logger.info(
|
|
60
|
-
"Automatically including modified fields in bulk_update: %s",
|
|
61
|
-
modified_fields,
|
|
62
|
-
)
|
|
63
54
|
|
|
64
55
|
for i in range(0, len(objs), self.CHUNK_SIZE):
|
|
65
56
|
chunk = objs[i : i + self.CHUNK_SIZE]
|
|
@@ -159,9 +150,6 @@ class BulkHookManager(models.Manager):
|
|
|
159
150
|
ctx = HookContext(model_cls)
|
|
160
151
|
|
|
161
152
|
if not bypass_hooks:
|
|
162
|
-
logger.info("Executing hooks for %s", model_cls.__name__)
|
|
163
|
-
logger.info("Number of objects to delete: %d", len(objs))
|
|
164
|
-
|
|
165
153
|
# Run validation hooks first
|
|
166
154
|
if not bypass_validation:
|
|
167
155
|
engine.run(model_cls, VALIDATE_DELETE, objs, ctx=ctx)
|
|
@@ -173,8 +161,6 @@ class BulkHookManager(models.Manager):
|
|
|
173
161
|
model_cls.objects.filter(pk__in=pks).delete()
|
|
174
162
|
|
|
175
163
|
if not bypass_hooks:
|
|
176
|
-
logger.info("Executing AFTER_DELETE hooks for %s", model_cls.__name__)
|
|
177
|
-
logger.info("Number of objects deleted: %d", len(objs))
|
|
178
164
|
engine.run(model_cls, AFTER_DELETE, objs, ctx=ctx)
|
|
179
165
|
|
|
180
166
|
return objs
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
from collections.abc import Callable
|
|
3
2
|
from typing import Union
|
|
4
3
|
|
|
5
4
|
from django_bulk_hooks.priority import Priority
|
|
6
5
|
|
|
7
|
-
logger = logging.getLogger(__name__)
|
|
8
|
-
|
|
9
6
|
_hooks: dict[tuple[type, str], list[tuple[type, str, Callable, int]]] = {}
|
|
10
7
|
|
|
11
8
|
|
|
@@ -17,29 +14,13 @@ def register_hook(
|
|
|
17
14
|
hooks.append((handler_cls, method_name, condition, priority))
|
|
18
15
|
# keep sorted by priority
|
|
19
16
|
hooks.sort(key=lambda x: x[3])
|
|
20
|
-
logger.info(
|
|
21
|
-
"Registering hook: model=%s, event=%s, handler_cls=%s, method_name=%s, condition=%s, priority=%s",
|
|
22
|
-
model.__name__,
|
|
23
|
-
event,
|
|
24
|
-
handler_cls.__name__,
|
|
25
|
-
method_name,
|
|
26
|
-
condition,
|
|
27
|
-
priority,
|
|
28
|
-
)
|
|
29
17
|
|
|
30
18
|
|
|
31
19
|
def get_hooks(model, event):
|
|
32
20
|
hooks = _hooks.get((model, event), [])
|
|
33
|
-
logger.info(
|
|
34
|
-
"Retrieving hooks: model=%s, event=%s, hooks_found=%d",
|
|
35
|
-
model.__name__,
|
|
36
|
-
event,
|
|
37
|
-
len(hooks),
|
|
38
|
-
)
|
|
39
21
|
return hooks
|
|
40
22
|
|
|
41
23
|
|
|
42
24
|
def list_all_hooks():
|
|
43
25
|
"""Debug function to list all registered hooks"""
|
|
44
|
-
logger.debug("All registered hooks: %s", _hooks)
|
|
45
26
|
return _hooks
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|