django-bulk-hooks 0.1.221__py3-none-any.whl → 0.1.223__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,4 +1,5 @@
1
1
  import logging
2
+
2
3
  from django.db import models, transaction
3
4
  from django.db.models import AutoField, Case, Field, Value, When
4
5
 
@@ -70,58 +71,77 @@ class HookQuerySetMixin:
70
71
  for value in kwargs.values()
71
72
  )
72
73
 
73
- # Apply field updates to instances
74
+ # Check if we're in a bulk operation context to prevent double hook execution
75
+ from django_bulk_hooks.context import get_bypass_hooks
76
+
77
+ current_bypass_hooks = get_bypass_hooks()
78
+
79
+ # Apply field updates to instances for all cases (needed for hook inspection)
74
80
  for obj in instances:
75
81
  for field, value in kwargs.items():
82
+ # For subquery fields, set the original Subquery object temporarily
83
+ # We'll resolve it after database update if needed
76
84
  setattr(obj, field, value)
77
85
 
78
- # Check if we're in a bulk operation context to prevent double hook execution
79
- from django_bulk_hooks.context import get_bypass_hooks
80
- current_bypass_hooks = get_bypass_hooks()
81
-
82
86
  # If we're in a bulk operation context, skip hooks to prevent double execution
83
87
  if current_bypass_hooks:
84
- logger.debug("update: skipping hooks (bulk context)")
85
88
  ctx = HookContext(model_cls, bypass_hooks=True)
89
+ # For bulk operations without hooks, execute update
90
+ update_count = super().update(**kwargs)
86
91
  else:
87
- logger.debug("update: running hooks (standalone)")
88
92
  ctx = HookContext(model_cls, bypass_hooks=False)
89
- # Run validation hooks first
90
- engine.run(model_cls, VALIDATE_UPDATE, instances, originals, ctx=ctx)
91
- # Then run BEFORE_UPDATE hooks
92
- engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
93
-
94
- # Use Django's built-in update logic directly
95
- # Call the base QuerySet implementation to avoid recursion
96
- update_count = super().update(**kwargs)
97
-
98
- # If we used Subquery objects, refresh the instances to get computed values
99
- if has_subquery and instances:
100
- # Simple refresh of model fields without fetching related objects
101
- # Subquery updates only affect the model's own fields, not relationships
102
- refreshed_instances = {
103
- obj.pk: obj for obj in model_cls._base_manager.filter(pk__in=pks)
104
- }
105
93
 
106
- # Bulk update all instances in memory
107
- for instance in instances:
108
- if instance.pk in refreshed_instances:
109
- refreshed_instance = refreshed_instances[instance.pk]
110
- # Update all fields except primary key
111
- for field in model_cls._meta.fields:
112
- if field.name != "id":
113
- setattr(
114
- instance,
115
- field.name,
116
- getattr(refreshed_instance, field.name),
117
- )
94
+ # For subquery cases, we need special handling
95
+ if has_subquery:
96
+ # Run validation hooks first with Subquery objects (if validation doesn't access them)
97
+ try:
98
+ engine.run(
99
+ model_cls, VALIDATE_UPDATE, instances, originals, ctx=ctx
100
+ )
101
+ except (TypeError, ValueError) as e:
102
+ # If validation fails due to Subquery comparison, skip validation for subquery updates
103
+ # This is a limitation - validation hooks cannot easily work with unresolved subqueries
104
+ logger.warning(
105
+ f"Skipping validation hooks for subquery update due to: {e}"
106
+ )
107
+
108
+ # Execute the database update first to compute subquery values
109
+ update_count = super().update(**kwargs)
110
+
111
+ # Refresh instances to get computed subquery values BEFORE running BEFORE hooks
112
+ refreshed_instances = {
113
+ obj.pk: obj for obj in model_cls._base_manager.filter(pk__in=pks)
114
+ }
115
+
116
+ # Update instances in memory with computed values
117
+ for instance in instances:
118
+ if instance.pk in refreshed_instances:
119
+ refreshed_instance = refreshed_instances[instance.pk]
120
+ # Update all fields except primary key with the computed values
121
+ for field in model_cls._meta.fields:
122
+ if field.name != "id":
123
+ setattr(
124
+ instance,
125
+ field.name,
126
+ getattr(refreshed_instance, field.name),
127
+ )
128
+
129
+ # Now run BEFORE_UPDATE hooks with resolved values
130
+ # Note: This is a trade-off - BEFORE hooks run after DB update for subquery cases
131
+ engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
132
+ else:
133
+ # Normal case without subqueries - run hooks in proper order
134
+ # Run validation hooks first
135
+ engine.run(model_cls, VALIDATE_UPDATE, instances, originals, ctx=ctx)
136
+ # Then run BEFORE_UPDATE hooks
137
+ engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
138
+
139
+ # Execute update
140
+ update_count = super().update(**kwargs)
118
141
 
