django-bulk-hooks 0.2.58__tar.gz → 0.2.60__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.2.58 → django_bulk_hooks-0.2.60}/PKG-INFO +1 -1
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/bulk_executor.py +26 -1
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/coordinator.py +42 -17
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/mti_handler.py +4 -17
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/pyproject.toml +1 -1
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/LICENSE +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/README.md +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/__init__.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/changeset.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/conditions.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/constants.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/context.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/decorators.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/dispatcher.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/enums.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/factory.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/handler.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/helpers.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/manager.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/models.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/__init__.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/analyzer.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/field_utils.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/mti_plans.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/record_classifier.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/queryset.py +0 -0
- {django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/registry.py +0 -0
{django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/bulk_executor.py
RENAMED
|
@@ -182,10 +182,35 @@ class BulkExecutor:
|
|
|
182
182
|
if not objs:
|
|
183
183
|
return 0
|
|
184
184
|
|
|
185
|
+
# Ensure auto_now fields are included and pre-saved for all models
|
|
186
|
+
# This handles both MTI and non-MTI models uniformly (SOC & DRY)
|
|
187
|
+
fields = list(fields) # Make a copy so we can modify it
|
|
188
|
+
|
|
189
|
+
# Get models to check - for MTI, check entire inheritance chain
|
|
190
|
+
if self.mti_handler.is_mti_model():
|
|
191
|
+
models_to_check = self.mti_handler.get_inheritance_chain()
|
|
192
|
+
else:
|
|
193
|
+
models_to_check = [self.model_cls]
|
|
194
|
+
|
|
195
|
+
# Collect all auto_now fields and pre-save them
|
|
196
|
+
auto_now_fields = set()
|
|
197
|
+
for model in models_to_check:
|
|
198
|
+
for field in model._meta.local_fields:
|
|
199
|
+
if getattr(field, "auto_now", False) and not getattr(field, "auto_now_add", False):
|
|
200
|
+
auto_now_fields.add(field.name)
|
|
201
|
+
# Pre-save the field to set the value on instances
|
|
202
|
+
for obj in objs:
|
|
203
|
+
field.pre_save(obj, add=False)
|
|
204
|
+
|
|
205
|
+
# Add auto_now fields to the update list if not already present
|
|
206
|
+
for auto_now_field in auto_now_fields:
|
|
207
|
+
if auto_now_field not in fields:
|
|
208
|
+
fields.append(auto_now_field)
|
|
209
|
+
|
|
185
210
|
# Check if this is an MTI model and route accordingly
|
|
186
211
|
if self.mti_handler.is_mti_model():
|
|
187
212
|
logger.info(f"Detected MTI model {self.model_cls.__name__}, using MTI bulk update")
|
|
188
|
-
# Build execution plan
|
|
213
|
+
# Build execution plan (fields already have auto_now included)
|
|
189
214
|
plan = self.mti_handler.build_update_plan(objs, fields, batch_size=batch_size)
|
|
190
215
|
# Execute the plan
|
|
191
216
|
return self._execute_mti_update_plan(plan)
|
{django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/coordinator.py
RENAMED
|
@@ -740,6 +740,8 @@ class BulkOperationCoordinator:
|
|
|
740
740
|
if not result_objects:
|
|
741
741
|
return
|
|
742
742
|
|
|
743
|
+
# First pass: collect objects with metadata and objects needing timestamp check
|
|
744
|
+
objects_needing_timestamp_check = []
|
|
743
745
|
for obj in result_objects:
|
|
744
746
|
# Check if metadata was set
|
|
745
747
|
if hasattr(obj, "_bulk_hooks_was_created"):
|
|
@@ -749,29 +751,52 @@ class BulkOperationCoordinator:
|
|
|
749
751
|
else:
|
|
750
752
|
updated_objects.append(obj)
|
|
751
753
|
else:
|
|
752
|
-
#
|
|
754
|
+
# Need to check timestamps - collect for bulk query
|
|
755
|
+
objects_needing_timestamp_check.append(obj)
|
|
756
|
+
|
|
757
|
+
# Bulk fetch timestamps for objects without metadata (avoids N+1 queries)
|
|
758
|
+
if objects_needing_timestamp_check:
|
|
759
|
+
# Group by model class to handle MTI scenarios
|
|
760
|
+
objects_by_model = {}
|
|
761
|
+
for obj in objects_needing_timestamp_check:
|
|
753
762
|
model_cls = obj.__class__
|
|
763
|
+
if model_cls not in objects_by_model:
|
|
764
|
+
objects_by_model[model_cls] = []
|
|
765
|
+
objects_by_model[model_cls].append(obj)
|
|
766
|
+
|
|
767
|
+
# Fetch timestamps in bulk for each model class
|
|
768
|
+
for model_cls, objs in objects_by_model.items():
|
|
754
769
|
if hasattr(model_cls, "created_at") and hasattr(model_cls, "updated_at"):
|
|
755
|
-
#
|
|
756
|
-
|
|
757
|
-
if
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
770
|
+
# Bulk fetch timestamps for all objects of this model
|
|
771
|
+
pks = [obj.pk for obj in objs if obj.pk is not None]
|
|
772
|
+
if pks:
|
|
773
|
+
timestamp_map = {
|
|
774
|
+
record["pk"]: (record["created_at"], record["updated_at"])
|
|
775
|
+
for record in model_cls.objects.filter(pk__in=pks).values("pk", "created_at", "updated_at")
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
# Classify each object based on timestamps
|
|
779
|
+
for obj in objs:
|
|
780
|
+
if obj.pk in timestamp_map:
|
|
781
|
+
created_at, updated_at = timestamp_map[obj.pk]
|
|
782
|
+
if created_at and updated_at:
|
|
783
|
+
time_diff = abs((updated_at - created_at).total_seconds())
|
|
784
|
+
if time_diff <= 1.0: # Within 1 second = just created
|
|
785
|
+
created_objects.append(obj)
|
|
786
|
+
else:
|
|
787
|
+
updated_objects.append(obj)
|
|
788
|
+
else:
|
|
789
|
+
# No timestamps, default to created
|
|
790
|
+
created_objects.append(obj)
|
|
764
791
|
else:
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
# No timestamps, default to created
|
|
768
|
-
created_objects.append(obj)
|
|
792
|
+
# Object not found, treat as created
|
|
793
|
+
created_objects.append(obj)
|
|
769
794
|
else:
|
|
770
|
-
#
|
|
771
|
-
created_objects.
|
|
795
|
+
# No PKs, default all to created
|
|
796
|
+
created_objects.extend(objs)
|
|
772
797
|
else:
|
|
773
798
|
# No timestamp fields, default to created
|
|
774
|
-
created_objects.
|
|
799
|
+
created_objects.extend(objs)
|
|
775
800
|
|
|
776
801
|
logger.info(f"Upsert after hooks: {len(created_objects)} created, {len(updated_objects)} updated")
|
|
777
802
|
|
{django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/mti_handler.py
RENAMED
|
@@ -537,7 +537,7 @@ class MTIHandler:
|
|
|
537
537
|
|
|
538
538
|
Args:
|
|
539
539
|
objs: List of model instances to update
|
|
540
|
-
fields: List of field names to update
|
|
540
|
+
fields: List of field names to update (auto_now fields already included by executor)
|
|
541
541
|
batch_size: Number of objects per batch
|
|
542
542
|
|
|
543
543
|
Returns:
|
|
@@ -555,28 +555,15 @@ class MTIHandler:
|
|
|
555
555
|
|
|
556
556
|
batch_size = batch_size or len(objs)
|
|
557
557
|
|
|
558
|
-
#
|
|
559
|
-
|
|
560
|
-
for model in inheritance_chain:
|
|
561
|
-
for field in model._meta.local_fields:
|
|
562
|
-
if getattr(field, "auto_now", False):
|
|
563
|
-
field.pre_save(obj, add=False)
|
|
564
|
-
|
|
565
|
-
# Add auto_now fields to update list
|
|
566
|
-
auto_now_fields = set()
|
|
567
|
-
for model in inheritance_chain:
|
|
568
|
-
for field in model._meta.local_fields:
|
|
569
|
-
if getattr(field, "auto_now", False):
|
|
570
|
-
auto_now_fields.add(field.name)
|
|
571
|
-
|
|
572
|
-
all_fields = list(fields) + list(auto_now_fields)
|
|
558
|
+
# Note: auto_now fields are already handled by executor.bulk_update()
|
|
559
|
+
# which calls pre_save() and includes them in the fields list
|
|
573
560
|
|
|
574
561
|
# Group fields by model
|
|
575
562
|
field_groups = []
|
|
576
563
|
for model_idx, model in enumerate(inheritance_chain):
|
|
577
564
|
model_fields = []
|
|
578
565
|
|
|
579
|
-
for field_name in
|
|
566
|
+
for field_name in fields:
|
|
580
567
|
try:
|
|
581
568
|
field = self.model_cls._meta.get_field(field_name)
|
|
582
569
|
if field in model._meta.local_fields:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/__init__.py
RENAMED
|
File without changes
|
{django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/analyzer.py
RENAMED
|
File without changes
|
{django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/field_utils.py
RENAMED
|
File without changes
|
{django_bulk_hooks-0.2.58 → django_bulk_hooks-0.2.60}/django_bulk_hooks/operations/mti_plans.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|