django-bulk-hooks 0.1.122__tar.gz → 0.1.123__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 (18) hide show
  1. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/PKG-INFO +1 -1
  2. django_bulk_hooks-0.1.123/django_bulk_hooks/manager.py +83 -0
  3. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/pyproject.toml +1 -1
  4. django_bulk_hooks-0.1.122/django_bulk_hooks/manager.py +0 -201
  5. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/LICENSE +0 -0
  6. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/README.md +0 -0
  7. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/django_bulk_hooks/__init__.py +0 -0
  8. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/django_bulk_hooks/conditions.py +0 -0
  9. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/django_bulk_hooks/constants.py +0 -0
  10. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/django_bulk_hooks/context.py +0 -0
  11. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/django_bulk_hooks/decorators.py +0 -0
  12. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/django_bulk_hooks/engine.py +0 -0
  13. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/django_bulk_hooks/enums.py +0 -0
  14. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/django_bulk_hooks/handler.py +0 -0
  15. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/django_bulk_hooks/models.py +0 -0
  16. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/django_bulk_hooks/priority.py +0 -0
  17. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/django_bulk_hooks/queryset.py +0 -0
  18. {django_bulk_hooks-0.1.122 → django_bulk_hooks-0.1.123}/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.1.122
3
+ Version: 0.1.123
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
@@ -0,0 +1,83 @@
1
+ from django.db import models
2
+
3
+ from django_bulk_hooks.queryset import HookQuerySet
4
+
5
+
6
+ class BulkHookManager(models.Manager):
7
+ def get_queryset(self):
8
+ return HookQuerySet(self.model, using=self._db)
9
+
10
+ def bulk_update(
11
+ self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs
12
+ ):
13
+ """
14
+ Delegate to QuerySet's bulk_update implementation.
15
+ This follows Django's pattern where Manager methods call QuerySet methods.
16
+ """
17
+ return self.get_queryset().bulk_update(
18
+ objs, fields, bypass_hooks=bypass_hooks, bypass_validation=bypass_validation, **kwargs
19
+ )
20
+
21
+ def bulk_create(
22
+ self,
23
+ objs,
24
+ batch_size=None,
25
+ ignore_conflicts=False,
26
+ update_conflicts=False,
27
+ update_fields=None,
28
+ unique_fields=None,
29
+ bypass_hooks=False,
30
+ bypass_validation=False,
31
+ ):
32
+ """
33
+ Delegate to QuerySet's bulk_create implementation.
34
+ This follows Django's pattern where Manager methods call QuerySet methods.
35
+ """
36
+ return self.get_queryset().bulk_create(
37
+ objs,
38
+ batch_size=batch_size,
39
+ ignore_conflicts=ignore_conflicts,
40
+ update_conflicts=update_conflicts,
41
+ update_fields=update_fields,
42
+ unique_fields=unique_fields,
43
+ bypass_hooks=bypass_hooks,
44
+ bypass_validation=bypass_validation,
45
+ )
46
+
47
+ def bulk_delete(
48
+ self, objs, batch_size=None, bypass_hooks=False, bypass_validation=False
49
+ ):
50
+ """
51
+ Delegate to QuerySet's bulk_delete implementation.
52
+ This follows Django's pattern where Manager methods call QuerySet methods.
53
+ """
54
+ return self.get_queryset().bulk_delete(
55
+ objs, batch_size=batch_size, bypass_hooks=bypass_hooks, bypass_validation=bypass_validation
56
+ )
57
+
58
+ def update(self, **kwargs):
59
+ """
60
+ Delegate to QuerySet's update implementation.
61
+ This follows Django's pattern where Manager methods call QuerySet methods.
62
+ """
63
+ return self.get_queryset().update(**kwargs)
64
+
65
+ def delete(self):
66
+ """
67
+ Delegate to QuerySet's delete implementation.
68
+ This follows Django's pattern where Manager methods call QuerySet methods.
69
+ """
70
+ return self.get_queryset().delete()
71
+
72
+ def save(self, obj):
73
+ """
74
+ Save a single object using the appropriate bulk operation.
75
+ """
76
+ if obj.pk:
77
+ self.bulk_update(
78
+ [obj],
79
+ fields=[field.name for field in obj._meta.fields if field.name != "id"],
80
+ )
81
+ else:
82
+ self.bulk_create([obj])
83
+ return obj
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-bulk-hooks"
3
- version = "0.1.122"
3
+ version = "0.1.123"
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"
@@ -1,201 +0,0 @@
1
- from django.db import models, transaction
2
-
3
- from django_bulk_hooks import engine
4
- from django_bulk_hooks.constants import (
5
- AFTER_CREATE,
6
- AFTER_DELETE,
7
- AFTER_UPDATE,
8
- BEFORE_CREATE,
9
- BEFORE_DELETE,
10
- BEFORE_UPDATE,
11
- VALIDATE_CREATE,
12
- VALIDATE_DELETE,
13
- VALIDATE_UPDATE,
14
- )
15
- from django_bulk_hooks.context import HookContext
16
- from django_bulk_hooks.queryset import HookQuerySet
17
-
18
-
19
- class BulkHookManager(models.Manager):
20
- CHUNK_SIZE = 200
21
-
22
- def get_queryset(self):
23
- return HookQuerySet(self.model, using=self._db)
24
-
25
- @transaction.atomic
26
- def bulk_update(
27
- self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs
28
- ):
29
- if not objs:
30
- return []
31
-
32
- model_cls = self.model
33
-
34
- if any(not isinstance(obj, model_cls) for obj in objs):
35
- raise TypeError(
36
- f"bulk_update expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
37
- )
38
-
39
- if not bypass_hooks:
40
- # Load originals for hook comparison and ensure they match the order of new instances
41
- original_map = {
42
- obj.pk: obj
43
- for obj in model_cls.objects.filter(pk__in=[obj.pk for obj in objs])
44
- }
45
- originals = [original_map.get(obj.pk) for obj in objs]
46
-
47
- ctx = HookContext(model_cls)
48
-
49
- # Run validation hooks first
50
- if not bypass_validation:
51
- engine.run(model_cls, VALIDATE_UPDATE, objs, originals, ctx=ctx)
52
-
53
- # Then run business logic hooks
54
- engine.run(model_cls, BEFORE_UPDATE, objs, originals, ctx=ctx)
55
-
56
- # Automatically detect fields that were modified during BEFORE_UPDATE hooks
57
- modified_fields = self._detect_modified_fields(objs, originals)
58
- if modified_fields:
59
- # Convert to set for efficient union operation
60
- fields_set = set(fields)
61
- fields_set.update(modified_fields)
62
- fields = list(fields_set)
63
-
64
- for i in range(0, len(objs), self.CHUNK_SIZE):
65
- chunk = objs[i : i + self.CHUNK_SIZE]
66
- # Call the base implementation to avoid re-triggering this method
67
- super(models.Manager, self).bulk_update(chunk, fields, **kwargs)
68
-
69
- if not bypass_hooks:
70
- engine.run(model_cls, AFTER_UPDATE, objs, originals, ctx=ctx)
71
-
72
- return objs
73
-
74
- @transaction.atomic
75
- def bulk_create(
76
- self,
77
- objs,
78
- batch_size=None,
79
- ignore_conflicts=False,
80
- update_conflicts=False,
81
- update_fields=None,
82
- unique_fields=None,
83
- bypass_hooks=False,
84
- bypass_validation=False,
85
- ):
86
- """
87
- Delegate to QuerySet's bulk_create implementation.
88
- This follows Django's pattern where Manager methods call QuerySet methods.
89
- """
90
- return self.get_queryset().bulk_create(
91
- objs,
92
- batch_size=batch_size,
93
- ignore_conflicts=ignore_conflicts,
94
- update_conflicts=update_conflicts,
95
- update_fields=update_fields,
96
- unique_fields=unique_fields,
97
- bypass_hooks=bypass_hooks,
98
- bypass_validation=bypass_validation,
99
- )
100
-
101
- @transaction.atomic
102
- def bulk_delete(
103
- self, objs, batch_size=None, bypass_hooks=False, bypass_validation=False
104
- ):
105
- if not objs:
106
- return []
107
-
108
- model_cls = self.model
109
-
110
- if any(not isinstance(obj, model_cls) for obj in objs):
111
- raise TypeError(
112
- f"bulk_delete expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
113
- )
114
-
115
- ctx = HookContext(model_cls)
116
-
117
- if not bypass_hooks:
118
- # Run validation hooks first
119
- if not bypass_validation:
120
- engine.run(model_cls, VALIDATE_DELETE, objs, ctx=ctx)
121
-
122
- # Then run business logic hooks
123
- engine.run(model_cls, BEFORE_DELETE, objs, ctx=ctx)
124
-
125
- pks = [obj.pk for obj in objs if obj.pk is not None]
126
-
127
- # Use base manager for the actual deletion to prevent recursion
128
- # The hooks have already been fired above, so we don't need them again
129
- model_cls._base_manager.filter(pk__in=pks).delete()
130
-
131
- if not bypass_hooks:
132
- engine.run(model_cls, AFTER_DELETE, objs, ctx=ctx)
133
-
134
- return objs
135
-
136
- @transaction.atomic
137
- def update(self, **kwargs):
138
- objs = list(self.all())
139
- if not objs:
140
- return 0
141
- for key, value in kwargs.items():
142
- for obj in objs:
143
- setattr(obj, key, value)
144
- self.bulk_update(objs, fields=list(kwargs.keys()))
145
- return len(objs)
146
-
147
- @transaction.atomic
148
- def delete(self):
149
- objs = list(self.all())
150
- if not objs:
151
- return 0
152
- self.bulk_delete(objs)
153
- return len(objs)
154
-
155
- @transaction.atomic
156
- def save(self, obj):
157
- if obj.pk:
158
- self.bulk_update(
159
- [obj],
160
- fields=[field.name for field in obj._meta.fields if field.name != "id"],
161
- )
162
- else:
163
- self.bulk_create([obj])
164
- return obj
165
-
166
- def _detect_modified_fields(self, new_instances, original_instances):
167
- """
168
- Detect fields that were modified during BEFORE_UPDATE hooks by comparing
169
- new instances with their original values.
170
- """
171
- if not original_instances:
172
- return set()
173
-
174
- modified_fields = set()
175
-
176
- # Since original_instances is now ordered to match new_instances, we can zip them directly
177
- for new_instance, original in zip(new_instances, original_instances):
178
- if new_instance.pk is None or original is None:
179
- continue
180
-
181
- # Compare all fields to detect changes
182
- for field in new_instance._meta.fields:
183
- if field.name == "id":
184
- continue
185
-
186
- new_value = getattr(new_instance, field.name)
187
- original_value = getattr(original, field.name)
188
-
189
- # Handle different field types appropriately
190
- if field.is_relation:
191
- # For foreign keys, compare the pk values
192
- new_pk = new_value.pk if new_value else None
193
- original_pk = original_value.pk if original_value else None
194
- if new_pk != original_pk:
195
- modified_fields.add(field.name)
196
- else:
197
- # For regular fields, use direct comparison
198
- if new_value != original_value:
199
- modified_fields.add(field.name)
200
-
201
- return modified_fields