django-bulk-hooks 0.2.60__py3-none-any.whl → 0.2.62__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.
- django_bulk_hooks/changeset.py +214 -230
- django_bulk_hooks/conditions.py +7 -3
- django_bulk_hooks/decorators.py +5 -15
- django_bulk_hooks/dispatcher.py +2 -6
- django_bulk_hooks/handler.py +2 -2
- django_bulk_hooks/helpers.py +251 -100
- django_bulk_hooks/manager.py +150 -130
- django_bulk_hooks/models.py +74 -75
- django_bulk_hooks/operations/analyzer.py +319 -312
- django_bulk_hooks/operations/bulk_executor.py +109 -126
- django_bulk_hooks/operations/coordinator.py +125 -86
- django_bulk_hooks/operations/field_utils.py +137 -5
- django_bulk_hooks/operations/mti_handler.py +29 -19
- django_bulk_hooks/operations/mti_plans.py +103 -99
- django_bulk_hooks/operations/record_classifier.py +1 -1
- django_bulk_hooks/queryset.py +8 -2
- django_bulk_hooks/registry.py +0 -2
- {django_bulk_hooks-0.2.60.dist-info → django_bulk_hooks-0.2.62.dist-info}/METADATA +1 -1
- django_bulk_hooks-0.2.62.dist-info/RECORD +27 -0
- django_bulk_hooks-0.2.60.dist-info/RECORD +0 -27
- {django_bulk_hooks-0.2.60.dist-info → django_bulk_hooks-0.2.62.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.2.60.dist-info → django_bulk_hooks-0.2.62.dist-info}/WHEEL +0 -0
|
@@ -1,99 +1,103 @@
|
|
|
1
|
-
"""
|
|
2
|
-
MTI operation plans - Data structures for multi-table inheritance operations.
|
|
3
|
-
|
|
4
|
-
These are pure data structures returned by MTIHandler to be executed by BulkExecutor.
|
|
5
|
-
This separates planning (logic) from execution (database operations).
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from dataclasses import dataclass
|
|
9
|
-
from dataclasses import field
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@dataclass
|
|
14
|
-
class ParentLevel:
|
|
15
|
-
"""
|
|
16
|
-
Represents one level in the parent hierarchy for MTI bulk create.
|
|
17
|
-
|
|
18
|
-
Attributes:
|
|
19
|
-
model_class: The parent model class for this level
|
|
20
|
-
objects: List of parent instances to create
|
|
21
|
-
original_object_map: Maps parent instance id() -> original object id()
|
|
22
|
-
update_conflicts: Whether to enable UPSERT for this level
|
|
23
|
-
unique_fields: Fields for conflict detection (if update_conflicts=True)
|
|
24
|
-
update_fields: Fields to update on conflict (if update_conflicts=True)
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
model_class: Any
|
|
28
|
-
objects: list[Any]
|
|
29
|
-
original_object_map: dict[int, int] = field(default_factory=dict)
|
|
30
|
-
update_conflicts: bool = False
|
|
31
|
-
unique_fields: list[str] = field(default_factory=list)
|
|
32
|
-
update_fields: list[str] = field(default_factory=list)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@dataclass
|
|
36
|
-
class MTICreatePlan:
|
|
37
|
-
"""
|
|
38
|
-
Plan for executing bulk_create on an MTI model.
|
|
39
|
-
|
|
40
|
-
This plan describes WHAT to create, not HOW to create it.
|
|
41
|
-
The executor is responsible for executing this plan.
|
|
42
|
-
|
|
43
|
-
Attributes:
|
|
44
|
-
inheritance_chain: List of model classes from root to child
|
|
45
|
-
parent_levels: List of ParentLevel objects, one per parent model
|
|
46
|
-
child_objects: List of child instances to create (not yet with parent links)
|
|
47
|
-
child_model: The child model class
|
|
48
|
-
original_objects: Original objects provided by user
|
|
49
|
-
batch_size: Batch size for operations
|
|
50
|
-
existing_record_ids: Set of id() of original objects that represent existing DB records
|
|
51
|
-
update_conflicts: Whether this is an upsert operation
|
|
52
|
-
unique_fields: Fields used for conflict detection
|
|
53
|
-
update_fields: Fields to update on conflict
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
1
|
+
"""
|
|
2
|
+
MTI operation plans - Data structures for multi-table inheritance operations.
|
|
3
|
+
|
|
4
|
+
These are pure data structures returned by MTIHandler to be executed by BulkExecutor.
|
|
5
|
+
This separates planning (logic) from execution (database operations).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from dataclasses import field
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class ParentLevel:
|
|
15
|
+
"""
|
|
16
|
+
Represents one level in the parent hierarchy for MTI bulk create.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
model_class: The parent model class for this level
|
|
20
|
+
objects: List of parent instances to create
|
|
21
|
+
original_object_map: Maps parent instance id() -> original object id()
|
|
22
|
+
update_conflicts: Whether to enable UPSERT for this level
|
|
23
|
+
unique_fields: Fields for conflict detection (if update_conflicts=True)
|
|
24
|
+
update_fields: Fields to update on conflict (if update_conflicts=True)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
model_class: Any
|
|
28
|
+
objects: list[Any]
|
|
29
|
+
original_object_map: dict[int, int] = field(default_factory=dict)
|
|
30
|
+
update_conflicts: bool = False
|
|
31
|
+
unique_fields: list[str] = field(default_factory=list)
|
|
32
|
+
update_fields: list[str] = field(default_factory=list)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class MTICreatePlan:
|
|
37
|
+
"""
|
|
38
|
+
Plan for executing bulk_create on an MTI model.
|
|
39
|
+
|
|
40
|
+
This plan describes WHAT to create, not HOW to create it.
|
|
41
|
+
The executor is responsible for executing this plan.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
inheritance_chain: List of model classes from root to child
|
|
45
|
+
parent_levels: List of ParentLevel objects, one per parent model
|
|
46
|
+
child_objects: List of child instances to create (not yet with parent links)
|
|
47
|
+
child_model: The child model class
|
|
48
|
+
original_objects: Original objects provided by user
|
|
49
|
+
batch_size: Batch size for operations
|
|
50
|
+
existing_record_ids: Set of id() of original objects that represent existing DB records
|
|
51
|
+
update_conflicts: Whether this is an upsert operation
|
|
52
|
+
unique_fields: Fields used for conflict detection (original, unfiltered)
|
|
53
|
+
update_fields: Fields to update on conflict (original, unfiltered)
|
|
54
|
+
child_unique_fields: Pre-filtered field objects for child table conflict detection
|
|
55
|
+
child_update_fields: Pre-filtered field objects for child table updates
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
inheritance_chain: list[Any]
|
|
59
|
+
parent_levels: list[ParentLevel]
|
|
60
|
+
child_objects: list[Any]
|
|
61
|
+
child_model: Any
|
|
62
|
+
original_objects: list[Any]
|
|
63
|
+
batch_size: int = None
|
|
64
|
+
existing_record_ids: set = field(default_factory=set)
|
|
65
|
+
update_conflicts: bool = False
|
|
66
|
+
unique_fields: list[str] = field(default_factory=list)
|
|
67
|
+
update_fields: list[str] = field(default_factory=list)
|
|
68
|
+
child_unique_fields: list = field(default_factory=list) # Field objects for child table
|
|
69
|
+
child_update_fields: list = field(default_factory=list) # Field objects for child table
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class ModelFieldGroup:
|
|
74
|
+
"""
|
|
75
|
+
Represents fields to update for one model in the inheritance chain.
|
|
76
|
+
|
|
77
|
+
Attributes:
|
|
78
|
+
model_class: The model class
|
|
79
|
+
fields: List of field names to update on this model
|
|
80
|
+
filter_field: Field to use for filtering (e.g., 'pk' or parent link attname)
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
model_class: Any
|
|
84
|
+
fields: list[str]
|
|
85
|
+
filter_field: str = "pk"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class MTIUpdatePlan:
|
|
90
|
+
"""
|
|
91
|
+
Plan for executing bulk_update on an MTI model.
|
|
92
|
+
|
|
93
|
+
Attributes:
|
|
94
|
+
inheritance_chain: List of model classes from root to child
|
|
95
|
+
field_groups: List of ModelFieldGroup objects
|
|
96
|
+
objects: Objects to update
|
|
97
|
+
batch_size: Batch size for operations
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
inheritance_chain: list[Any]
|
|
101
|
+
field_groups: list[ModelFieldGroup]
|
|
102
|
+
objects: list[Any]
|
|
103
|
+
batch_size: int = None
|
|
@@ -64,7 +64,7 @@ class RecordClassifier:
|
|
|
64
64
|
# Build lookup dict for this object's unique fields
|
|
65
65
|
lookup = {}
|
|
66
66
|
normalized_values = []
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
for field_name in unique_fields:
|
|
69
69
|
# Use centralized field value extraction for consistent FK handling
|
|
70
70
|
value = get_field_value_for_db(obj, field_name, query_model)
|
django_bulk_hooks/queryset.py
CHANGED
|
@@ -11,6 +11,8 @@ import logging
|
|
|
11
11
|
from django.db import models
|
|
12
12
|
from django.db import transaction
|
|
13
13
|
|
|
14
|
+
from django_bulk_hooks.helpers import extract_pks
|
|
15
|
+
|
|
14
16
|
logger = logging.getLogger(__name__)
|
|
15
17
|
|
|
16
18
|
|
|
@@ -132,7 +134,11 @@ class HookQuerySet(models.QuerySet):
|
|
|
132
134
|
|
|
133
135
|
@transaction.atomic
|
|
134
136
|
def bulk_delete(
|
|
135
|
-
self,
|
|
137
|
+
self,
|
|
138
|
+
objs,
|
|
139
|
+
bypass_hooks=False,
|
|
140
|
+
bypass_validation=False,
|
|
141
|
+
**kwargs,
|
|
136
142
|
):
|
|
137
143
|
"""
|
|
138
144
|
Delete multiple objects with hook support.
|
|
@@ -148,7 +154,7 @@ class HookQuerySet(models.QuerySet):
|
|
|
148
154
|
Tuple of (count, details dict)
|
|
149
155
|
"""
|
|
150
156
|
# Filter queryset to only these objects
|
|
151
|
-
pks =
|
|
157
|
+
pks = extract_pks(objs)
|
|
152
158
|
if not pks:
|
|
153
159
|
return 0
|
|
154
160
|
|
django_bulk_hooks/registry.py
CHANGED
|
@@ -116,7 +116,6 @@ class HookRegistry:
|
|
|
116
116
|
if not self._hooks[key]:
|
|
117
117
|
del self._hooks[key]
|
|
118
118
|
|
|
119
|
-
|
|
120
119
|
def clear(self) -> None:
|
|
121
120
|
"""
|
|
122
121
|
Clear all registered hooks.
|
|
@@ -132,7 +131,6 @@ class HookRegistry:
|
|
|
132
131
|
HookMeta._registered.clear()
|
|
133
132
|
HookMeta._class_hook_map.clear()
|
|
134
133
|
|
|
135
|
-
|
|
136
134
|
def list_all(self) -> dict[tuple[type, str], list[HookInfo]]:
|
|
137
135
|
"""
|
|
138
136
|
Get all registered hooks for debugging.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
django_bulk_hooks/__init__.py,sha256=ujBvX-GY4Pg4ACBh7RXm_MOmi2eAowf5s7pG2SXWdpo,2276
|
|
2
|
+
django_bulk_hooks/changeset.py,sha256=qnMD3bR2cNh8ZM8J6ASR5ly5Rjx-tPzXBYkqIjKGW98,6568
|
|
3
|
+
django_bulk_hooks/conditions.py,sha256=ar4pGjtxLKmgSIlO4S6aZFKmaBNchLtxMmWpkn4g9RU,8114
|
|
4
|
+
django_bulk_hooks/constants.py,sha256=PxpEETaO6gdENcTPoXS586lerGKVP3nmjpDvOkmhYxI,509
|
|
5
|
+
django_bulk_hooks/context.py,sha256=mqaC5-yESDTA5ruI7fuXlt8qSgKuOFp0mjq7h1-4HdQ,1926
|
|
6
|
+
django_bulk_hooks/decorators.py,sha256=TdkO4FJyFrVU2zqK6Y_6JjEJ4v3nbKkk7aa22jN10sk,11994
|
|
7
|
+
django_bulk_hooks/dispatcher.py,sha256=yHfmAWChj9rsZwO4jhYDVtJde_Z8bPe9WHRjkYBMfEo,8151
|
|
8
|
+
django_bulk_hooks/enums.py,sha256=Zo8_tJzuzZ2IKfVc7gZ-0tWPT8q1QhqZbAyoh9ZVJbs,381
|
|
9
|
+
django_bulk_hooks/factory.py,sha256=ezrVM5U023KZqOBbJXb6lYUP-pE7WJmi8Olh2Ew-7RA,18085
|
|
10
|
+
django_bulk_hooks/handler.py,sha256=SRCrMzgolrruTkvMnYBFmXLR-ABiw0JiH3605PEdCZM,4207
|
|
11
|
+
django_bulk_hooks/helpers.py,sha256=tMxUI5oWhbtWByzCCR0Qcj1CgZ6iP5Jfx03EqVmEhxU,7597
|
|
12
|
+
django_bulk_hooks/manager.py,sha256=aDuP87DZLWWbDK2qeA7usl3pxoIjHFIWnQNi_jEq6z0,4446
|
|
13
|
+
django_bulk_hooks/models.py,sha256=TWN_F-SsLGPx9jrkNT9pmJFR5VsZ0Z_QaVOZOmt7bpw,2434
|
|
14
|
+
django_bulk_hooks/operations/__init__.py,sha256=BtJYjmRhe_sScivLsniDaZmBkm0ZLvcmzXFKL7QY2Xg,550
|
|
15
|
+
django_bulk_hooks/operations/analyzer.py,sha256=Pz8mc-EL8KDOfLQFYiRuN-r0OmINW3nIBhRJJCma-yo,10360
|
|
16
|
+
django_bulk_hooks/operations/bulk_executor.py,sha256=po8V_2H3ULiE0RYJ-wbaRIz52SKhss81UHwuQjlz3H8,26214
|
|
17
|
+
django_bulk_hooks/operations/coordinator.py,sha256=NPPkiEIMgbJXEIOtBqvy3OH4l0pOK_NL5jwT7Og9I4o,33765
|
|
18
|
+
django_bulk_hooks/operations/field_utils.py,sha256=cQ9w4xdk-z3PrMLFvRzVV07Wc0D2qbpSepwoupqwQH8,7888
|
|
19
|
+
django_bulk_hooks/operations/mti_handler.py,sha256=Vmz0C0gtYDvbybmb4cDzIaGglSaQK4DQVkaBK-WuQeE,25855
|
|
20
|
+
django_bulk_hooks/operations/mti_plans.py,sha256=HIRJgogHPpm6MV7nZZ-sZhMLUnozpZPV2SzwQHLRzYc,3667
|
|
21
|
+
django_bulk_hooks/operations/record_classifier.py,sha256=It85hJC2K-UsEOLbTR-QBdY5UPV-acQIJ91TSGa7pYo,7053
|
|
22
|
+
django_bulk_hooks/queryset.py,sha256=g_9OtOTC8FXY0hBwYr2FCqQ3mYXbfJTFPLlFV3SHmWQ,5600
|
|
23
|
+
django_bulk_hooks/registry.py,sha256=4HxP1mVK2z4VzvlohbEw2359wM21UJZJYagJJ1komM0,7947
|
|
24
|
+
django_bulk_hooks-0.2.62.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
25
|
+
django_bulk_hooks-0.2.62.dist-info/METADATA,sha256=Ib--glZc6N4AtvIcIGqMAQUTdAk0AeCmrV2wv6u8iWQ,9265
|
|
26
|
+
django_bulk_hooks-0.2.62.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
27
|
+
django_bulk_hooks-0.2.62.dist-info/RECORD,,
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
django_bulk_hooks/__init__.py,sha256=ujBvX-GY4Pg4ACBh7RXm_MOmi2eAowf5s7pG2SXWdpo,2276
|
|
2
|
-
django_bulk_hooks/changeset.py,sha256=wU6wckJEQ_hG5UZq_9g_kWODoKwEj99JrtacVUQ9BxA,7445
|
|
3
|
-
django_bulk_hooks/conditions.py,sha256=v2DMFmWI7bppBQw5qdbO5CmQRN_QtUwnBjcyKBJLLbw,8030
|
|
4
|
-
django_bulk_hooks/constants.py,sha256=PxpEETaO6gdENcTPoXS586lerGKVP3nmjpDvOkmhYxI,509
|
|
5
|
-
django_bulk_hooks/context.py,sha256=mqaC5-yESDTA5ruI7fuXlt8qSgKuOFp0mjq7h1-4HdQ,1926
|
|
6
|
-
django_bulk_hooks/decorators.py,sha256=P7cvzFgORJRW-YQHNAxNXqQOP9OywBmA7Rz9kiJoxUk,12237
|
|
7
|
-
django_bulk_hooks/dispatcher.py,sha256=CiKYe5ecUPu5TYUZq8ToaRT40TkLc5l5mczgf5XDzGA,8217
|
|
8
|
-
django_bulk_hooks/enums.py,sha256=Zo8_tJzuzZ2IKfVc7gZ-0tWPT8q1QhqZbAyoh9ZVJbs,381
|
|
9
|
-
django_bulk_hooks/factory.py,sha256=ezrVM5U023KZqOBbJXb6lYUP-pE7WJmi8Olh2Ew-7RA,18085
|
|
10
|
-
django_bulk_hooks/handler.py,sha256=38ejMdQ9reYA07_XQ9tC8xv0lW3amO-m8gPzuRNOyj0,4200
|
|
11
|
-
django_bulk_hooks/helpers.py,sha256=Nw8eXryLUUquW7AgiuKp0PQT3Pq6HAHsdP-xAtqhmjA,3216
|
|
12
|
-
django_bulk_hooks/manager.py,sha256=3mFzB0ZzHHeXWdKGObZD_H0NlskHJc8uYBF69KKdAXU,4068
|
|
13
|
-
django_bulk_hooks/models.py,sha256=4Vvi2LiGP0g4j08a5liqBROfsO8Wd_ermBoyjKwfrPU,2512
|
|
14
|
-
django_bulk_hooks/operations/__init__.py,sha256=BtJYjmRhe_sScivLsniDaZmBkm0ZLvcmzXFKL7QY2Xg,550
|
|
15
|
-
django_bulk_hooks/operations/analyzer.py,sha256=wAG8sAG9NwfwNqG9z81VfGR7AANDzRmMGE_o82MWji4,10689
|
|
16
|
-
django_bulk_hooks/operations/bulk_executor.py,sha256=0_r9qaOMl9RJpi9eE3GrdUAlXA6ZU016nHYeIZ-yiPE,28279
|
|
17
|
-
django_bulk_hooks/operations/coordinator.py,sha256=1Ka5eZJXTFjx3tr-BD6Tr350Y2T57SUOX3vjagBYBvM,32193
|
|
18
|
-
django_bulk_hooks/operations/field_utils.py,sha256=Tvr5bcZLG8imH-r2S85oui1Cbw6hGv3VtuIMn4OvsU4,2895
|
|
19
|
-
django_bulk_hooks/operations/mti_handler.py,sha256=pguym-6vqaLxu-lY7gnb5laKT9RJQlUciNw1SflZ1Zs,25574
|
|
20
|
-
django_bulk_hooks/operations/mti_plans.py,sha256=7STQ2oA2ZT8cEG3-t-6xciRAdf7OeSf0gRLXR_BRG-Q,3363
|
|
21
|
-
django_bulk_hooks/operations/record_classifier.py,sha256=kqML4aO11X9K3SSJ5DUlUukwI172j_Tk12Kr77ee8q8,7065
|
|
22
|
-
django_bulk_hooks/queryset.py,sha256=aQitlbexcVnmeAdc0jtO3hci39p4QEu4srQPEzozy5s,5546
|
|
23
|
-
django_bulk_hooks/registry.py,sha256=uum5jhGI3TPaoiXuA1MdBdu4gbE3rQGGwQ5YDjiMcjk,7949
|
|
24
|
-
django_bulk_hooks-0.2.60.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
25
|
-
django_bulk_hooks-0.2.60.dist-info/METADATA,sha256=tYgGJtWSG7Zx360peLawbsJAiIkT-BwF7WuJg4JfkJM,9265
|
|
26
|
-
django_bulk_hooks-0.2.60.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
27
|
-
django_bulk_hooks-0.2.60.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|