django-bulk-hooks 0.2.68__py3-none-any.whl → 0.2.70__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/manager.py +5 -15
- django_bulk_hooks/operations/coordinator.py +12 -0
- django_bulk_hooks/queryset.py +51 -0
- {django_bulk_hooks-0.2.68.dist-info → django_bulk_hooks-0.2.70.dist-info}/METADATA +55 -4
- {django_bulk_hooks-0.2.68.dist-info → django_bulk_hooks-0.2.70.dist-info}/RECORD +7 -7
- {django_bulk_hooks-0.2.68.dist-info → django_bulk_hooks-0.2.70.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.2.68.dist-info → django_bulk_hooks-0.2.70.dist-info}/WHEEL +0 -0
django_bulk_hooks/manager.py
CHANGED
|
@@ -21,29 +21,19 @@ class BulkHookManager(models.Manager):
|
|
|
21
21
|
"""
|
|
22
22
|
Manager that provides hook-aware bulk operations.
|
|
23
23
|
|
|
24
|
-
This
|
|
25
|
-
|
|
24
|
+
This manager automatically applies hook functionality to its querysets.
|
|
25
|
+
It can be used as a base class or composed with other managers using
|
|
26
|
+
the queryset-based approach.
|
|
26
27
|
"""
|
|
27
28
|
|
|
28
29
|
def get_queryset(self):
|
|
29
30
|
"""
|
|
30
31
|
Return a HookQuerySet for this manager.
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
Uses the new with_hooks() method for better composition with other managers.
|
|
33
34
|
"""
|
|
34
35
|
base_queryset = super().get_queryset()
|
|
35
|
-
|
|
36
|
-
# If the base queryset is already a HookQuerySet, return it as-is
|
|
37
|
-
if isinstance(base_queryset, HookQuerySet):
|
|
38
|
-
return base_queryset
|
|
39
|
-
|
|
40
|
-
# Otherwise, create a new HookQuerySet with the same parameters
|
|
41
|
-
return HookQuerySet(
|
|
42
|
-
model=base_queryset.model,
|
|
43
|
-
query=base_queryset.query,
|
|
44
|
-
using=base_queryset._db,
|
|
45
|
-
hints=base_queryset._hints,
|
|
46
|
-
)
|
|
36
|
+
return HookQuerySet.with_hooks(base_queryset)
|
|
47
37
|
|
|
48
38
|
def bulk_create(
|
|
49
39
|
self,
|
|
@@ -938,6 +938,18 @@ class BulkOperationCoordinator:
|
|
|
938
938
|
except Exception as e:
|
|
939
939
|
logger.warning(f" ❌ Failed to extract @select_related from {handler_cls.__name__}.{method_name}: {e}")
|
|
940
940
|
|
|
941
|
+
# AGGRESSIVE APPROACH: Also preload ALL relationship fields on the model
|
|
942
|
+
# This prevents N+1 queries from any relationship access during hook execution
|
|
943
|
+
try:
|
|
944
|
+
for field in self.model_cls._meta.get_fields():
|
|
945
|
+
if field.is_relation and not field.many_to_many and not field.one_to_many:
|
|
946
|
+
# This is a forward foreign key relationship
|
|
947
|
+
field_name = field.name
|
|
948
|
+
logger.info(f" 🔗 AUTO: Adding all relationship fields including {field_name}")
|
|
949
|
+
relationships.add(field_name)
|
|
950
|
+
except Exception as e:
|
|
951
|
+
logger.warning(f" ❌ Failed to extract all relationship fields: {e}")
|
|
952
|
+
|
|
941
953
|
logger.info(f"🔗 BULK PRELOAD: Total extracted relationships for {self.model_cls.__name__}: {list(relationships)}")
|
|
942
954
|
return relationships
|
|
943
955
|
|
django_bulk_hooks/queryset.py
CHANGED
|
@@ -35,6 +35,57 @@ class HookQuerySet(models.QuerySet):
|
|
|
35
35
|
super().__init__(*args, **kwargs)
|
|
36
36
|
self._coordinator = None
|
|
37
37
|
|
|
38
|
+
@classmethod
|
|
39
|
+
def with_hooks(cls, queryset):
|
|
40
|
+
"""
|
|
41
|
+
Apply hook functionality to any queryset.
|
|
42
|
+
|
|
43
|
+
This enables hooks to work with any manager by applying hook
|
|
44
|
+
capabilities at the queryset level rather than through inheritance.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
queryset: Any Django QuerySet instance
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
HookQuerySet instance with the same query parameters
|
|
51
|
+
"""
|
|
52
|
+
if isinstance(queryset, cls):
|
|
53
|
+
return queryset # Already has hooks
|
|
54
|
+
|
|
55
|
+
# Create a new HookQuerySet with the same parameters as the original queryset
|
|
56
|
+
hook_qs = cls(
|
|
57
|
+
model=queryset.model,
|
|
58
|
+
query=queryset.query,
|
|
59
|
+
using=queryset._db,
|
|
60
|
+
hints=getattr(queryset, '_hints', {}),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Preserve any additional attributes from the original queryset
|
|
64
|
+
# This allows composition with other queryset enhancements
|
|
65
|
+
cls._preserve_queryset_attributes(hook_qs, queryset)
|
|
66
|
+
|
|
67
|
+
return hook_qs
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def _preserve_queryset_attributes(cls, hook_qs, original_qs):
|
|
71
|
+
"""
|
|
72
|
+
Preserve attributes from the original queryset.
|
|
73
|
+
|
|
74
|
+
This enables composition with other queryset enhancements like
|
|
75
|
+
queryable properties, annotations, etc.
|
|
76
|
+
"""
|
|
77
|
+
# Copy non-method attributes that might be set by other managers
|
|
78
|
+
for attr_name in dir(original_qs):
|
|
79
|
+
if (not attr_name.startswith('_') and
|
|
80
|
+
not hasattr(cls, attr_name) and
|
|
81
|
+
not callable(getattr(original_qs, attr_name, None))):
|
|
82
|
+
try:
|
|
83
|
+
value = getattr(original_qs, attr_name)
|
|
84
|
+
setattr(hook_qs, attr_name, value)
|
|
85
|
+
except (AttributeError, TypeError):
|
|
86
|
+
# Skip attributes that can't be copied
|
|
87
|
+
continue
|
|
88
|
+
|
|
38
89
|
@property
|
|
39
90
|
def coordinator(self):
|
|
40
91
|
"""Lazy initialization of coordinator"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: django-bulk-hooks
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.70
|
|
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
|
|
@@ -244,17 +244,68 @@ LoanAccount.objects.bulk_update(reordered) # fields are auto-detected
|
|
|
244
244
|
|
|
245
245
|
## 🧩 Integration with Other Managers
|
|
246
246
|
|
|
247
|
-
|
|
247
|
+
### Recommended: QuerySet-based Composition (New Approach)
|
|
248
|
+
|
|
249
|
+
For the best compatibility and to avoid inheritance conflicts, use the queryset-based composition approach:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
from django_bulk_hooks.queryset import HookQuerySet
|
|
253
|
+
from queryable_properties.managers import QueryablePropertiesManager
|
|
254
|
+
|
|
255
|
+
class MyManager(QueryablePropertiesManager):
|
|
256
|
+
"""Manager that combines queryable properties with hooks"""
|
|
257
|
+
|
|
258
|
+
def get_queryset(self):
|
|
259
|
+
# Get the QueryableProperties QuerySet
|
|
260
|
+
qs = super().get_queryset()
|
|
261
|
+
# Apply hooks on top of it
|
|
262
|
+
return HookQuerySet.with_hooks(qs)
|
|
263
|
+
|
|
264
|
+
class Article(models.Model):
|
|
265
|
+
title = models.CharField(max_length=100)
|
|
266
|
+
published = models.BooleanField(default=False)
|
|
267
|
+
|
|
268
|
+
objects = MyManager()
|
|
269
|
+
|
|
270
|
+
# This gives you both queryable properties AND hooks
|
|
271
|
+
# No inheritance conflicts, no MRO issues!
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Alternative: Explicit Hook Application
|
|
275
|
+
|
|
276
|
+
For more control, you can apply hooks explicitly:
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
class MyManager(QueryablePropertiesManager):
|
|
280
|
+
def get_queryset(self):
|
|
281
|
+
return super().get_queryset()
|
|
282
|
+
|
|
283
|
+
def with_hooks(self):
|
|
284
|
+
"""Apply hooks to this queryset"""
|
|
285
|
+
return HookQuerySet.with_hooks(self.get_queryset())
|
|
286
|
+
|
|
287
|
+
# Usage:
|
|
288
|
+
Article.objects.with_hooks().filter(published=True).update(title="Updated")
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Legacy: Manager Inheritance (Not Recommended)
|
|
292
|
+
|
|
293
|
+
The old inheritance approach still works but is not recommended due to potential MRO conflicts:
|
|
248
294
|
|
|
249
295
|
```python
|
|
250
296
|
from django_bulk_hooks.manager import BulkHookManager
|
|
251
297
|
from queryable_properties.managers import QueryablePropertiesManager
|
|
252
298
|
|
|
253
299
|
class MyManager(BulkHookManager, QueryablePropertiesManager):
|
|
254
|
-
pass
|
|
300
|
+
pass # ⚠️ Can cause inheritance conflicts
|
|
255
301
|
```
|
|
256
302
|
|
|
257
|
-
|
|
303
|
+
**Why the new approach is better:**
|
|
304
|
+
- ✅ No inheritance conflicts
|
|
305
|
+
- ✅ No MRO (Method Resolution Order) issues
|
|
306
|
+
- ✅ Works with any manager combination
|
|
307
|
+
- ✅ Cleaner and more maintainable
|
|
308
|
+
- ✅ Follows Django's queryset enhancement patterns
|
|
258
309
|
|
|
259
310
|
Framework needs to:
|
|
260
311
|
Register these methods
|
|
@@ -9,19 +9,19 @@ django_bulk_hooks/enums.py,sha256=Zo8_tJzuzZ2IKfVc7gZ-0tWPT8q1QhqZbAyoh9ZVJbs,38
|
|
|
9
9
|
django_bulk_hooks/factory.py,sha256=ezrVM5U023KZqOBbJXb6lYUP-pE7WJmi8Olh2Ew-7RA,18085
|
|
10
10
|
django_bulk_hooks/handler.py,sha256=SRCrMzgolrruTkvMnYBFmXLR-ABiw0JiH3605PEdCZM,4207
|
|
11
11
|
django_bulk_hooks/helpers.py,sha256=3rH9TJkdCPF7Vu--0tDaZzJg9Yxcv7yoSF1K1_-0psQ,8048
|
|
12
|
-
django_bulk_hooks/manager.py,sha256=
|
|
12
|
+
django_bulk_hooks/manager.py,sha256=k-uhKAzNqkxQzzH775v06g21L-LnAderBywWeJHJy38,4174
|
|
13
13
|
django_bulk_hooks/models.py,sha256=TWN_F-SsLGPx9jrkNT9pmJFR5VsZ0Z_QaVOZOmt7bpw,2434
|
|
14
14
|
django_bulk_hooks/operations/__init__.py,sha256=BtJYjmRhe_sScivLsniDaZmBkm0ZLvcmzXFKL7QY2Xg,550
|
|
15
15
|
django_bulk_hooks/operations/analyzer.py,sha256=Pz8mc-EL8KDOfLQFYiRuN-r0OmINW3nIBhRJJCma-yo,10360
|
|
16
16
|
django_bulk_hooks/operations/bulk_executor.py,sha256=po8V_2H3ULiE0RYJ-wbaRIz52SKhss81UHwuQjlz3H8,26214
|
|
17
|
-
django_bulk_hooks/operations/coordinator.py,sha256=
|
|
17
|
+
django_bulk_hooks/operations/coordinator.py,sha256=NHQWg36IvWAsbQnAppsExOhFQsvlk_VD-aMwWsoo1ao,38572
|
|
18
18
|
django_bulk_hooks/operations/field_utils.py,sha256=cQ9w4xdk-z3PrMLFvRzVV07Wc0D2qbpSepwoupqwQH8,7888
|
|
19
19
|
django_bulk_hooks/operations/mti_handler.py,sha256=Vmz0C0gtYDvbybmb4cDzIaGglSaQK4DQVkaBK-WuQeE,25855
|
|
20
20
|
django_bulk_hooks/operations/mti_plans.py,sha256=HIRJgogHPpm6MV7nZZ-sZhMLUnozpZPV2SzwQHLRzYc,3667
|
|
21
21
|
django_bulk_hooks/operations/record_classifier.py,sha256=It85hJC2K-UsEOLbTR-QBdY5UPV-acQIJ91TSGa7pYo,7053
|
|
22
|
-
django_bulk_hooks/queryset.py,sha256=
|
|
22
|
+
django_bulk_hooks/queryset.py,sha256=tHt1U2O9Hvozg9sdn5MjzAk_I6wDU-LupuKFTfv1SiQ,7449
|
|
23
23
|
django_bulk_hooks/registry.py,sha256=4HxP1mVK2z4VzvlohbEw2359wM21UJZJYagJJ1komM0,7947
|
|
24
|
-
django_bulk_hooks-0.2.
|
|
25
|
-
django_bulk_hooks-0.2.
|
|
26
|
-
django_bulk_hooks-0.2.
|
|
27
|
-
django_bulk_hooks-0.2.
|
|
24
|
+
django_bulk_hooks-0.2.70.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
25
|
+
django_bulk_hooks-0.2.70.dist-info/METADATA,sha256=cgeAfI0qSgIJJDl7WXpsqErM3YzqmuGnIeqPeSUpVOk,10555
|
|
26
|
+
django_bulk_hooks-0.2.70.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
27
|
+
django_bulk_hooks-0.2.70.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|