karrio-server-manager 2025.5.2__py3-none-any.whl → 2025.5.3__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.
@@ -131,11 +131,16 @@ class ShipmentSerializer(ShipmentData):
131
131
  self, validated_data: dict, context: Context, **kwargs
132
132
  ) -> models.Shipment:
133
133
  # fmt: off
134
+ # Apply shipping method if specified (HIGHEST PRIORITY - supersedes service)
135
+ apply_shipping_method_flag, validated_data = resolve_shipping_method(
136
+ validated_data, context
137
+ )
138
+ options = validated_data.get("options") or {}
139
+
134
140
  service = validated_data.get("service")
135
141
  carrier_ids = validated_data.get("carrier_ids") or []
136
142
  fetch_rates = validated_data.get("fetch_rates") is not False
137
143
  services = [service] if service is not None else validated_data.get("services")
138
- options = validated_data.get("options") or {}
139
144
 
140
145
  # Check if we should skip rate fetching for has_alternative_services
141
146
  skip_rate_fetching, resolved_carrier_name, _ = (
@@ -244,8 +249,8 @@ class ShipmentSerializer(ShipmentData):
244
249
  context=context,
245
250
  )
246
251
 
247
- # Buy label if preferred service is selected, shipping rules applied, or skip rate fetching
248
- if (service and fetch_rates) or apply_shipping_rules or skip_rate_fetching:
252
+ # Buy label if preferred service is selected, shipping method applied, shipping rules applied, or skip rate fetching
253
+ if (service and fetch_rates) or apply_shipping_method_flag or apply_shipping_rules or skip_rate_fetching:
249
254
  return buy_shipment_label(
250
255
  shipment,
251
256
  context=context,
@@ -673,16 +678,23 @@ def buy_shipment_label(
673
678
  docs={**lib.to_dict(response.docs), **invoice},
674
679
  )
675
680
 
676
- # Update shipment state
681
+ # Update shipment state - preserve original meta and merge with response meta
682
+ response_details = ShipmentDetails(response).data
683
+ merged_meta = {
684
+ **(shipment.meta or {}),
685
+ **(response_details.get("meta") or {}),
686
+ **({"rule_activity": kwargs.get("rule_activity")} if kwargs.get("rule_activity") else {}),
687
+ }
688
+
677
689
  purchased_shipment = lib.identity(
678
690
  ShipmentSerializer.map(
679
691
  shipment,
680
692
  context=context,
681
693
  data={
682
694
  **payload,
683
- **ShipmentDetails(response).data,
695
+ **response_details,
684
696
  **extra,
685
- # "meta": {**(response.meta or {}), "rule_activity": kwargs.get("rule_activity", None)},
697
+ "meta": merged_meta,
686
698
  },
687
699
  )
688
700
  .save()
@@ -998,3 +1010,43 @@ def resolve_alternative_service_carrier(
998
1010
  ]
999
1011
 
1000
1012
  return skip_rate_fetching, resolved_carrier_name, synthetic_rates
1013
+
1014
+
1015
+ def resolve_shipping_method(
1016
+ validated_data: dict,
1017
+ context: Context,
1018
+ ) -> typing.Tuple[bool, dict]:
1019
+ """
1020
+ Resolve and apply shipping method configuration if specified.
1021
+
1022
+ When options.shipping_method is provided, this function:
1023
+ 1. Validates the SHIPPING_METHODS feature is enabled
1024
+ 2. Loads and applies the shipping method configuration
1025
+
1026
+ Returns:
1027
+ Tuple of (apply_shipping_method_flag, modified_validated_data)
1028
+ """
1029
+ if not getattr(conf.settings, "SHIPPING_METHODS", False):
1030
+ options = validated_data.get("options") or {}
1031
+ shipping_method_id = options.get("shipping_method")
1032
+
1033
+ if shipping_method_id is not None:
1034
+ raise exceptions.APIException(
1035
+ "Shipping methods feature is not enabled.",
1036
+ code="feature_disabled",
1037
+ status_code=status.HTTP_400_BAD_REQUEST,
1038
+ )
1039
+
1040
+ return False, validated_data
1041
+
1042
+ options = validated_data.get("options") or {}
1043
+ shipping_method_id = options.get("shipping_method")
1044
+
1045
+ if shipping_method_id is None:
1046
+ return False, validated_data
1047
+
1048
+ modified_data = utils.load_and_apply_shipping_method(
1049
+ validated_data, shipping_method_id, context
1050
+ )
1051
+
1052
+ return True, modified_data
@@ -16,7 +16,7 @@ class TestNotFoundErrors(APITestCase):
16
16
  self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
17
17
  self.assertDictEqual(
18
18
  response_data,
19
- {"errors": [{"code": "not_found", "message": "Address not found"}]},
19
+ {"errors": [{"code": "not_found", "message": "Address not found", "level": "warning"}]},
20
20
  )
21
21
 
22
22
  def test_parcel_not_found_returns_resource_name(self):
@@ -30,7 +30,7 @@ class TestNotFoundErrors(APITestCase):
30
30
  self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
31
31
  self.assertDictEqual(
32
32
  response_data,
33
- {"errors": [{"code": "not_found", "message": "Parcel not found"}]},
33
+ {"errors": [{"code": "not_found", "message": "Parcel not found", "level": "warning"}]},
34
34
  )
35
35
 
36
36
  def test_shipment_not_found_returns_resource_name(self):
@@ -44,7 +44,7 @@ class TestNotFoundErrors(APITestCase):
44
44
  self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
45
45
  self.assertDictEqual(
46
46
  response_data,
47
- {"errors": [{"code": "not_found", "message": "Shipment not found"}]},
47
+ {"errors": [{"code": "not_found", "message": "Shipment not found", "level": "warning"}]},
48
48
  )
49
49
 
50
50
  def test_customs_not_found_returns_resource_name(self):
@@ -58,7 +58,7 @@ class TestNotFoundErrors(APITestCase):
58
58
  self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
59
59
  self.assertDictEqual(
60
60
  response_data,
61
- {"errors": [{"code": "not_found", "message": "Customs not found"}]},
61
+ {"errors": [{"code": "not_found", "message": "Customs not found", "level": "warning"}]},
62
62
  )
63
63
 
64
64
 
@@ -76,6 +76,9 @@ class TestValidationErrors(APITestCase):
76
76
  self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
77
77
  self.assertIn("errors", response_data)
78
78
  self.assertTrue(len(response_data["errors"]) > 0)
79
+ # Validation errors should have level="error"
80
+ for error in response_data["errors"]:
81
+ self.assertEqual(error.get("level"), "error")
79
82
 
80
83
  def test_address_validation_error_format(self):
81
84
  url = reverse("karrio.server.manager:address-list")
@@ -86,3 +89,6 @@ class TestValidationErrors(APITestCase):
86
89
  self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
87
90
  self.assertIn("errors", response_data)
88
91
  self.assertTrue(len(response_data["errors"]) > 0)
92
+ # Validation errors should have level="error"
93
+ for error in response_data["errors"]:
94
+ self.assertEqual(error.get("level"), "error")
@@ -0,0 +1,83 @@
1
+ import json
2
+ from unittest.mock import patch, ANY
3
+ from django.urls import reverse
4
+ from rest_framework import status
5
+ from karrio.core.models import ManifestDetails as ManifestDetailsModel
6
+ from karrio.server.manager.tests.test_shipments import (
7
+ TestShipmentFixture,
8
+ RETURNED_RATES_VALUE,
9
+ CREATED_SHIPMENT_RESPONSE,
10
+ SINGLE_CALL_LABEL_DATA,
11
+ )
12
+
13
+
14
+ class TestManifestDocumentDownload(TestShipmentFixture):
15
+ """Test manifest document download POST API."""
16
+
17
+ def create_manifest(self):
18
+ """Create a manifest via API with a purchased shipment."""
19
+ # First create and purchase a shipment
20
+ shipment_url = reverse("karrio.server.manager:shipment-list")
21
+
22
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
23
+ mock.side_effect = [RETURNED_RATES_VALUE, CREATED_SHIPMENT_RESPONSE]
24
+ response = self.client.post(shipment_url, SINGLE_CALL_LABEL_DATA)
25
+ shipment = json.loads(response.content)
26
+
27
+ # Create manifest via API
28
+ manifest_url = reverse("karrio.server.manager:manifest-list")
29
+ manifest_data = {
30
+ "carrier_name": "canadapost",
31
+ "shipment_ids": [shipment["id"]],
32
+ "address": {
33
+ "address_line1": "125 Church St",
34
+ "city": "Moncton",
35
+ "country_code": "CA",
36
+ "postal_code": "E1C4Z8",
37
+ "state_code": "NB",
38
+ },
39
+ }
40
+
41
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
42
+ mock.return_value = MANIFEST_RESPONSE
43
+ response = self.client.post(manifest_url, manifest_data)
44
+ return json.loads(response.content)
45
+
46
+ def test_download_manifest_document(self):
47
+ manifest = self.create_manifest()
48
+
49
+ url = reverse(
50
+ "karrio.server.manager:manifest-document-download",
51
+ kwargs=dict(pk=manifest["id"]),
52
+ )
53
+ response = self.client.post(url)
54
+ response_data = json.loads(response.content)
55
+
56
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
57
+ self.assertDictEqual(response_data, MANIFEST_DOCUMENT_RESPONSE)
58
+
59
+ def test_download_manifest_not_found(self):
60
+ url = reverse(
61
+ "karrio.server.manager:manifest-document-download",
62
+ kwargs=dict(pk="manf_non_existent_id"),
63
+ )
64
+ response = self.client.post(url)
65
+
66
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
67
+
68
+
69
+ MANIFEST_RESPONSE = (
70
+ ManifestDetailsModel(
71
+ carrier_id="canadapost",
72
+ carrier_name="canadapost",
73
+ doc=dict(manifest="JVBERi0xLjQK"),
74
+ ),
75
+ [],
76
+ )
77
+
78
+ MANIFEST_DOCUMENT_RESPONSE = {
79
+ "category": "manifest",
80
+ "format": "PDF",
81
+ "base64": "JVBERi0xLjQK",
82
+ "url": ANY,
83
+ }
@@ -1096,3 +1096,104 @@ SINGLE_CALL_SKIP_RATES_DATA = {
1096
1096
  "service": "canadapost_priority",
1097
1097
  "options": {"has_alternative_services": True},
1098
1098
  }
1099
+
1100
+
1101
+ class TestShipmentDocumentDownload(APITestCase):
1102
+ """Test shipment document download POST API."""
1103
+
1104
+ def create_purchased_shipment(self):
1105
+ """Create and purchase a shipment via API."""
1106
+ url = reverse("karrio.server.manager:shipment-list")
1107
+
1108
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
1109
+ mock.side_effect = [RETURNED_RATES_VALUE, CREATED_SHIPMENT_RESPONSE]
1110
+ response = self.client.post(url, SINGLE_CALL_LABEL_DATA)
1111
+ return json.loads(response.content)
1112
+
1113
+ def test_download_label_document(self):
1114
+ shipment = self.create_purchased_shipment()
1115
+
1116
+ url = reverse(
1117
+ "karrio.server.manager:shipment-document-download",
1118
+ kwargs=dict(pk=shipment["id"], doc="label"),
1119
+ )
1120
+ response = self.client.post(url)
1121
+ response_data = json.loads(response.content)
1122
+
1123
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
1124
+ self.assertDictEqual(
1125
+ response_data,
1126
+ LABEL_DOCUMENT_RESPONSE,
1127
+ )
1128
+
1129
+ def test_download_document_not_found(self):
1130
+ # Create a draft shipment (no label)
1131
+ url = reverse("karrio.server.manager:shipment-list")
1132
+
1133
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
1134
+ mock.return_value = RETURNED_RATES_VALUE
1135
+ response = self.client.post(url, SHIPMENT_DATA)
1136
+ shipment = json.loads(response.content)
1137
+
1138
+ url = reverse(
1139
+ "karrio.server.manager:shipment-document-download",
1140
+ kwargs=dict(pk=shipment["id"], doc="label"),
1141
+ )
1142
+ response = self.client.post(url)
1143
+
1144
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
1145
+
1146
+ def test_download_invalid_document_type(self):
1147
+ shipment = self.create_purchased_shipment()
1148
+
1149
+ url = reverse(
1150
+ "karrio.server.manager:shipment-document-download",
1151
+ kwargs=dict(pk=shipment["id"], doc="invalid"),
1152
+ )
1153
+ response = self.client.post(url)
1154
+
1155
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
1156
+
1157
+ def test_download_shipment_not_found(self):
1158
+ url = reverse(
1159
+ "karrio.server.manager:shipment-document-download",
1160
+ kwargs=dict(pk="shp_non_existent_id", doc="label"),
1161
+ )
1162
+ response = self.client.post(url)
1163
+
1164
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
1165
+
1166
+
1167
+ class TestShipmentCancelIdempotent(APITestCase):
1168
+ """Test shipment cancel idempotency."""
1169
+
1170
+ def test_cancel_already_cancelled_shipment_returns_202(self):
1171
+ # Create a shipment via API
1172
+ url = reverse("karrio.server.manager:shipment-list")
1173
+
1174
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
1175
+ mock.return_value = RETURNED_RATES_VALUE
1176
+ response = self.client.post(url, SHIPMENT_DATA)
1177
+ shipment = json.loads(response.content)
1178
+
1179
+ # Cancel the shipment first time
1180
+ cancel_url = reverse(
1181
+ "karrio.server.manager:shipment-cancel",
1182
+ kwargs=dict(pk=shipment["id"]),
1183
+ )
1184
+ self.client.post(cancel_url)
1185
+
1186
+ # Cancel again - should return 202
1187
+ response = self.client.post(cancel_url)
1188
+ response_data = json.loads(response.content)
1189
+
1190
+ self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
1191
+ self.assertEqual(response_data["status"], "cancelled")
1192
+
1193
+
1194
+ LABEL_DOCUMENT_RESPONSE = {
1195
+ "category": "label",
1196
+ "format": "PDF",
1197
+ "base64": "==apodifjoefr",
1198
+ "url": ANY,
1199
+ }
@@ -17,6 +17,7 @@ import karrio.server.core.filters as filters
17
17
  import karrio.server.manager.models as models
18
18
  import karrio.server.manager.router as router
19
19
  import karrio.server.manager.serializers as serializers
20
+ from karrio.server.core.serializers import ShippingDocument
20
21
 
21
22
  ENDPOINT_ID = "$$$$&&" # This endpoint id is used to make operation ids unique make sure not to duplicate
22
23
  Manifests = serializers.PaginatedResult("ManifestList", serializers.Manifest)
@@ -115,7 +116,14 @@ class ManifestDoc(django_downloadview.VirtualDownloadView):
115
116
 
116
117
  query_params = req.GET.dict()
117
118
 
118
- self.manifest = models.Manifest.objects.get(pk=pk, manifest__isnull=False)
119
+ self.manifest = models.Manifest.objects.filter(pk=pk, manifest__isnull=False).first()
120
+
121
+ if self.manifest is None:
122
+ return response.Response(
123
+ {"errors": [{"message": f"Manifest '{pk}' not found or has no document"}]},
124
+ status=status.HTTP_404_NOT_FOUND,
125
+ )
126
+
119
127
  self.document = getattr(self.manifest, doc, None)
120
128
  self.name = f"{doc}_{self.manifest.id}.{format}"
121
129
 
@@ -134,6 +142,47 @@ class ManifestDoc(django_downloadview.VirtualDownloadView):
134
142
  return base.ContentFile(buffer.getvalue(), name=self.name)
135
143
 
136
144
 
145
+ class ManifestDocumentDownload(api.APIView):
146
+
147
+ @openapi.extend_schema(
148
+ tags=["Manifests"],
149
+ operation_id=f"{ENDPOINT_ID}document",
150
+ extensions={"x-operationId": "retrieveManifestDocument"},
151
+ summary="Retrieve a manifest document",
152
+ request=None,
153
+ responses={
154
+ 200: ShippingDocument(),
155
+ 404: serializers.ErrorResponse(),
156
+ 500: serializers.ErrorResponse(),
157
+ },
158
+ )
159
+ def post(self, req: request.Request, pk: str):
160
+ """
161
+ Retrieve a manifest document as base64 encoded content.
162
+ """
163
+ manifest = models.Manifest.access_by(req).filter(pk=pk, manifest__isnull=False).first()
164
+
165
+ if manifest is None:
166
+ return response.Response(
167
+ {"errors": [{"message": f"Manifest '{pk}' not found or has no document"}]},
168
+ status=status.HTTP_404_NOT_FOUND,
169
+ )
170
+
171
+ # Build the GET URL for the document
172
+ doc_url = f"/v1/manifests/{pk}/manifest.pdf"
173
+
174
+ return response.Response(
175
+ ShippingDocument(
176
+ {
177
+ "category": "manifest",
178
+ "format": "PDF",
179
+ "base64": manifest.manifest,
180
+ "url": doc_url,
181
+ }
182
+ ).data
183
+ )
184
+
185
+
137
186
  router.router.urls.append(
138
187
  urls.path(
139
188
  "manifests",
@@ -148,9 +197,16 @@ router.router.urls.append(
148
197
  name="manifest-details",
149
198
  )
150
199
  )
200
+ router.router.urls.append(
201
+ urls.path(
202
+ "manifests/<str:pk>/document",
203
+ ManifestDocumentDownload.as_view(),
204
+ name="manifest-document-download",
205
+ )
206
+ )
151
207
  router.router.urls.append(
152
208
  urls.re_path(
153
- r"^manifests/(?P<pk>\w+)/(?P<doc>[a-z0-9]+).(?P<format>[a-z0-9]+)",
209
+ r"^manifests/(?P<pk>\w+)/(?P<doc>[a-z0-9]+)\.(?P<format>[a-z0-9]+)",
154
210
  ManifestDoc.as_view(),
155
211
  name="manifest-docs",
156
212
  )
@@ -25,6 +25,7 @@ from karrio.server.manager.serializers import (
25
25
  ErrorMessages,
26
26
  Shipment,
27
27
  ShipmentData,
28
+ ShipmentStatus,
28
29
  buy_shipment_label,
29
30
  can_mutate_shipment,
30
31
  ShipmentSerializer,
@@ -32,6 +33,7 @@ from karrio.server.manager.serializers import (
32
33
  ShipmentUpdateData,
33
34
  ShipmentPurchaseData,
34
35
  ShipmentCancelSerializer,
36
+ ShippingDocument,
35
37
  )
36
38
 
37
39
  ENDPOINT_ID = "$$$$$" # This endpoint id is used to make operation ids unique make sure not to duplicate
@@ -165,6 +167,7 @@ class ShipmentCancel(APIView):
165
167
  request=None,
166
168
  responses={
167
169
  200: Shipment(),
170
+ 202: Shipment(),
168
171
  404: ErrorResponse(),
169
172
  400: ErrorResponse(),
170
173
  409: ErrorResponse(),
@@ -177,6 +180,11 @@ class ShipmentCancel(APIView):
177
180
  Void a shipment with the associated label.
178
181
  """
179
182
  shipment = models.Shipment.access_by(request).get(pk=pk)
183
+
184
+ # Return 202 if already cancelled (idempotent)
185
+ if shipment.status == ShipmentStatus.cancelled.value:
186
+ return Response(Shipment(shipment).data, status=status.HTTP_202_ACCEPTED)
187
+
180
188
  can_mutate_shipment(shipment, delete=True)
181
189
 
182
190
  update = ShipmentCancelSerializer.map(shipment, context=request).save().instance
@@ -275,7 +283,14 @@ class ShipmentDocs(VirtualDownloadView):
275
283
 
276
284
  query_params = request.GET.dict()
277
285
 
278
- self.shipment = models.Shipment.objects.get(pk=pk, label__isnull=False)
286
+ self.shipment = models.Shipment.objects.filter(pk=pk, label__isnull=False).first()
287
+
288
+ if self.shipment is None:
289
+ return Response(
290
+ {"errors": [{"message": f"Shipment '{pk}' not found or has no label"}]},
291
+ status=status.HTTP_404_NOT_FOUND,
292
+ )
293
+
279
294
  self.document = getattr(self.shipment, doc, None)
280
295
  self.name = f"{doc}_{self.shipment.tracking_number}.{format}"
281
296
 
@@ -314,6 +329,64 @@ class ShipmentDocs(VirtualDownloadView):
314
329
  return ContentFile(buffer.getvalue(), name=self.name)
315
330
 
316
331
 
332
+ class ShipmentDocumentDownload(APIView):
333
+ throttle_scope = "carrier_request"
334
+
335
+ @openapi.extend_schema(
336
+ tags=["Shipments"],
337
+ operation_id=f"{ENDPOINT_ID}document",
338
+ extensions={"x-operationId": "retrieveShipmentDocument"},
339
+ summary="Retrieve a shipment document",
340
+ request=None,
341
+ responses={
342
+ 200: ShippingDocument(),
343
+ 404: ErrorResponse(),
344
+ 500: ErrorResponse(),
345
+ },
346
+ )
347
+ def post(self, request: Request, pk: str, doc: str = "label"):
348
+ """
349
+ Retrieve a shipment document (label or invoice) as base64 encoded content.
350
+ """
351
+ if doc not in ["label", "invoice"]:
352
+ return Response(
353
+ {"errors": [{"message": f"Invalid document type: {doc}"}]},
354
+ status=status.HTTP_400_BAD_REQUEST,
355
+ )
356
+
357
+ # Build filter based on document type
358
+ filter_kwargs = {"pk": pk, f"{doc}__isnull": False}
359
+ shipment = models.Shipment.access_by(request).filter(**filter_kwargs).first()
360
+
361
+ if shipment is None:
362
+ return Response(
363
+ {"errors": [{"message": f"Shipment '{pk}' not found or has no {doc}"}]},
364
+ status=status.HTTP_404_NOT_FOUND,
365
+ )
366
+
367
+ document = getattr(shipment, doc)
368
+
369
+ # Determine format based on label_type for label, always PDF for invoice
370
+ if doc == "label":
371
+ doc_format = shipment.label_type or "PDF"
372
+ else:
373
+ doc_format = "PDF"
374
+
375
+ # Build the GET URL for the document
376
+ doc_url = f"/v1/shipments/{pk}/{doc}.{doc_format.lower()}"
377
+
378
+ return Response(
379
+ ShippingDocument(
380
+ {
381
+ "category": doc,
382
+ "format": doc_format,
383
+ "base64": document,
384
+ "url": doc_url,
385
+ }
386
+ ).data
387
+ )
388
+
389
+
317
390
  router.urls.append(path("shipments", ShipmentList.as_view(), name="shipment-list"))
318
391
  router.urls.append(
319
392
  path("shipments/<str:pk>", ShipmentDetails.as_view(), name="shipment-details")
@@ -331,9 +404,16 @@ router.urls.append(
331
404
  name="shipment-purchase",
332
405
  )
333
406
  )
407
+ router.urls.append(
408
+ path(
409
+ "shipments/<str:pk>/documents/<str:doc>",
410
+ ShipmentDocumentDownload.as_view(),
411
+ name="shipment-document-download",
412
+ )
413
+ )
334
414
  router.urls.append(
335
415
  re_path(
336
- r"^shipments/(?P<pk>\w+)/(?P<doc>[a-z0-9]+).(?P<format>[a-z0-9]+)",
416
+ r"^shipments/(?P<pk>\w+)/(?P<doc>[a-z0-9]+)\.(?P<format>[a-z0-9]+)",
337
417
  ShipmentDocs.as_view(),
338
418
  name="shipment-docs",
339
419
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karrio_server_manager
3
- Version: 2025.5.2
3
+ Version: 2025.5.3
4
4
  Summary: Multi-carrier shipping API Shipments manager module
5
5
  Author-email: karrio <hello@karrio.io>
6
6
  License-Expression: LGPL-3.0
@@ -82,26 +82,27 @@ karrio/server/manager/serializers/manifest.py,sha256=mSneCk_7HMXpi64_7hggWvkR7Ma
82
82
  karrio/server/manager/serializers/parcel.py,sha256=733Bg26lVbEkoWtAVM5Qt2IRBS2QDuVxhG40Hiqh3bw,2621
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
- karrio/server/manager/serializers/shipment.py,sha256=N4mld4eIM1HQ6NdsQ7gDt73aDv4j0wHM4AAMfgChnMc,34919
85
+ karrio/server/manager/serializers/shipment.py,sha256=8joRuh_F6V6NwFXWekdhsn0eCSvubAAjUbgwbujns6Y,36674
86
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
90
- karrio/server/manager/tests/test_errors.py,sha256=QYsGLUtwMvrHeX1XSCpdteTKbug7-y1-Xgvbl96aN9g,3220
90
+ karrio/server/manager/tests/test_errors.py,sha256=x2-mSsXknHkE4V7TajEu8d3rpqV38T_xyAaYJU7xcGQ,3616
91
+ karrio/server/manager/tests/test_manifests.py,sha256=X35ZTXTFEM4Gxdjz598yiNNkOOKZGpILjHWRC0oM5U4,2764
91
92
  karrio/server/manager/tests/test_parcels.py,sha256=lVLBOsHzXgXQvYjHIUy5oiPvrMfxYpueVvvhtuhstWk,2559
92
93
  karrio/server/manager/tests/test_pickups.py,sha256=8jxddwTnBvBM9FOyWxW9TtZ-GOVYUje7HQ2EZjsbtD8,10681
93
- karrio/server/manager/tests/test_shipments.py,sha256=LBblskbeJyUvWtJdy5hZPvumuOT2PE__ikIK3YlvcnY,35914
94
+ karrio/server/manager/tests/test_shipments.py,sha256=wvF_ZBK3QDI1id51bVh9FARuLQR18jkxP7n9tub7loo,39455
94
95
  karrio/server/manager/tests/test_trackers.py,sha256=KvmWkplokNDZ0dzB16mFl0WcMJ0OYp_ErZeWJPGW_NA,7151
95
96
  karrio/server/manager/views/__init__.py,sha256=kDFUaORRQ3Xh0ZPm-Jk88Ss8dgGYM57iUFXb9TPMzh0,401
96
97
  karrio/server/manager/views/addresses.py,sha256=7YCAs2ZYgd1icYwMcGGWfX7A7vZEL4BEAbU4eIxhiMY,4620
97
98
  karrio/server/manager/views/customs.py,sha256=-ZreiKyJ1xeLeNVG53nMfRQFeURduWr1QkDItdLPnE8,4875
98
99
  karrio/server/manager/views/documents.py,sha256=znW54qJ_k7WInIut5FBZFDT93CioozXTOYFKRSUTBhA,4005
99
- karrio/server/manager/views/manifests.py,sha256=_Dd83YxVJOgWhAhD745Kr4tcLWnKaU1dxnT5xB8opvk,5227
100
+ karrio/server/manager/views/manifests.py,sha256=bk-8XoGLVqgjDfpTZbTKjXW7r8DYNDp2ce2xGG73sbI,7012
100
101
  karrio/server/manager/views/parcels.py,sha256=hZY45rg6SrTWfQqyJ38MGKSor1yqgPUEVHtu16aG37g,4594
101
102
  karrio/server/manager/views/pickups.py,sha256=gmpxz9ot1OR-BP1qh-0MXU3kUJi1ht_74hfaLJzJ42w,5503
102
- karrio/server/manager/views/shipments.py,sha256=TqLpBH5Jf-rI3enJwvNptRwGzfo7co9R1VSP_oqhB3o,10419
103
+ karrio/server/manager/views/shipments.py,sha256=YCP0A8U-wFFwMgEfMZ3egWb4PwJ-aPLijILjwtH7aHo,12998
103
104
  karrio/server/manager/views/trackers.py,sha256=3oGn2qDpHgk8GZvuz-Cb93Fc0j_h_HbXQR692Zhfiok,12363
104
- karrio_server_manager-2025.5.2.dist-info/METADATA,sha256=WZxA5Br3FwwK7PO0Au-FKvQPHFPUcXevyuoaK596rWQ,730
105
- karrio_server_manager-2025.5.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
106
- karrio_server_manager-2025.5.2.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
107
- karrio_server_manager-2025.5.2.dist-info/RECORD,,
105
+ karrio_server_manager-2025.5.3.dist-info/METADATA,sha256=LpqgZqS3merjsArSi0Hz_yZKBjNSksZ40YXNMhe5IVY,730
106
+ karrio_server_manager-2025.5.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
107
+ karrio_server_manager-2025.5.3.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
108
+ karrio_server_manager-2025.5.3.dist-info/RECORD,,