karrio-server-manager 2025.5rc34__py3-none-any.whl → 2025.5rc36__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.
@@ -72,7 +72,6 @@ class PickupManager(models.Manager):
72
72
  return (
73
73
  super()
74
74
  .get_queryset()
75
- .select_related("pickup_carrier")
76
75
  .prefetch_related(
77
76
  "shipments",
78
77
  *(("org",) if conf.settings.MULTI_ORGANIZATIONS else tuple()),
@@ -94,12 +93,13 @@ class ShipmentManager(models.Manager):
94
93
  "return_address",
95
94
  "billing_address",
96
95
  "shipment_tracker",
97
- "selected_rate_carrier",
98
96
  "shipment_upload_record",
99
97
  )
100
98
  .prefetch_related(
101
99
  "parcels",
102
- "carriers",
100
+ "parcels__items",
101
+ "customs__commodities",
102
+ "customs__duty_billing_address",
103
103
  *(("org",) if conf.settings.MULTI_ORGANIZATIONS else tuple()),
104
104
  )
105
105
  )
@@ -112,7 +112,6 @@ class TrackingManager(models.Manager):
112
112
  .get_queryset()
113
113
  .select_related(
114
114
  "created_by",
115
- "tracking_carrier",
116
115
  "shipment",
117
116
  "shipment__recipient",
118
117
  "shipment__shipper",
@@ -128,7 +127,6 @@ class DocumentUploadRecordManager(models.Manager):
128
127
  return (
129
128
  super()
130
129
  .get_queryset()
131
- .select_related("upload_carrier")
132
130
  .prefetch_related(
133
131
  *(("org",) if conf.settings.MULTI_ORGANIZATIONS else tuple()),
134
132
  )
@@ -141,7 +139,6 @@ class ManifestManager(models.Manager):
141
139
  return (
142
140
  super()
143
141
  .get_queryset()
144
- .select_related("manifest_carrier")
145
142
  .defer("manifest")
146
143
  .prefetch_related(
147
144
  *(("org",) if conf.settings.MULTI_ORGANIZATIONS else tuple()),
@@ -483,6 +480,7 @@ class Customs(core.OwnedEntity):
483
480
 
484
481
  @core.register_model
485
482
  class Pickup(core.OwnedEntity):
483
+ CONTEXT_RELATIONS = ["pickup_carrier"]
486
484
  DIRECT_PROPS = [
487
485
  "confirmation_number",
488
486
  "pickup_date",
@@ -545,6 +543,16 @@ class Pickup(core.OwnedEntity):
545
543
  handle = self.address or super()
546
544
  return handle.delete(*args, **kwargs)
547
545
 
546
+ @classmethod
547
+ def resolve_context_data(cls, queryset, context):
548
+ """Apply context-aware carrier config resolution for pickup_carrier."""
549
+ from karrio.server.providers.models.carrier import Carrier
550
+
551
+ carrier_queryset = Carrier.objects.resolve_config_for(context)
552
+ return queryset.prefetch_related(
553
+ models.Prefetch("pickup_carrier", queryset=carrier_queryset),
554
+ )
555
+
548
556
  @property
549
557
  def object_type(self):
550
558
  return "pickup"
@@ -572,6 +580,7 @@ class Pickup(core.OwnedEntity):
572
580
 
573
581
  @core.register_model
574
582
  class Tracking(core.OwnedEntity):
583
+ CONTEXT_RELATIONS = ["tracking_carrier"]
575
584
  DIRECT_PROPS = [
576
585
  "metadata",
577
586
  "info",
@@ -640,6 +649,16 @@ class Tracking(core.OwnedEntity):
640
649
  "Shipment", on_delete=models.CASCADE, related_name="shipment_tracker", null=True
641
650
  )
642
651
 
652
+ @classmethod
653
+ def resolve_context_data(cls, queryset, context):
654
+ """Apply context-aware carrier config resolution for tracking_carrier."""
655
+ from karrio.server.providers.models.carrier import Carrier
656
+
657
+ carrier_queryset = Carrier.objects.resolve_config_for(context)
658
+ return queryset.prefetch_related(
659
+ models.Prefetch("tracking_carrier", queryset=carrier_queryset),
660
+ )
661
+
643
662
  @property
644
663
  def object_type(self):
645
664
  return "tracker"
@@ -683,6 +702,7 @@ class Tracking(core.OwnedEntity):
683
702
 
684
703
  @core.register_model
685
704
  class Shipment(core.OwnedEntity):
705
+ CONTEXT_RELATIONS = ["selected_rate_carrier", "carriers"]
686
706
  DIRECT_PROPS = [
687
707
  "options",
688
708
  "services",
@@ -833,6 +853,25 @@ class Shipment(core.OwnedEntity):
833
853
  self.customs and self.customs.delete()
834
854
  return super().delete(*args, **kwargs)
835
855
 
856
+ @classmethod
857
+ def resolve_context_data(cls, queryset, context):
858
+ """
859
+ Apply context-aware prefetching for carriers with proper config resolution.
860
+ This is called by access_by() to ensure carrier configs are resolved for the request context.
861
+ """
862
+ from karrio.server.providers.models.carrier import Carrier
863
+
864
+ # Resolve carrier configs with the request context for user/org-specific config
865
+ carrier_queryset = Carrier.objects.resolve_config_for(context)
866
+
867
+ # Re-apply carrier prefetches with context-aware config resolution
868
+ # Note: Manager's get_queryset() already sets up base prefetches with context=None
869
+ # This overrides those prefetches with context-aware ones when called via access_by()
870
+ return queryset.prefetch_related(
871
+ models.Prefetch("carriers", queryset=carrier_queryset),
872
+ models.Prefetch("selected_rate_carrier", queryset=carrier_queryset),
873
+ )
874
+
836
875
  @property
837
876
  def object_type(self):
838
877
  return "shipment"
@@ -910,6 +949,7 @@ class Shipment(core.OwnedEntity):
910
949
 
911
950
  @core.register_model
912
951
  class DocumentUploadRecord(core.OwnedEntity):
952
+ CONTEXT_RELATIONS = ["upload_carrier"]
913
953
  HIDDEN_PROPS = (
914
954
  "upload_carrier",
915
955
  *(("org",) if conf.settings.MULTI_ORGANIZATIONS else tuple()),
@@ -951,6 +991,16 @@ class DocumentUploadRecord(core.OwnedEntity):
951
991
  related_name="shipment_upload_record",
952
992
  )
953
993
 
994
+ @classmethod
995
+ def resolve_context_data(cls, queryset, context):
996
+ """Apply context-aware carrier config resolution for upload_carrier."""
997
+ from karrio.server.providers.models.carrier import Carrier
998
+
999
+ carrier_queryset = Carrier.objects.resolve_config_for(context)
1000
+ return queryset.prefetch_related(
1001
+ models.Prefetch("upload_carrier", queryset=carrier_queryset),
1002
+ )
1003
+
954
1004
  # Computed properties
955
1005
 
956
1006
  @property
@@ -987,6 +1037,8 @@ class Manifest(core.OwnedEntity):
987
1037
  verbose_name_plural = "Manifests"
988
1038
  ordering = ["-created_at"]
989
1039
 
1040
+ CONTEXT_RELATIONS = ["manifest_carrier"]
1041
+
990
1042
  id = models.CharField(
991
1043
  max_length=50,
992
1044
  primary_key=True,
@@ -1016,6 +1068,16 @@ class Manifest(core.OwnedEntity):
1016
1068
 
1017
1069
  manifest_carrier = models.ForeignKey(providers.Carrier, on_delete=models.CASCADE)
1018
1070
 
1071
+ @classmethod
1072
+ def resolve_context_data(cls, queryset, context):
1073
+ """Apply context-aware carrier config resolution for manifest_carrier."""
1074
+ from karrio.server.providers.models.carrier import Carrier
1075
+
1076
+ carrier_queryset = Carrier.objects.resolve_config_for(context)
1077
+ return queryset.prefetch_related(
1078
+ models.Prefetch("manifest_carrier", queryset=carrier_queryset),
1079
+ )
1080
+
1019
1081
  # Computed properties
1020
1082
 
1021
1083
  @property
@@ -22,6 +22,7 @@ from karrio.server.manager.serializers.tracking import (
22
22
  TrackerUpdateData,
23
23
  update_shipment_tracker,
24
24
  can_mutate_tracker,
25
+ update_tracker,
25
26
  )
26
27
  from karrio.server.manager.serializers.shipment import (
27
28
  ShipmentRateData,
@@ -120,64 +120,26 @@ class TrackingSerializer(TrackingDetails):
120
120
  ).data,
121
121
  carrier=carrier,
122
122
  )
123
- # update values only if changed; This is important for webhooks notification
124
- changes = []
125
- details = response.tracking
126
- info = lib.to_dict(details.info or {})
127
- events = utils.process_events(
128
- response_events=details.events, current_events=instance.events
129
- )
130
-
131
- if events != instance.events:
132
- instance.events = events
133
- changes.append("events")
134
-
135
- if response.messages != instance.messages:
136
- instance.messages = lib.to_dict(response.messages)
137
- changes.append("messages")
138
-
139
- if details.delivered != instance.delivered:
140
- instance.delivered = details.delivered
141
- changes.append("delivered")
142
-
143
- if details.status != instance.status:
144
- instance.status = details.status
145
- changes.append("status")
146
-
147
- if details.estimated_delivery != instance.estimated_delivery:
148
- instance.estimated_delivery = details.estimated_delivery
149
- changes.append("estimated_delivery")
150
-
151
- if details.options != instance.options:
152
- instance.options = details.options
153
- changes.append("options")
154
-
155
- if any(info.keys()) and info != instance.info:
156
- instance.info = serializers.process_dictionaries_mutations(
157
- ["info"], dict(info=info), instance
158
- )["info"]
159
- changes.append("info")
160
123
 
124
+ # Handle carrier change separately (not part of tracking_details)
161
125
  if carrier.id != instance.tracking_carrier.id:
162
- instance.carrier = carrier
163
- changes.append("tracking_carrier")
164
-
165
- if details.images is not None and (
166
- details.images.delivery_image != instance.delivery_image
167
- or details.images.signature_image != instance.signature_image
168
- ):
169
- changes.append("delivery_image")
170
- changes.append("signature_image")
171
- instance.delivery_image = (
172
- details.images.delivery_image or instance.delivery_image
173
- )
174
- instance.signature_image = (
175
- details.images.signature_image or instance.signature_image
176
- )
126
+ instance.tracking_carrier = carrier
127
+ instance.save(update_fields=["tracking_carrier"])
177
128
 
178
- if any(changes):
179
- instance.save(update_fields=changes)
180
- update_shipment_tracker(instance)
129
+ # Use update_tracker for the rest of the tracking details
130
+ update_tracker(
131
+ instance,
132
+ dict(
133
+ events=response.tracking.events,
134
+ messages=response.messages,
135
+ delivered=response.tracking.delivered,
136
+ status=response.tracking.status,
137
+ estimated_delivery=response.tracking.estimated_delivery,
138
+ options=response.tracking.options,
139
+ info=lib.to_dict(response.tracking.info or {}),
140
+ images=response.tracking.images,
141
+ ),
142
+ )
181
143
 
182
144
  return instance
183
145
 
@@ -246,3 +208,120 @@ def update_shipment_tracker(tracker: models.Tracking):
246
208
  tracker.shipment.save(update_fields=["status"])
247
209
  except Exception as e:
248
210
  logger.exception("Failed to update the tracked shipment", error=str(e), tracker_id=tracker.id, tracking_number=tracker.tracking_number)
211
+
212
+
213
+ def update_tracker(tracker: models.Tracking, tracking_details: dict) -> models.Tracking:
214
+ """Update tracker with new tracking details from webhook or external source.
215
+
216
+ This utility function consolidates the change detection logic for updating
217
+ a tracker instance. It only saves fields that have changed and triggers
218
+ the shipment status update via update_shipment_tracker.
219
+
220
+ Args:
221
+ tracker: The Tracking model instance to update
222
+ tracking_details: Dictionary containing tracking details with keys like:
223
+ - events: List of tracking event dictionaries
224
+ - messages: List of message dictionaries
225
+ - delivered: Boolean delivery status
226
+ - status: Tracker status string
227
+ - estimated_delivery: Estimated delivery date string
228
+ - options: Dictionary of tracking options
229
+ - meta: Dictionary of metadata
230
+ - info: Dictionary of tracking info
231
+ - images: Dictionary with delivery_image and signature_image
232
+
233
+ Returns:
234
+ The updated Tracking model instance
235
+ """
236
+ try:
237
+ changes = []
238
+
239
+ # Process events - merge with existing events
240
+ new_events = tracking_details.get("events") or []
241
+ if new_events:
242
+ events = utils.process_events(
243
+ response_events=new_events, current_events=tracker.events
244
+ )
245
+ if events != tracker.events:
246
+ tracker.events = events
247
+ changes.append("events")
248
+
249
+ # Update messages
250
+ messages = tracking_details.get("messages")
251
+ if messages is not None and messages != tracker.messages:
252
+ tracker.messages = lib.to_dict(messages)
253
+ changes.append("messages")
254
+
255
+ # Update delivered status
256
+ delivered = tracking_details.get("delivered")
257
+ if delivered is not None and delivered != tracker.delivered:
258
+ tracker.delivered = delivered
259
+ changes.append("delivered")
260
+
261
+ # Update status
262
+ status = tracking_details.get("status")
263
+ if status is not None and status != tracker.status:
264
+ tracker.status = status
265
+ changes.append("status")
266
+
267
+ # Update estimated delivery
268
+ estimated_delivery = tracking_details.get("estimated_delivery")
269
+ if estimated_delivery is not None and estimated_delivery != tracker.estimated_delivery:
270
+ tracker.estimated_delivery = estimated_delivery
271
+ changes.append("estimated_delivery")
272
+
273
+ # Update options
274
+ options = tracking_details.get("options")
275
+ if options is not None and options != tracker.options:
276
+ tracker.options = options
277
+ changes.append("options")
278
+
279
+ # Update meta
280
+ meta = tracking_details.get("meta")
281
+ if meta is not None and meta != tracker.meta:
282
+ tracker.meta = {**(tracker.meta or {}), **meta}
283
+ changes.append("meta")
284
+
285
+ # Update info - merge with existing info
286
+ info = tracking_details.get("info") or {}
287
+ if any(info.keys()) and info != tracker.info:
288
+ tracker.info = serializers.process_dictionaries_mutations(
289
+ ["info"], dict(info=info), tracker
290
+ )["info"]
291
+ changes.append("info")
292
+
293
+ # Update images
294
+ images = tracking_details.get("images") or {}
295
+ delivery_image = images.get("delivery_image") if isinstance(images, dict) else getattr(images, "delivery_image", None)
296
+ signature_image = images.get("signature_image") if isinstance(images, dict) else getattr(images, "signature_image", None)
297
+
298
+ if delivery_image is not None or signature_image is not None:
299
+ if delivery_image != tracker.delivery_image or signature_image != tracker.signature_image:
300
+ if delivery_image is not None:
301
+ tracker.delivery_image = delivery_image
302
+ changes.append("delivery_image")
303
+ if signature_image is not None:
304
+ tracker.signature_image = signature_image
305
+ changes.append("signature_image")
306
+
307
+ # Save changes and update associated shipment
308
+ if any(changes):
309
+ tracker.save(update_fields=changes)
310
+ update_shipment_tracker(tracker)
311
+ logger.info(
312
+ "Tracker updated via webhook",
313
+ tracker_id=tracker.id,
314
+ tracking_number=tracker.tracking_number,
315
+ changes=changes,
316
+ )
317
+
318
+ return tracker
319
+
320
+ except Exception as e:
321
+ logger.exception(
322
+ "Failed to update tracker",
323
+ error=str(e),
324
+ tracker_id=tracker.id,
325
+ tracking_number=tracker.tracking_number,
326
+ )
327
+ return tracker
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karrio_server_manager
3
- Version: 2025.5rc34
3
+ Version: 2025.5rc36
4
4
  Summary: Multi-carrier shipping API Shipments manager module
5
5
  Author-email: karrio <hello@karrio.io>
6
6
  License-Expression: Apache-2.0
@@ -1,7 +1,7 @@
1
1
  karrio/server/manager/__init__.py,sha256=lDTMs_O6mQl0DEI2_TniT24TDJbjnkla-5QpnwfYlxs,64
2
2
  karrio/server/manager/admin.py,sha256=QOl5e2m3ekU5aj0yj9Uq4nRQrNMB_FfqNae6RyIxkC0,35
3
3
  karrio/server/manager/apps.py,sha256=WHTQ1t79uDZTbinRzvNg1NjtFwnwEvg0tP_ChrtTRwI,364
4
- karrio/server/manager/models.py,sha256=DE1ocNQ2bI-0dy1uotxmmDUL8IoEmckE7VVUM9q0BSE,31565
4
+ karrio/server/manager/models.py,sha256=WruyZDTfqKlqu81fwAyxVCwxDu1lvl9l4bo9FbnFbos,34329
5
5
  karrio/server/manager/router.py,sha256=IBUR7rfBkdEHQzWxYOPcVSM8NBp3fte9G6Q5BVTUNNw,95
6
6
  karrio/server/manager/signals.py,sha256=3ZApZY4Ne8Gb0AT5rjC3Xneb7dnbSQyXp1OiBXt7eIA,1908
7
7
  karrio/server/manager/urls.py,sha256=oXJlvhHNKxFkc_CmpFoyTSAMJcLp4Wt9dHbViQDkqw4,220
@@ -73,7 +73,7 @@ karrio/server/manager/migrations/0064_shipment_shipment_created_at_idx_and_more.
73
73
  karrio/server/manager/migrations/0065_alter_address_city_alter_address_company_name_and_more.py,sha256=m0KQSz8cnJzaXUlaAKdqU0duZ8wSQeTYQCIeam3Wivw,6279
74
74
  karrio/server/manager/migrations/0066_commodity_image_url_commodity_product_id_and_more.py,sha256=Jw7bEBmqzDSp6zw0xcAiKc9aDCsa3Wrw-oTM8r9Pqfc,997
75
75
  karrio/server/manager/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
- karrio/server/manager/serializers/__init__.py,sha256=cbi3PpPA8nw_le56PbGP_Ua1kDyyxOt88_Vn9uESbpI,1411
76
+ karrio/server/manager/serializers/__init__.py,sha256=YVwGfjpb-_PezzGc8N8oLTkORan0hZ2muGj2PYG3n0g,1431
77
77
  karrio/server/manager/serializers/address.py,sha256=yGKvZiNukeI15LEDdbo1ycqqK8QW77ak_vyLMIKyglI,2779
78
78
  karrio/server/manager/serializers/commodity.py,sha256=PKrW-xAYkiNByk5RF8up_Bt1Z8mJV_BGW0mPWT9w4ME,1658
79
79
  karrio/server/manager/serializers/customs.py,sha256=gMGC4TJMykjgELBLFHL6v7kPQ5YQKe7cQcMnOGBLL84,2872
@@ -83,7 +83,7 @@ karrio/server/manager/serializers/parcel.py,sha256=733Bg26lVbEkoWtAVM5Qt2IRBS2QD
83
83
  karrio/server/manager/serializers/pickup.py,sha256=sX0VmcQxGkXn3IEosMuFwdXh4HhdkPcuBOp79O8PoDQ,9233
84
84
  karrio/server/manager/serializers/rate.py,sha256=7vYK_v8iWEDnswqYHG2Lir16_UhHTOxW5rdC6lw3lzA,652
85
85
  karrio/server/manager/serializers/shipment.py,sha256=G4vA51UFHvBVNNR9z3Vtac-Y7GexjGadPOCRxSUVqRE,30355
86
- karrio/server/manager/serializers/tracking.py,sha256=TguVVqNwbKSdDx0m614jP3PiBrkNiQ7fq1A7Ulw7jjo,9241
86
+ karrio/server/manager/serializers/tracking.py,sha256=ixrAjIiZQsvSt4y0qtisGkt6TFOJ3ORNkJAQVt6YQrA,12483
87
87
  karrio/server/manager/tests/__init__.py,sha256=Y1UNteEE60vWdUAkjbldu_r_-h4u0He8-UoiBgTjKcU,391
88
88
  karrio/server/manager/tests/test_addresses.py,sha256=pNkZC_yJyb29ZlEOtOAs4blcEYiOarw0zhZIZC5uj1w,3111
89
89
  karrio/server/manager/tests/test_custom_infos.py,sha256=iv2cLdZVoVWFZK_mDUEnrZssncAnQcn87Rn2sAk8UQI,2731
@@ -100,7 +100,7 @@ karrio/server/manager/views/parcels.py,sha256=hZY45rg6SrTWfQqyJ38MGKSor1yqgPUEVH
100
100
  karrio/server/manager/views/pickups.py,sha256=gmpxz9ot1OR-BP1qh-0MXU3kUJi1ht_74hfaLJzJ42w,5503
101
101
  karrio/server/manager/views/shipments.py,sha256=_5EJfgxEO6H2EdQQbaSwILgnim7slqxMKDkEk_97F9c,10267
102
102
  karrio/server/manager/views/trackers.py,sha256=3oGn2qDpHgk8GZvuz-Cb93Fc0j_h_HbXQR692Zhfiok,12363
103
- karrio_server_manager-2025.5rc34.dist-info/METADATA,sha256=hfvpl4yElmDkuRuJuOGez88jHjhGzM32CqGLN_q3xmM,734
104
- karrio_server_manager-2025.5rc34.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
105
- karrio_server_manager-2025.5rc34.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
106
- karrio_server_manager-2025.5rc34.dist-info/RECORD,,
103
+ karrio_server_manager-2025.5rc36.dist-info/METADATA,sha256=gqVdjXCp3F4TIfvQWnxEJ76Js6deZo0n6BRct9pJC9I,734
104
+ karrio_server_manager-2025.5rc36.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
105
+ karrio_server_manager-2025.5rc36.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
106
+ karrio_server_manager-2025.5rc36.dist-info/RECORD,,