119
142
  # Run AFTER_UPDATE hooks only for standalone updates
120
143
  if not current_bypass_hooks:
121
- logger.debug("update: running AFTER_UPDATE")
122
144
  engine.run(model_cls, AFTER_UPDATE, instances, originals, ctx=ctx)
123
- else:
124
- logger.debug("update: skipping AFTER_UPDATE (bulk context)")
125
145
 
126
146
  return update_count
127
147
 
@@ -180,12 +200,12 @@ class HookQuerySetMixin:
180
200
 
181
201
  # Fire hooks before DB ops
182
202
  if not bypass_hooks:
183
- ctx = HookContext(model_cls, bypass_hooks=False) # Pass bypass_hooks
203
+ ctx = HookContext(model_cls, bypass_hooks=False) # Pass bypass_hooks
184
204
  if not bypass_validation:
185
205
  engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
186
206
  engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
187
207
  else:
188
- ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
208
+ ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
189
209
  logger.debug("bulk_create bypassed hooks")
190
210
 
191
211
  # For MTI models, we need to handle them specially
@@ -241,7 +261,9 @@ class HookQuerySetMixin:
241
261
  f"bulk_update expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
242
262
  )
243
263
 
244
- logger.debug(f"bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}")
264
+ logger.debug(
265
+ f"bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}"
266
+ )
245
267
 
246
268
  # Check for MTI
247
269
  is_mti = False
@@ -257,7 +279,9 @@ class HookQuerySetMixin:
257
279
  else:
258
280
  logger.debug("bulk_update: hooks bypassed")
259
281
  ctx = HookContext(model_cls, bypass_hooks=True)
260
- originals = [None] * len(objs) # Ensure originals is defined for after_update call
282
+ originals = [None] * len(
283
+ objs
284
+ ) # Ensure originals is defined for after_update call
261
285
 
262
286
  # Handle auto_now fields like Django's update_or_create does
263
287
  fields_set = set(fields)
@@ -1,7 +1,8 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.1
2
2
  Name: django-bulk-hooks
3
- Version: 0.1.221
3
+ Version: 0.1.223
4
4
  Summary: Hook-style hooks for Django bulk operations like bulk_create and bulk_update.
5
+ Home-page: https://github.com/AugendLimited/django-bulk-hooks
5
6
  License: MIT
6
7
  Keywords: django,bulk,hooks
7
8
  Author: Konrad Beck
@@ -13,7 +14,6 @@ Classifier: Programming Language :: Python :: 3.11
13
14
  Classifier: Programming Language :: Python :: 3.12
14
15
  Classifier: Programming Language :: Python :: 3.13
15
16
  Requires-Dist: Django (>=4.0)
16
- Project-URL: Homepage, https://github.com/AugendLimited/django-bulk-hooks
17
17
  Project-URL: Repository, https://github.com/AugendLimited/django-bulk-hooks
18
18
  Description-Content-Type: text/markdown
19
19
 
@@ -9,9 +9,9 @@ django_bulk_hooks/handler.py,sha256=xZt8iNdYF-ACz-MnKMY0co6scWINU5V5wC1lyDn844k,
9
9
  django_bulk_hooks/manager.py,sha256=nfWiwU5-yAoxdnQsUMohxtyCpkV0MBv6X3wmipr9eQY,3697
10
10
  django_bulk_hooks/models.py,sha256=exnXYVKEVbYAXhChCP8VdWTnKCnm9DiTcokEIBee1I0,4350
11
11
  django_bulk_hooks/priority.py,sha256=HG_2D35nga68lBCZmSXTcplXrjFoRgZFRDOy4ROKonY,376
12
- django_bulk_hooks/queryset.py,sha256=YSDCMAf24YLeLJrRKCDYwbZInP2mK_Tcuw3EHLDkv_w,32605
12
+ django_bulk_hooks/queryset.py,sha256=ZEYrO8AJunmoS9n7xSkjDTG3kOAntAOTnpfsd-GhC0c,33798
13
13
  django_bulk_hooks/registry.py,sha256=8UuhniiH5ChSeOKV1UUbqTEiIu25bZXvcHmkaRbxmME,1131
14
- django_bulk_hooks-0.1.221.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
- django_bulk_hooks-0.1.221.dist-info/METADATA,sha256=QVFXKdg3v7WcK1IsfDD3h3Nijg3tXdHtlqS6JoGemFo,9061
16
- django_bulk_hooks-0.1.221.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
17
- django_bulk_hooks-0.1.221.dist-info/RECORD,,
14
+ django_bulk_hooks-0.1.223.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
+ django_bulk_hooks-0.1.223.dist-info/METADATA,sha256=IW85vPi4EzF2Z3NrWrlZri4Ps65WGsITEvhbfcbZdWw,9049
16
+ django_bulk_hooks-0.1.223.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
17
+ django_bulk_hooks-0.1.223.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.3
2
+ Generator: poetry-core 1.9.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any