django-bulk-hooks 0.1.232__py3-none-any.whl → 0.1.233__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,6 +1,7 @@
1
1
  import logging
2
+
2
3
  from django.db import models, transaction
3
- from django.db.models import AutoField, Case, Field, Value, When
4
+ from django.db.models import AutoField, Case, Value, When
4
5
 
5
6
  from django_bulk_hooks import engine
6
7
 
@@ -64,21 +65,19 @@ class HookQuerySetMixin:
64
65
  }
65
66
  originals = [original_map.get(obj.pk) for obj in instances]
66
67
 
67
- # Check if any of the update values are Subquery objects
68
- has_subquery = any(
69
- hasattr(value, "query") and hasattr(value, "resolve_expression")
70
- for value in kwargs.values()
71
- )
68
+ # Resolve subqueries to actual values before applying to instances
69
+ resolved_kwargs = self._resolve_subquery_values(kwargs)
72
70
 
73
- # Apply field updates to instances
71
+ # Apply resolved field updates to instances
74
72
  for obj in instances:
75
- for field, value in kwargs.items():
73
+ for field, value in resolved_kwargs.items():
76
74
  setattr(obj, field, value)
77
75
 
78
76
  # Check if we're in a bulk operation context to prevent double hook execution
79
77
  from django_bulk_hooks.context import get_bypass_hooks
78
+
80
79
  current_bypass_hooks = get_bypass_hooks()
81
-
80
+
82
81
  # If we're in a bulk operation context, skip hooks to prevent double execution
83
82
  if current_bypass_hooks:
84
83
  logger.debug("update: skipping hooks (bulk context)")
@@ -93,28 +92,10 @@ class HookQuerySetMixin:
93
92
 
94
93
  # Use Django's built-in update logic directly
95
94
  # Call the base QuerySet implementation to avoid recursion
95
+ # Use original kwargs so Django can handle subqueries at database level
96
96
  update_count = super().update(**kwargs)
97
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
-
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
- )
98
+ # Since we resolved subqueries upfront, we don't need the post-refresh logic
118
99
 
119
100
  # Run AFTER_UPDATE hooks only for standalone updates
120
101
  if not current_bypass_hooks:
@@ -125,6 +106,38 @@ class HookQuerySetMixin:
125
106
 
126
107
  return update_count
127
108
 
109
+ def _resolve_subquery_values(self, kwargs):
110
+ """
111
+ Resolve Subquery objects to their actual values by evaluating them
112
+ against the database. This ensures hooks receive resolved values
113
+ instead of raw Subquery objects.
114
+ """
115
+ resolved_kwargs = {}
116
+ for field, value in kwargs.items():
117
+ if hasattr(value, "query") and hasattr(value, "resolve_expression"):
118
+ # This is a subquery - we need to resolve it
119
+ try:
120
+ # Create a temporary queryset to evaluate the subquery
121
+ temp_qs = self.model._default_manager.all()
122
+ temp_qs = temp_qs.annotate(_temp_field=value)
123
+ temp_qs = temp_qs.values("_temp_field")
124
+
125
+ # Get the resolved value (assuming single result for update context)
126
+ resolved_value = temp_qs.first()["_temp_field"]
127
+ resolved_kwargs[field] = resolved_value
128
+ except Exception:
129
+ # If resolution fails, use the original subquery
130
+ # Django's update() will handle it at the database level
131
+ logger.warning(
132
+ f"Failed to resolve subquery for field {field}, using original"
133
+ )
134
+ resolved_kwargs[field] = value
135
+ else:
136
+ # Not a subquery, use as-is
137
+ resolved_kwargs[field] = value
138
+
139
+ return resolved_kwargs
140
+
128
141
  @transaction.atomic
129
142
  def bulk_create(
130
143
  self,
@@ -180,12 +193,12 @@ class HookQuerySetMixin:
180
193
 
181
194
  # Fire hooks before DB ops
182
195
  if not bypass_hooks:
183
- ctx = HookContext(model_cls, bypass_hooks=False) # Pass bypass_hooks
196
+ ctx = HookContext(model_cls, bypass_hooks=False) # Pass bypass_hooks
184
197
  if not bypass_validation:
185
198
  engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
186
199
  engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
187
200
  else:
188
- ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
201
+ ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
189
202
  logger.debug("bulk_create bypassed hooks")
190
203
 
191
204
  # For MTI models, we need to handle them specially
@@ -241,7 +254,9 @@ class HookQuerySetMixin:
241
254
  f"bulk_update expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
242
255
  )
243
256
 
244
- logger.debug(f"bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}")
257
+ logger.debug(
258
+ f"bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}"
259
+ )
245
260
 
246
261
  # Check for MTI
247
262
  is_mti = False
@@ -257,7 +272,9 @@ class HookQuerySetMixin:
257
272
  else:
258
273
  logger.debug("bulk_update: hooks bypassed")
259
274
  ctx = HookContext(model_cls, bypass_hooks=True)
260
- originals = [None] * len(objs) # Ensure originals is defined for after_update call
275
+ originals = [None] * len(
276
+ objs
277
+ ) # Ensure originals is defined for after_update call
261
278
 
262
279
  # Handle auto_now fields like Django's update_or_create does
263
280
  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.232
3
+ Version: 0.1.233
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=7ug24q6M457TQwqpwqJHnxct1Hhhp9xnl8zR0cgIzWY,33237
13
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,,
14
+ django_bulk_hooks-0.1.233.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
+ django_bulk_hooks-0.1.233.dist-info/METADATA,sha256=yLbe8bU5rPxGaovxTa9_nKPujSRW-XoXjZ1Ok2GSidg,9049
16
+ django_bulk_hooks-0.1.233.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
17
+ django_bulk_hooks-0.1.233.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