django-bulk-hooks 0.2.69__tar.gz → 0.2.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.

Files changed (27) hide show
  1. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/PKG-INFO +55 -4
  2. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/README.md +54 -3
  3. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/manager.py +5 -15
  4. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/queryset.py +51 -0
  5. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/pyproject.toml +1 -1
  6. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/LICENSE +0 -0
  7. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/__init__.py +0 -0
  8. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/changeset.py +0 -0
  9. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/conditions.py +0 -0
  10. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/constants.py +0 -0
  11. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/context.py +0 -0
  12. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/decorators.py +0 -0
  13. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/dispatcher.py +0 -0
  14. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/enums.py +0 -0
  15. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/factory.py +0 -0
  16. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/handler.py +0 -0
  17. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/helpers.py +0 -0
  18. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/models.py +0 -0
  19. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/operations/__init__.py +0 -0
  20. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/operations/analyzer.py +0 -0
  21. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/operations/bulk_executor.py +0 -0
  22. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/operations/coordinator.py +0 -0
  23. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/operations/field_utils.py +0 -0
  24. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/operations/mti_handler.py +0 -0
  25. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/operations/mti_plans.py +0 -0
  26. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/operations/record_classifier.py +0 -0
  27. {django_bulk_hooks-0.2.69 → django_bulk_hooks-0.2.70}/django_bulk_hooks/registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-bulk-hooks
3
- Version: 0.2.69
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
- You can extend from `BulkHookManager` to work with other manager classes. The manager uses a cooperative approach that dynamically injects bulk hook functionality into any queryset, ensuring compatibility with other managers.
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
- This approach uses the industry-standard injection pattern, similar to how `QueryablePropertiesManager` works, ensuring both functionalities work seamlessly together without any framework-specific knowledge.
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
@@ -225,17 +225,68 @@ LoanAccount.objects.bulk_update(reordered) # fields are auto-detected
225
225
 
226
226
  ## 🧩 Integration with Other Managers
227
227
 
228
- You can extend from `BulkHookManager` to work with other manager classes. The manager uses a cooperative approach that dynamically injects bulk hook functionality into any queryset, ensuring compatibility with other managers.
228
+ ### Recommended: QuerySet-based Composition (New Approach)
229
+
230
+ For the best compatibility and to avoid inheritance conflicts, use the queryset-based composition approach:
231
+
232
+ ```python
233
+ from django_bulk_hooks.queryset import HookQuerySet
234
+ from queryable_properties.managers import QueryablePropertiesManager
235
+
236
+ class MyManager(QueryablePropertiesManager):
237
+ """Manager that combines queryable properties with hooks"""
238
+
239
+ def get_queryset(self):
240
+ # Get the QueryableProperties QuerySet
241
+ qs = super().get_queryset()
242
+ # Apply hooks on top of it
243
+ return HookQuerySet.with_hooks(qs)
244
+
245
+ class Article(models.Model):
246
+ title = models.CharField(max_length=100)
247
+ published = models.BooleanField(default=False)
248
+
249
+ objects = MyManager()
250
+
251
+ # This gives you both queryable properties AND hooks
252
+ # No inheritance conflicts, no MRO issues!
253
+ ```
254
+
255
+ ### Alternative: Explicit Hook Application
256
+
257
+ For more control, you can apply hooks explicitly:
258
+
259
+ ```python
260
+ class MyManager(QueryablePropertiesManager):
261
+ def get_queryset(self):
262
+ return super().get_queryset()
263
+
264
+ def with_hooks(self):
265
+ """Apply hooks to this queryset"""
266
+ return HookQuerySet.with_hooks(self.get_queryset())
267
+
268
+ # Usage:
269
+ Article.objects.with_hooks().filter(published=True).update(title="Updated")
270
+ ```
271
+
272
+ ### Legacy: Manager Inheritance (Not Recommended)
273
+
274
+ The old inheritance approach still works but is not recommended due to potential MRO conflicts:
229
275
 
230
276
  ```python
231
277
  from django_bulk_hooks.manager import BulkHookManager
232
278
  from queryable_properties.managers import QueryablePropertiesManager
233
279
 
234
280
  class MyManager(BulkHookManager, QueryablePropertiesManager):
235
- pass
281
+ pass # ⚠️ Can cause inheritance conflicts
236
282
  ```
237
283
 
238
- This approach uses the industry-standard injection pattern, similar to how `QueryablePropertiesManager` works, ensuring both functionalities work seamlessly together without any framework-specific knowledge.
284
+ **Why the new approach is better:**
285
+ - ✅ No inheritance conflicts
286
+ - ✅ No MRO (Method Resolution Order) issues
287
+ - ✅ Works with any manager combination
288
+ - ✅ Cleaner and more maintainable
289
+ - ✅ Follows Django's queryset enhancement patterns
239
290
 
240
291
  Framework needs to:
241
292
  Register these methods
@@ -21,29 +21,19 @@ class BulkHookManager(models.Manager):
21
21
  """
22
22
  Manager that provides hook-aware bulk operations.
23
23
 
24
- This is a simple facade that returns HookQuerySet,
25
- delegating all bulk operations to it.
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
- This ensures all bulk operations go through the coordinator.
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,
@@ -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
  [tool.poetry]
2
2
  name = "django-bulk-hooks"
3
- version = "0.2.69"
3
+ version = "0.2.70"
4
4
  description = "Hook-style hooks for Django bulk operations like bulk_create and bulk_update."
5
5
  authors = ["Konrad Beck <konrad.beck@merchantcapital.co.za>"]
6
6
  readme = "README.md"