django-bulk-hooks 0.2.44__py3-none-any.whl → 0.2.50__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.

@@ -14,7 +14,7 @@ from typing import Any
14
14
  class ParentLevel:
15
15
  """
16
16
  Represents one level in the parent hierarchy for MTI bulk create.
17
-
17
+
18
18
  Attributes:
19
19
  model_class: The parent model class for this level
20
20
  objects: List of parent instances to create
@@ -23,6 +23,7 @@ class ParentLevel:
23
23
  unique_fields: Fields for conflict detection (if update_conflicts=True)
24
24
  update_fields: Fields to update on conflict (if update_conflicts=True)
25
25
  """
26
+
26
27
  model_class: Any
27
28
  objects: list[Any]
28
29
  original_object_map: dict[int, int] = field(default_factory=dict)
@@ -35,10 +36,10 @@ class ParentLevel:
35
36
  class MTICreatePlan:
36
37
  """
37
38
  Plan for executing bulk_create on an MTI model.
38
-
39
+
39
40
  This plan describes WHAT to create, not HOW to create it.
40
41
  The executor is responsible for executing this plan.
41
-
42
+
42
43
  Attributes:
43
44
  inheritance_chain: List of model classes from root to child
44
45
  parent_levels: List of ParentLevel objects, one per parent model
@@ -51,6 +52,7 @@ class MTICreatePlan:
51
52
  unique_fields: Fields used for conflict detection
52
53
  update_fields: Fields to update on conflict
53
54
  """
55
+
54
56
  inheritance_chain: list[Any]
55
57
  parent_levels: list[ParentLevel]
56
58
  child_objects: list[Any]
@@ -67,12 +69,13 @@ class MTICreatePlan:
67
69
  class ModelFieldGroup:
68
70
  """
69
71
  Represents fields to update for one model in the inheritance chain.
70
-
72
+
71
73
  Attributes:
72
74
  model_class: The model class
73
75
  fields: List of field names to update on this model
74
76
  filter_field: Field to use for filtering (e.g., 'pk' or parent link attname)
75
77
  """
78
+
76
79
  model_class: Any
77
80
  fields: list[str]
78
81
  filter_field: str = "pk"
@@ -82,15 +85,15 @@ class ModelFieldGroup:
82
85
  class MTIUpdatePlan:
83
86
  """
84
87
  Plan for executing bulk_update on an MTI model.
85
-
88
+
86
89
  Attributes:
87
90
  inheritance_chain: List of model classes from root to child
88
91
  field_groups: List of ModelFieldGroup objects
89
92
  objects: Objects to update
90
93
  batch_size: Batch size for operations
91
94
  """
95
+
92
96
  inheritance_chain: list[Any]
93
97
  field_groups: list[ModelFieldGroup]
94
98
  objects: list[Any]
95
99
  batch_size: int = None
96
-
@@ -17,7 +17,7 @@ logger = logging.getLogger(__name__)
17
17
  class RecordClassifier:
18
18
  """
19
19
  Service for classifying and fetching records via database queries.
20
-
20
+
21
21
  This is the SINGLE point of truth for record classification queries.
22
22
  Keeps database access logic separate from business/planning logic.
23
23
  """
@@ -31,17 +31,18 @@ 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
+
38
38
  Queries the database to check which records already exist based on the
39
39
  unique_fields constraint.
40
-
40
+
41
41
  Args:
42
42
  objs: List of model instances
43
43
  unique_fields: List of field names that form the unique constraint
44
-
44
+ query_model: Optional model class to query (for MTI, may be different from self.model_cls)
45
+
45
46
  Returns:
46
47
  Tuple of (existing_record_ids, existing_pks_map)
47
48
  - existing_record_ids: Set of id() for objects that exist in DB
@@ -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,9 +81,12 @@ class RecordClassifier:
77
81
  for q in queries[1:]:
78
82
  combined_query |= q
79
83
 
80
- existing_records = list(
81
- self.model_cls.objects.filter(combined_query).values("pk", *unique_fields),
82
- )
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)
86
+ logger.info(f"Queryset SQL: {queryset.query}")
87
+ logger.info(f"All records in table: {query_model.objects.all().count()}")
88
+ existing_records = list(queryset.values("pk", *unique_fields))
89
+ logger.info(f"Found {len(existing_records)} existing records: {existing_records}")
83
90
 
84
91
  # Map existing records back to original objects
85
92
  existing_record_ids = set()
@@ -94,8 +101,7 @@ class RecordClassifier:
94
101
  existing_pks_map[obj_id] = record["pk"]
95
102
 
96
103
  logger.info(
97
- f"Classified {len(existing_record_ids)} existing and "
98
- f"{len(objs) - len(existing_record_ids)} new records for upsert",
104
+ f"Classified {len(existing_record_ids)} existing and {len(objs) - len(existing_record_ids)} new records for upsert",
99
105
  )
100
106
 
101
107
  return existing_record_ids, existing_pks_map
@@ -103,12 +109,12 @@ class RecordClassifier:
103
109
  def fetch_by_pks(self, pks, select_related=None, prefetch_related=None):
104
110
  """
105
111
  Fetch records by primary keys with optional relationship loading.
106
-
112
+
107
113
  Args:
108
114
  pks: List of primary key values
109
115
  select_related: Optional list of fields to select_related
110
116
  prefetch_related: Optional list of fields to prefetch_related
111
-
117
+
112
118
  Returns:
113
119
  Dict[pk, instance] for O(1) lookups
114
120
  """
