django-bulk-hooks 0.2.46__py3-none-any.whl → 0.2.47__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/operations/bulk_executor.py +8 -1
- django_bulk_hooks/operations/coordinator.py +9 -1
- django_bulk_hooks/operations/mti_handler.py +29 -0
- django_bulk_hooks/operations/record_classifier.py +8 -4
- {django_bulk_hooks-0.2.46.dist-info → django_bulk_hooks-0.2.47.dist-info}/METADATA +1 -1
- {django_bulk_hooks-0.2.46.dist-info → django_bulk_hooks-0.2.47.dist-info}/RECORD +8 -8
- {django_bulk_hooks-0.2.46.dist-info → django_bulk_hooks-0.2.47.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.2.46.dist-info → django_bulk_hooks-0.2.47.dist-info}/WHEEL +0 -0
|
@@ -80,7 +80,14 @@ class BulkExecutor:
|
|
|
80
80
|
existing_record_ids = set()
|
|
81
81
|
existing_pks_map = {}
|
|
82
82
|
if update_conflicts and unique_fields:
|
|
83
|
-
|
|
83
|
+
# For MTI, find which model has the unique fields and query THAT model
|
|
84
|
+
# This handles the schema migration case where parent exists but child doesn't
|
|
85
|
+
query_model = self.mti_handler.find_model_with_unique_fields(unique_fields)
|
|
86
|
+
logger.info(f"MTI upsert: querying {query_model.__name__} for unique fields {unique_fields}")
|
|
87
|
+
|
|
88
|
+
existing_record_ids, existing_pks_map = self.record_classifier.classify_for_upsert(
|
|
89
|
+
objs, unique_fields, query_model=query_model
|
|
90
|
+
)
|
|
84
91
|
logger.info(f"MTI Upsert classification: {len(existing_record_ids)} existing, {len(objs) - len(existing_record_ids)} new")
|
|
85
92
|
logger.info(f"existing_record_ids: {existing_record_ids}")
|
|
86
93
|
logger.info(f"existing_pks_map: {existing_pks_map}")
|
|
@@ -136,7 +136,15 @@ class BulkOperationCoordinator:
|
|
|
136
136
|
existing_record_ids = set()
|
|
137
137
|
existing_pks_map = {}
|
|
138
138
|
if update_conflicts and unique_fields:
|
|
139
|
-
|
|
139
|
+
# For MTI models, query the parent model that has the unique fields
|
|
140
|
+
query_model = None
|
|
141
|
+
if self.mti_handler.is_mti_model():
|
|
142
|
+
query_model = self.mti_handler.find_model_with_unique_fields(unique_fields)
|
|
143
|
+
logger.info(f"MTI model detected: querying {query_model.__name__} for unique fields {unique_fields}")
|
|
144
|
+
|
|
145
|
+
existing_record_ids, existing_pks_map = self.record_classifier.classify_for_upsert(
|
|
146
|
+
objs, unique_fields, query_model=query_model
|
|
147
|
+
)
|
|
140
148
|
logger.info(f"Upsert operation: {len(existing_record_ids)} existing, {len(objs) - len(existing_record_ids)} new records")
|
|
141
149
|
logger.debug(f"Existing record IDs: {existing_record_ids}")
|
|
142
150
|
logger.debug(f"Existing PKs map: {existing_pks_map}")
|
|
@@ -111,6 +111,35 @@ class MTIHandler:
|
|
|
111
111
|
"""
|
|
112
112
|
return list(model_cls._meta.local_fields)
|
|
113
113
|
|
|
114
|
+
def find_model_with_unique_fields(self, unique_fields):
|
|
115
|
+
"""
|
|
116
|
+
Find which model in the inheritance chain contains the unique fields.
|
|
117
|
+
|
|
118
|
+
This is critical for MTI upserts: we need to query the model that has
|
|
119
|
+
the unique constraint, not necessarily the child model.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
unique_fields: List of field names forming the unique constraint
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Model class that contains all the unique fields (closest to root)
|
|
126
|
+
"""
|
|
127
|
+
if not unique_fields:
|
|
128
|
+
return self.model_cls
|
|
129
|
+
|
|
130
|
+
inheritance_chain = self.get_inheritance_chain()
|
|
131
|
+
|
|
132
|
+
# Start from root and find the first model that has all unique fields
|
|
133
|
+
for model_cls in inheritance_chain:
|
|
134
|
+
model_field_names = {f.name for f in model_cls._meta.local_fields}
|
|
135
|
+
|
|
136
|
+
# Check if this model has all the unique fields
|
|
137
|
+
if all(field in model_field_names for field in unique_fields):
|
|
138
|
+
return model_cls
|
|
139
|
+
|
|
140
|
+
# Fallback to child model (shouldn't happen if unique_fields are valid)
|
|
141
|
+
return self.model_cls
|
|
142
|
+
|
|
114
143
|
# ==================== MTI BULK CREATE PLANNING ====================
|
|
115
144
|
|
|
116
145
|
def build_create_plan(
|
|
@@ -31,7 +31,7 @@ class RecordClassifier:
|
|
|
31
31
|
"""
|
|
32
32
|
self.model_cls = model_cls
|
|
33
33
|
|
|
34
|
-
def classify_for_upsert(self, objs, unique_fields):
|
|
34
|
+
def classify_for_upsert(self, objs, unique_fields, query_model=None):
|
|
35
35
|
"""
|
|
36
36
|
Classify records as new or existing based on unique_fields.
|
|
37
37
|
|
|
@@ -41,6 +41,7 @@ class RecordClassifier:
|
|
|
41
41
|
Args:
|
|
42
42
|
objs: List of model instances
|
|
43
43
|
unique_fields: List of field names that form the unique constraint
|
|
44
|
+
query_model: Optional model class to query (for MTI, may be different from self.model_cls)
|
|
44
45
|
|
|
45
46
|
Returns:
|
|
46
47
|
Tuple of (existing_record_ids, existing_pks_map)
|
|
@@ -50,6 +51,9 @@ class RecordClassifier:
|
|
|
50
51
|
if not unique_fields or not objs:
|
|
51
52
|
return set(), {}
|
|
52
53
|
|
|
54
|
+
# Use query_model if provided (for MTI scenarios), otherwise use self.model_cls
|
|
55
|
+
query_model = query_model or self.model_cls
|
|
56
|
+
|
|
53
57
|
# Build a query to find existing records
|
|
54
58
|
queries = []
|
|
55
59
|
obj_to_unique_values = {}
|
|
@@ -77,10 +81,10 @@ class RecordClassifier:
|
|
|
77
81
|
for q in queries[1:]:
|
|
78
82
|
combined_query |= q
|
|
79
83
|
|
|
80
|
-
logger.info(f"Classifying for upsert: model={
|
|
81
|
-
queryset =
|
|
84
|
+
logger.info(f"Classifying for upsert: model={query_model.__name__}, query={combined_query}, unique_fields={unique_fields}")
|
|
85
|
+
queryset = query_model.objects.filter(combined_query)
|
|
82
86
|
logger.info(f"Queryset SQL: {queryset.query}")
|
|
83
|
-
logger.info(f"All records in table: {
|
|
87
|
+
logger.info(f"All records in table: {query_model.objects.all().count()}")
|
|
84
88
|
existing_records = list(queryset.values("pk", *unique_fields))
|
|
85
89
|
logger.info(f"Found {len(existing_records)} existing records: {existing_records}")
|
|
86
90
|
|
|
@@ -13,14 +13,14 @@ django_bulk_hooks/manager.py,sha256=3mFzB0ZzHHeXWdKGObZD_H0NlskHJc8uYBF69KKdAXU,
|
|
|
13
13
|
django_bulk_hooks/models.py,sha256=4Vvi2LiGP0g4j08a5liqBROfsO8Wd_ermBoyjKwfrPU,2512
|
|
14
14
|
django_bulk_hooks/operations/__init__.py,sha256=BtJYjmRhe_sScivLsniDaZmBkm0ZLvcmzXFKL7QY2Xg,550
|
|
15
15
|
django_bulk_hooks/operations/analyzer.py,sha256=wAG8sAG9NwfwNqG9z81VfGR7AANDzRmMGE_o82MWji4,10689
|
|
16
|
-
django_bulk_hooks/operations/bulk_executor.py,sha256=
|
|
17
|
-
django_bulk_hooks/operations/coordinator.py,sha256=
|
|
18
|
-
django_bulk_hooks/operations/mti_handler.py,sha256=
|
|
16
|
+
django_bulk_hooks/operations/bulk_executor.py,sha256=nS8oRxMTPesJnwsqR4KdbJU6Sj0iD7ZWU2z9RJgtxao,23170
|
|
17
|
+
django_bulk_hooks/operations/coordinator.py,sha256=iGavJLqe3eYRqFay8cMn6muwyRYzQo-HFGphsS5hL6g,30799
|
|
18
|
+
django_bulk_hooks/operations/mti_handler.py,sha256=GIpTzzoo_hx1iI5osx2COk3cVQgqTsD-o41d9S7aiqo,20748
|
|
19
19
|
django_bulk_hooks/operations/mti_plans.py,sha256=7STQ2oA2ZT8cEG3-t-6xciRAdf7OeSf0gRLXR_BRG-Q,3363
|
|
20
|
-
django_bulk_hooks/operations/record_classifier.py,sha256=
|
|
20
|
+
django_bulk_hooks/operations/record_classifier.py,sha256=vNi0WSNiPAVb8pTZZJ26b81oX59snk7LIxWMzyenDCk,6694
|
|
21
21
|
django_bulk_hooks/queryset.py,sha256=aQitlbexcVnmeAdc0jtO3hci39p4QEu4srQPEzozy5s,5546
|
|
22
22
|
django_bulk_hooks/registry.py,sha256=uum5jhGI3TPaoiXuA1MdBdu4gbE3rQGGwQ5YDjiMcjk,7949
|
|
23
|
-
django_bulk_hooks-0.2.
|
|
24
|
-
django_bulk_hooks-0.2.
|
|
25
|
-
django_bulk_hooks-0.2.
|
|
26
|
-
django_bulk_hooks-0.2.
|
|
23
|
+
django_bulk_hooks-0.2.47.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
24
|
+
django_bulk_hooks-0.2.47.dist-info/METADATA,sha256=Dp_OKvGqoa7j0zax8Cc-j5uH-acwsaCd1o0y-nw30U0,9265
|
|
25
|
+
django_bulk_hooks-0.2.47.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
26
|
+
django_bulk_hooks-0.2.47.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|