django-bulk-hooks 0.1.116__tar.gz → 0.1.118__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.1.116 → django_bulk_hooks-0.1.118}/PKG-INFO +3 -3
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/django_bulk_hooks/manager.py +90 -42
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/pyproject.toml +1 -1
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/LICENSE +0 -0
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/README.md +0 -0
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/django_bulk_hooks/__init__.py +0 -0
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/django_bulk_hooks/conditions.py +0 -0
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/django_bulk_hooks/constants.py +0 -0
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/django_bulk_hooks/context.py +0 -0
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/django_bulk_hooks/decorators.py +0 -0
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/django_bulk_hooks/engine.py +0 -0
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/django_bulk_hooks/enums.py +0 -0
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/django_bulk_hooks/handler.py +0 -0
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/django_bulk_hooks/models.py +0 -0
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/django_bulk_hooks/priority.py +0 -0
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/django_bulk_hooks/queryset.py +0 -0
- {django_bulk_hooks-0.1.116 → django_bulk_hooks-0.1.118}/django_bulk_hooks/registry.py +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: django-bulk-hooks
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.118
|
|
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
|
|
6
5
|
License: MIT
|
|
7
6
|
Keywords: django,bulk,hooks
|
|
8
7
|
Author: Konrad Beck
|
|
@@ -14,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
15
|
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
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
from django.db import models, transaction
|
|
1
|
+
from django.db import models, transaction, connections
|
|
4
2
|
from django.db.models import AutoField
|
|
5
3
|
|
|
6
4
|
from django_bulk_hooks import engine
|
|
@@ -22,12 +20,9 @@ from django_bulk_hooks.queryset import HookQuerySet
|
|
|
22
20
|
class BulkHookManager(models.Manager):
|
|
23
21
|
CHUNK_SIZE = 200
|
|
24
22
|
|
|
25
|
-
|
|
26
23
|
def get_queryset(self):
|
|
27
24
|
return HookQuerySet(self.model, using=self._db)
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
26
|
@transaction.atomic
|
|
32
27
|
def bulk_update(
|
|
33
28
|
self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs
|
|
@@ -77,8 +72,6 @@ class BulkHookManager(models.Manager):
|
|
|
77
72
|
|
|
78
73
|
return objs
|
|
79
74
|
|
|
80
|
-
|
|
81
|
-
|
|
82
75
|
@transaction.atomic
|
|
83
76
|
def bulk_create(
|
|
84
77
|
self,
|
|
@@ -102,9 +95,10 @@ class BulkHookManager(models.Manager):
|
|
|
102
95
|
raise ValueError("Batch size must be a positive integer.")
|
|
103
96
|
|
|
104
97
|
# Check that the parents share the same concrete model with our model to detect inheritance pattern
|
|
98
|
+
# (Do NOT raise for MTI, just skip the exception)
|
|
105
99
|
for parent in model_cls._meta.all_parents:
|
|
106
100
|
if parent._meta.concrete_model is not model_cls._meta.concrete_model:
|
|
107
|
-
#
|
|
101
|
+
# Do not raise, just continue
|
|
108
102
|
break
|
|
109
103
|
|
|
110
104
|
if not objs:
|
|
@@ -125,39 +119,68 @@ class BulkHookManager(models.Manager):
|
|
|
125
119
|
engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
|
|
126
120
|
engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
|
|
127
121
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
122
|
+
opts = model_cls._meta
|
|
123
|
+
if unique_fields:
|
|
124
|
+
unique_fields = [
|
|
125
|
+
model_cls._meta.get_field(opts.pk.name if name == "pk" else name)
|
|
126
|
+
for name in unique_fields
|
|
127
|
+
]
|
|
128
|
+
if update_fields:
|
|
129
|
+
update_fields = [model_cls._meta.get_field(name) for name in update_fields]
|
|
130
|
+
on_conflict = self._check_bulk_create_options(
|
|
131
|
+
ignore_conflicts,
|
|
132
|
+
update_conflicts,
|
|
133
|
+
update_fields,
|
|
134
|
+
unique_fields,
|
|
135
|
+
)
|
|
136
|
+
self._for_write = True
|
|
137
|
+
fields = [f for f in opts.concrete_fields if not f.generated]
|
|
138
|
+
objs = list(objs)
|
|
139
|
+
objs_with_pk, objs_without_pk = self._prepare_for_bulk_create(objs)
|
|
140
|
+
with transaction.atomic(using=self.db, savepoint=False):
|
|
141
|
+
self._handle_order_with_respect_to(objs)
|
|
142
|
+
if objs_with_pk:
|
|
143
|
+
returned_columns = self._batched_insert(
|
|
144
|
+
objs_with_pk,
|
|
145
|
+
fields,
|
|
146
|
+
batch_size,
|
|
147
|
+
on_conflict=on_conflict,
|
|
148
|
+
update_fields=update_fields,
|
|
149
|
+
unique_fields=unique_fields,
|
|
147
150
|
)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
for obj_with_pk, results in zip(objs_with_pk, returned_columns):
|
|
152
|
+
for result, field in zip(results, opts.db_returning_fields):
|
|
153
|
+
if field != opts.pk:
|
|
154
|
+
setattr(obj_with_pk, field.attname, result)
|
|
155
|
+
for obj_with_pk in objs_with_pk:
|
|
156
|
+
obj_with_pk._state.adding = False
|
|
157
|
+
obj_with_pk._state.db = self.db
|
|
158
|
+
if objs_without_pk:
|
|
159
|
+
fields_wo_pk = [f for f in fields if not isinstance(f, AutoField)]
|
|
160
|
+
returned_columns = self._batched_insert(
|
|
161
|
+
objs_without_pk,
|
|
162
|
+
fields_wo_pk,
|
|
163
|
+
batch_size,
|
|
164
|
+
on_conflict=on_conflict,
|
|
165
|
+
update_fields=update_fields,
|
|
166
|
+
unique_fields=unique_fields,
|
|
167
|
+
)
|
|
168
|
+
connection = connections[self.db]
|
|
169
|
+
if (
|
|
170
|
+
connection.features.can_return_rows_from_bulk_insert
|
|
171
|
+
and on_conflict is None
|
|
172
|
+
):
|
|
173
|
+
assert len(returned_columns) == len(objs_without_pk)
|
|
174
|
+
for obj_without_pk, results in zip(objs_without_pk, returned_columns):
|
|
175
|
+
for result, field in zip(results, opts.db_returning_fields):
|
|
176
|
+
setattr(obj_without_pk, field.attname, result)
|
|
177
|
+
obj_without_pk._state.adding = False
|
|
178
|
+
obj_without_pk._state.db = self.db
|
|
151
179
|
|
|
152
180
|
if not bypass_hooks:
|
|
153
|
-
engine.run(model_cls, AFTER_CREATE,
|
|
154
|
-
|
|
155
|
-
return result
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
181
|
+
engine.run(model_cls, AFTER_CREATE, objs, ctx=ctx)
|
|
160
182
|
|
|
183
|
+
return objs
|
|
161
184
|
|
|
162
185
|
# --- Private helper methods (moved to bottom for clarity) ---
|
|
163
186
|
|
|
@@ -261,6 +284,8 @@ class BulkHookManager(models.Manager):
|
|
|
261
284
|
obj, child_model, parent_objects_map.get(id(obj), {})
|
|
262
285
|
)
|
|
263
286
|
child_objects.append(child_obj)
|
|
287
|
+
# Handle order_with_respect_to like Django's bulk_create
|
|
288
|
+
self._handle_order_with_respect_to(child_objects)
|
|
264
289
|
# If the child model is still MTI, call our own logic recursively
|
|
265
290
|
if len([p for p in child_model._meta.parents.keys() if not p._meta.proxy]) > 0:
|
|
266
291
|
# Build inheritance chain for the child model
|
|
@@ -329,12 +354,16 @@ class BulkHookManager(models.Manager):
|
|
|
329
354
|
Set auto_now_add and auto_now fields on objects before bulk_create.
|
|
330
355
|
"""
|
|
331
356
|
from django.utils import timezone
|
|
357
|
+
|
|
332
358
|
now = timezone.now()
|
|
333
359
|
for obj in objs:
|
|
334
360
|
for field in model._meta.local_fields:
|
|
335
|
-
if
|
|
361
|
+
if (
|
|
362
|
+
getattr(field, "auto_now_add", False)
|
|
363
|
+
and getattr(obj, field.name, None) is None
|
|
364
|
+
):
|
|
336
365
|
setattr(obj, field.name, now)
|
|
337
|
-
if getattr(field,
|
|
366
|
+
if getattr(field, "auto_now", False):
|
|
338
367
|
setattr(obj, field.name, now)
|
|
339
368
|
|
|
340
369
|
@transaction.atomic
|
|
@@ -370,8 +399,6 @@ class BulkHookManager(models.Manager):
|
|
|
370
399
|
if not bypass_hooks:
|
|
371
400
|
engine.run(model_cls, AFTER_DELETE, objs, ctx=ctx)
|
|
372
401
|
|
|
373
|
-
return objs
|
|
374
|
-
|
|
375
402
|
@transaction.atomic
|
|
376
403
|
def update(self, **kwargs):
|
|
377
404
|
objs = list(self.all())
|
|
@@ -401,3 +428,24 @@ class BulkHookManager(models.Manager):
|
|
|
401
428
|
else:
|
|
402
429
|
self.bulk_create([obj])
|
|
403
430
|
return obj
|
|
431
|
+
|
|
432
|
+
def _handle_order_with_respect_to(self, objs):
|
|
433
|
+
"""
|
|
434
|
+
Set _order fields for models with order_with_respect_to.
|
|
435
|
+
"""
|
|
436
|
+
for obj in objs:
|
|
437
|
+
order_with_respect_to = obj.__class__._meta.order_with_respect_to
|
|
438
|
+
if order_with_respect_to:
|
|
439
|
+
key = getattr(obj, order_with_respect_to.attname)
|
|
440
|
+
obj._order = key
|
|
441
|
+
# Group by the value of order_with_respect_to
|
|
442
|
+
groups = defaultdict(list)
|
|
443
|
+
for obj in objs:
|
|
444
|
+
order_with_respect_to = obj.__class__._meta.order_with_respect_to
|
|
445
|
+
if order_with_respect_to:
|
|
446
|
+
key = getattr(obj, order_with_respect_to.attname)
|
|
447
|
+
groups[key].append(obj)
|
|
448
|
+
# Enumerate within each group
|
|
449
|
+
for group_objs in groups.values():
|
|
450
|
+
for i, obj in enumerate(group_objs):
|
|
451
|
+
obj._order = i
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "django-bulk-hooks"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.118"
|
|
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"
|
|
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
|