@@ -128,10 +134,10 @@ class RecordClassifier:
128
134
  def fetch_by_unique_constraint(self, field_values_map):
129
135
  """
130
136
  Fetch records matching a unique constraint.
131
-
137
+
132
138
  Args:
133
139
  field_values_map: Dict of {field_name: value} for unique constraint
134
-
140
+
135
141
  Returns:
136
142
  Model instance if found, None otherwise
137
143
  """
@@ -141,18 +147,17 @@ class RecordClassifier:
141
147
  return None
142
148
  except self.model_cls.MultipleObjectsReturned:
143
149
  logger.warning(
144
- f"Multiple {self.model_cls.__name__} records found for "
145
- f"unique constraint {field_values_map}",
150
+ f"Multiple {self.model_cls.__name__} records found for unique constraint {field_values_map}",
146
151
  )
147
152
  return self.model_cls.objects.filter(**field_values_map).first()
148
153
 
149
154
  def exists_by_pks(self, pks):
150
155
  """
151
156
  Check if records exist by primary keys without fetching them.
152
-
157
+
153
158
  Args:
154
159
  pks: List of primary key values
155
-
160
+
156
161
  Returns:
157
162
  Set of PKs that exist in the database
158
163
  """
@@ -168,13 +173,13 @@ class RecordClassifier:
168
173
  def count_by_unique_fields(self, objs, unique_fields):
169
174
  """
170
175
  Count how many objects already exist based on unique fields.
171
-
176
+
172
177
  Useful for validation or reporting before upsert operations.
173
-
178
+
174
179
  Args:
175
180
  objs: List of model instances
176
181
  unique_fields: List of field names that form the unique constraint
177
-
182
+
178
183
  Returns:
179
184
  Tuple of (existing_count, new_count)
180
185
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-bulk-hooks
3
- Version: 0.2.44
3
+ Version: 0.2.50
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
@@ -12,15 +12,15 @@ django_bulk_hooks/helpers.py,sha256=Nw8eXryLUUquW7AgiuKp0PQT3Pq6HAHsdP-xAtqhmjA,
12
12
  django_bulk_hooks/manager.py,sha256=3mFzB0ZzHHeXWdKGObZD_H0NlskHJc8uYBF69KKdAXU,4068
13
13
  django_bulk_hooks/models.py,sha256=4Vvi2LiGP0g4j08a5liqBROfsO8Wd_ermBoyjKwfrPU,2512
14
14
  django_bulk_hooks/operations/__init__.py,sha256=BtJYjmRhe_sScivLsniDaZmBkm0ZLvcmzXFKL7QY2Xg,550
15
- django_bulk_hooks/operations/analyzer.py,sha256=dXcgk99Q9Zv7r2PMNIQE9f-hkPW3rGKXnDw28r3C7IE,10782
16
- django_bulk_hooks/operations/bulk_executor.py,sha256=Biyt8RxbBsxC5u4GDwHq2BSGQTrF646mRKYAjxR-WC0,23259
17
- django_bulk_hooks/operations/coordinator.py,sha256=qH4AAx1fZ-h_Q6w5qa2Vqbq98nx5ezAwCPpbj2oKccA,29076
18
- django_bulk_hooks/operations/mti_handler.py,sha256=xKEz9hgdFnA6mX5RQ8Pa1MJFluzeJBJ6EqFJxXTyedM,19664
19
- django_bulk_hooks/operations/mti_plans.py,sha256=YP7LcV9Z8UqNS_x74OswF9_5swqruRTdAu6z-J_R6C0,3377
20
- django_bulk_hooks/operations/record_classifier.py,sha256=KzUoAhfoqzFVrOabNZAby9Akb54h-fAQZmb8O-fIx_0,6221
15
+ django_bulk_hooks/operations/analyzer.py,sha256=wAG8sAG9NwfwNqG9z81VfGR7AANDzRmMGE_o82MWji4,10689
16
+ django_bulk_hooks/operations/bulk_executor.py,sha256=kqRGSdqKHc1WoG8yEhn82gKZ9KelV3GGTU7YccUdFG0,24737
17
+ django_bulk_hooks/operations/coordinator.py,sha256=iGavJLqe3eYRqFay8cMn6muwyRYzQo-HFGphsS5hL6g,30799
18
+ django_bulk_hooks/operations/mti_handler.py,sha256=uDrfE29e80v4YjPF4fPw2x0bntRnFGyfEh-grMQUGu0,20907
19
+ django_bulk_hooks/operations/mti_plans.py,sha256=7STQ2oA2ZT8cEG3-t-6xciRAdf7OeSf0gRLXR_BRG-Q,3363
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.44.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
24
- django_bulk_hooks-0.2.44.dist-info/METADATA,sha256=uyhU1_WPDEgljEWHdoWHa0KtfTAkMkY8MRpuKDSUpYU,9265
25
- django_bulk_hooks-0.2.44.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
26
- django_bulk_hooks-0.2.44.dist-info/RECORD,,
23
+ django_bulk_hooks-0.2.50.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
24
+ django_bulk_hooks-0.2.50.dist-info/METADATA,sha256=oVFxbFoCH691HY57pcgVa9em9_M0IAg1L0VECri_l_Q,9265
25
+ django_bulk_hooks-0.2.50.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
26
+ django_bulk_hooks-0.2.50.dist-info/RECORD,,