karrio-server-proxy 2025.5rc1__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.
@@ -0,0 +1,337 @@
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 ShipmentDetails, ConfirmationDetails, Message
6
+ from karrio.server.core.tests import APITestCase
7
+
8
+
9
+ class TestShipping(APITestCase):
10
+ def test_shipping_request(self):
11
+ url = reverse("karrio.server.proxy:shipping-request")
12
+ data = SHIPPING_DATA
13
+
14
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
15
+ mock.return_value = RETURNED_VALUE
16
+ response = self.client.post(url, data)
17
+ response_data = json.loads(response.content)
18
+
19
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
20
+ self.assertDictEqual(response_data, SHIPPING_RESPONSE)
21
+
22
+ def test_shipping_cancel(self):
23
+ url = reverse(
24
+ "karrio.server.proxy:shipping-cancel",
25
+ kwargs=dict(carrier_name="canadapost"),
26
+ )
27
+ data = SHIPPING_CANCEL_DATA
28
+
29
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
30
+ mock.return_value = RETURNED_SUCCESS_CANCEL_VALUE
31
+ response = self.client.post(f"{url}", data)
32
+ response_data = json.loads(response.content)
33
+
34
+ self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
35
+ self.assertDictEqual(response_data, SHIPPING_CANCEL_SUCCESS_RESPONSE)
36
+
37
+ def test_shipping_failed_cancel(self):
38
+ url = reverse(
39
+ "karrio.server.proxy:shipping-cancel",
40
+ kwargs=dict(carrier_name="canadapost"),
41
+ )
42
+ data = SHIPPING_CANCEL_DATA
43
+
44
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
45
+ mock.return_value = RETURNED_FAILED_CANCEL_VALUE
46
+ response = self.client.post(f"{url}", data)
47
+ response_data = json.loads(response.content)
48
+
49
+ self.assertEqual(response.status_code, status.HTTP_424_FAILED_DEPENDENCY)
50
+ self.assertDictEqual(response_data, SHIPPING_CANCEL_FAILED_RESPONSE)
51
+
52
+
53
+ SHIPPING_DATA = {
54
+ "selected_rate_id": "prx_a9b96e5a82f644b0921bfed3190b4d6c",
55
+ "options": {},
56
+ "recipient": {
57
+ "address_line1": "125 Church St",
58
+ "person_name": "John Doe",
59
+ "company_name": "A corp.",
60
+ "phone_number": "514 000 0000",
61
+ "city": "Moncton",
62
+ "country_code": "CA",
63
+ "postal_code": "E1C4Z8",
64
+ "residential": False,
65
+ "state_code": "NB",
66
+ "validate_location": False,
67
+ "validation": None,
68
+ },
69
+ "shipper": {
70
+ "address_line1": "5840 Oak St",
71
+ "person_name": "Jane Doe",
72
+ "company_name": "B corp.",
73
+ "phone_number": "514 000 0000",
74
+ "city": "Vancouver",
75
+ "country_code": "CA",
76
+ "postal_code": "v6m2V9",
77
+ "residential": False,
78
+ "state_code": "BC",
79
+ "validate_location": False,
80
+ "validation": None,
81
+ },
82
+ "parcels": [
83
+ {
84
+ "weight": 1,
85
+ "weight_unit": "KG",
86
+ "package_preset": "canadapost_corrugated_small_box",
87
+ }
88
+ ],
89
+ "rates": [
90
+ {
91
+ "base_charge": 101.83,
92
+ "carrier_id": "canadapost",
93
+ "carrier_name": "canadapost",
94
+ "currency": "CAD",
95
+ "estimated_delivery": ANY,
96
+ "discount": -9.04,
97
+ "duties_and_taxes": 13.92,
98
+ "estimated_delivery": "2020-06-22",
99
+ "extra_charges": [
100
+ {"amount": 2.7, "currency": "CAD", "name": "Fuel surcharge"},
101
+ {"amount": -11.74, "currency": "CAD", "name": "SMB Savings"},
102
+ ],
103
+ "id": "prx_a9b96e5a82f644b0921bfed3190b4d6c",
104
+ "service": "canadapost_priority",
105
+ "total_charge": 106.71,
106
+ "test_mode": True,
107
+ },
108
+ {
109
+ "base_charge": 27.36,
110
+ "carrier_id": "canadapost",
111
+ "carrier_name": "canadapost",
112
+ "currency": "CAD",
113
+ "estimated_delivery": None,
114
+ "discount": -3.06,
115
+ "duties_and_taxes": 3.65,
116
+ "estimated_delivery": "2020-07-02",
117
+ "extra_charges": [
118
+ {"amount": 0.71, "currency": "CAD", "name": "Fuel surcharge"},
119
+ {"amount": -3.77, "currency": "CAD", "name": "SMB Savings"},
120
+ ],
121
+ "id": "prx_9290e4a2c8e34c8d8c73ab990b029f3d",
122
+ "service": "canadapost_regular_parcel",
123
+ "total_charge": 27.95,
124
+ "test_mode": True,
125
+ },
126
+ ],
127
+ "payment": {"currency": "CAD", "paid_by": "sender"},
128
+ }
129
+
130
+ SHIPPING_CANCEL_DATA = {"shipment_identifier": "123456789012"}
131
+
132
+ RETURNED_VALUE = [
133
+ ShipmentDetails(
134
+ carrier_name="canadapost",
135
+ carrier_id="canadapost",
136
+ tracking_number="123456789012",
137
+ shipment_identifier="123456789012",
138
+ docs=dict(label="==apodifjoefr"),
139
+ ),
140
+ [],
141
+ ]
142
+
143
+ RETURNED_SUCCESS_CANCEL_VALUE = [
144
+ ConfirmationDetails(
145
+ carrier_name="canadapost",
146
+ carrier_id="canadapost",
147
+ success=True,
148
+ operation="Cancel Shipment",
149
+ ),
150
+ [],
151
+ ]
152
+
153
+ RETURNED_FAILED_CANCEL_VALUE = [
154
+ None,
155
+ [
156
+ Message(
157
+ carrier_name="canadapost",
158
+ carrier_id="canadapost",
159
+ message="Not Found",
160
+ code="404",
161
+ )
162
+ ],
163
+ ]
164
+
165
+ SHIPPING_RESPONSE = {
166
+ "id": ANY,
167
+ "object_type": "shipment",
168
+ "tracking_url": "/v1/proxy/tracking/canadapost/123456789012",
169
+ "shipper": {
170
+ "id": None,
171
+ "postal_code": "V6M2V9",
172
+ "city": "Vancouver",
173
+ "federal_tax_id": None,
174
+ "state_tax_id": None,
175
+ "person_name": "Jane Doe",
176
+ "company_name": "B corp.",
177
+ "country_code": "CA",
178
+ "email": None,
179
+ "phone_number": "+1 514-000-0000",
180
+ "state_code": "BC",
181
+ "street_number": None,
182
+ "residential": False,
183
+ "address_line1": "5840 Oak St",
184
+ "address_line2": "",
185
+ "validate_location": False,
186
+ "object_type": "address",
187
+ "validation": None,
188
+ },
189
+ "recipient": {
190
+ "id": None,
191
+ "postal_code": "E1C4Z8",
192
+ "city": "Moncton",
193
+ "federal_tax_id": None,
194
+ "state_tax_id": None,
195
+ "person_name": "John Doe",
196
+ "company_name": "A corp.",
197
+ "country_code": "CA",
198
+ "email": None,
199
+ "phone_number": "+1 514-000-0000",
200
+ "state_code": "NB",
201
+ "street_number": None,
202
+ "residential": False,
203
+ "address_line1": "125 Church St",
204
+ "address_line2": "",
205
+ "validate_location": False,
206
+ "object_type": "address",
207
+ "validation": None,
208
+ },
209
+ "parcels": [
210
+ {
211
+ "id": None,
212
+ "weight": 1.0,
213
+ "width": 42.0,
214
+ "height": 32.0,
215
+ "length": 32.0,
216
+ "packaging_type": None,
217
+ "package_preset": "canadapost_corrugated_small_box",
218
+ "description": None,
219
+ "content": None,
220
+ "is_document": False,
221
+ "weight_unit": "KG",
222
+ "dimension_unit": "CM",
223
+ "items": [],
224
+ "freight_class": None,
225
+ "reference_number": "123456789012",
226
+ "object_type": "parcel",
227
+ "options": {},
228
+ }
229
+ ],
230
+ "services": [],
231
+ "options": {"shipment_date": ANY, "shipping_date": ANY},
232
+ "payment": {"paid_by": "sender", "currency": "CAD", "account_number": None},
233
+ "billing_address": None,
234
+ "customs": None,
235
+ "rates": [
236
+ {
237
+ "id": ANY,
238
+ "object_type": "rate",
239
+ "carrier_name": "canadapost",
240
+ "carrier_id": "canadapost",
241
+ "currency": "CAD",
242
+ "estimated_delivery": ANY,
243
+ "service": "canadapost_priority",
244
+ "total_charge": 106.71,
245
+ "transit_days": None,
246
+ "extra_charges": [
247
+ {"name": "Fuel surcharge", "amount": 2.7, "currency": "CAD"},
248
+ {"name": "SMB Savings", "amount": -11.74, "currency": "CAD"},
249
+ ],
250
+ "meta": None,
251
+ "test_mode": True,
252
+ },
253
+ {
254
+ "id": ANY,
255
+ "object_type": "rate",
256
+ "carrier_name": "canadapost",
257
+ "carrier_id": "canadapost",
258
+ "currency": "CAD",
259
+ "estimated_delivery": ANY,
260
+ "service": "canadapost_regular_parcel",
261
+ "total_charge": 27.95,
262
+ "transit_days": None,
263
+ "extra_charges": [
264
+ {"name": "Fuel surcharge", "amount": 0.71, "currency": "CAD"},
265
+ {"name": "SMB Savings", "amount": -3.77, "currency": "CAD"},
266
+ ],
267
+ "meta": None,
268
+ "test_mode": True,
269
+ },
270
+ ],
271
+ "reference": "",
272
+ "return_address": None,
273
+ "label_type": "PDF",
274
+ "carrier_ids": [],
275
+ "tracker_id": None,
276
+ "created_at": ANY,
277
+ "metadata": {},
278
+ "messages": [],
279
+ "status": "purchased",
280
+ "carrier_name": "canadapost",
281
+ "carrier_id": "canadapost",
282
+ "tracking_number": "123456789012",
283
+ "shipment_identifier": "123456789012",
284
+ "selected_rate": {
285
+ "id": ANY,
286
+ "object_type": "rate",
287
+ "carrier_name": "canadapost",
288
+ "carrier_id": "canadapost",
289
+ "currency": "CAD",
290
+ "estimated_delivery": ANY,
291
+ "service": "canadapost_priority",
292
+ "total_charge": 106.71,
293
+ "transit_days": None,
294
+ "extra_charges": [
295
+ {"name": "Fuel surcharge", "amount": 2.7, "currency": "CAD"},
296
+ {"name": "SMB Savings", "amount": -11.74, "currency": "CAD"},
297
+ ],
298
+ "meta": {
299
+ "ext": "canadapost",
300
+ "carrier": "canadapost",
301
+ "rate_provider": "canadapost",
302
+ "service_name": "CANADAPOST PRIORITY",
303
+ },
304
+ "test_mode": True,
305
+ },
306
+ "docs": {"label": "==apodifjoefr", "invoice": None},
307
+ "meta": {
308
+ "ext": "canadapost",
309
+ "carrier": "canadapost",
310
+ "rate_provider": "canadapost",
311
+ "service_name": "CANADAPOST PRIORITY",
312
+ },
313
+ "service": "canadapost_priority",
314
+ "selected_rate_id": ANY,
315
+ "test_mode": True,
316
+ }
317
+
318
+ SHIPPING_CANCEL_SUCCESS_RESPONSE = {
319
+ "messages": [],
320
+ "confirmation": {
321
+ "carrier_name": "canadapost",
322
+ "carrier_id": "canadapost",
323
+ "operation": "Cancel Shipment",
324
+ "success": True,
325
+ },
326
+ }
327
+
328
+ SHIPPING_CANCEL_FAILED_RESPONSE = {
329
+ "messages": [
330
+ {
331
+ "carrier_id": "canadapost",
332
+ "carrier_name": "canadapost",
333
+ "code": "404",
334
+ "message": "Not Found",
335
+ }
336
+ ]
337
+ }
@@ -0,0 +1,92 @@
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 TrackingDetails, TrackingEvent
6
+ from karrio.server.core.tests import APITestCase
7
+
8
+
9
+ class TestTracking(APITestCase):
10
+ def test_tracking_shipment(self):
11
+ url = reverse("karrio.server.proxy:get-tracking")
12
+ data = dict(tracking_number="1Z12345E6205277936", carrier_name="ups")
13
+
14
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
15
+ mock.return_value = RETURNED_VALUE
16
+ response = self.client.post(f"{url}", data)
17
+ response_data = json.loads(response.content)
18
+
19
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
20
+ self.assertDictEqual(response_data, TRACKING_RESPONSE)
21
+
22
+
23
+ RETURNED_VALUE = [
24
+ [
25
+ TrackingDetails(
26
+ carrier_id="ups_package",
27
+ carrier_name="ups",
28
+ tracking_number="1Z12345E6205277936",
29
+ events=[
30
+ TrackingEvent(
31
+ code="KB",
32
+ date="2010-08-30",
33
+ description="UPS INTERNAL ACTIVITY CODE",
34
+ location="BONN",
35
+ time="10:39",
36
+ )
37
+ ],
38
+ )
39
+ ],
40
+ [],
41
+ ]
42
+
43
+ TRACKING_RESPONSE = {
44
+ "messages": [],
45
+ "tracking": {
46
+ "id": ANY,
47
+ "carrier_name": "ups",
48
+ "carrier_id": "ups_package",
49
+ "tracking_number": "1Z12345E6205277936",
50
+ "images": None,
51
+ "info": {
52
+ "carrier_tracking_link": "https://www.ups.com/track?loc=en_US&requester=QUIC&tracknum=1Z12345E6205277936/trackdetails",
53
+ "customer_name": None,
54
+ "expected_delivery": None,
55
+ "note": None,
56
+ "order_date": None,
57
+ "order_id": None,
58
+ "package_weight": None,
59
+ "package_weight_unit": None,
60
+ "shipment_package_count": None,
61
+ "shipment_pickup_date": None,
62
+ "shipment_delivery_date": None,
63
+ "shipment_service": None,
64
+ "shipment_origin_country": None,
65
+ "shipment_origin_postal_code": None,
66
+ "shipment_destination_country": None,
67
+ "shipment_destination_postal_code": None,
68
+ "shipping_date": None,
69
+ "signed_by": None,
70
+ "source": "api",
71
+ },
72
+ "events": [
73
+ {
74
+ "date": "2010-08-30",
75
+ "description": "UPS INTERNAL ACTIVITY CODE",
76
+ "location": "BONN",
77
+ "code": "KB",
78
+ "time": "10:39",
79
+ "latitude": None,
80
+ "longitude": None,
81
+ }
82
+ ],
83
+ "delivered": None,
84
+ "test_mode": True,
85
+ "status": "in_transit",
86
+ "estimated_delivery": ANY,
87
+ "meta": {"ext": "ups", "carrier": "ups"},
88
+ "object_type": "tracker",
89
+ "metadata": {},
90
+ "messages": [],
91
+ },
92
+ }
@@ -0,0 +1,10 @@
1
+ """
2
+ karrio server proxy module urls
3
+ """
4
+ from django.urls import include, path
5
+ from karrio.server.proxy.views import router
6
+
7
+ app_name = 'karrio.server.proxy'
8
+ urlpatterns = [
9
+ path('v1/', include(router.urls)),
10
+ ]
@@ -0,0 +1,6 @@
1
+ import karrio.server.proxy.views.tracking
2
+ import karrio.server.proxy.views.rating
3
+ import karrio.server.proxy.views.shipping
4
+ import karrio.server.proxy.views.pickup
5
+ import karrio.server.proxy.views.manifest
6
+ from karrio.server.proxy.router import router
@@ -0,0 +1,55 @@
1
+ import logging
2
+ import django.urls as urls
3
+ import rest_framework.status as status
4
+ import rest_framework.request as request
5
+ import rest_framework.response as response
6
+
7
+ import karrio.server.openapi as openapi
8
+ import karrio.server.core.views.api as api
9
+ import karrio.server.proxy.router as router
10
+ import karrio.server.core.gateway as gateway
11
+ import karrio.server.core.serializers as serializers
12
+
13
+ logger = logging.getLogger(__name__)
14
+ ENDPOINT_ID = "@@@$" # This endpoint id is used to make operation ids unique make sure not to duplicate
15
+
16
+ DESCRIPTION = """
17
+ Some carriers require shipment manifests to be created for pickups and dropoff.
18
+ Creating a manifest for a shipment also kicks off billing as a commitment or confirmation of the shipment.
19
+ """
20
+
21
+
22
+ class ManifestingAPI(api.APIView):
23
+ throttle_scope = "carrier_request"
24
+
25
+ @openapi.extend_schema(
26
+ tags=["Proxy"],
27
+ operation_id=f"{ENDPOINT_ID}generate_manifest",
28
+ extensions={"x-operationId": "generateManifest"},
29
+ summary="Create a manifest",
30
+ description=DESCRIPTION,
31
+ responses={
32
+ 200: serializers.ManifestResponse(),
33
+ 400: serializers.ErrorResponse(),
34
+ 424: serializers.ErrorMessages(),
35
+ 500: serializers.ErrorResponse(),
36
+ },
37
+ request=serializers.ManifestRequest(),
38
+ )
39
+ def post(self, request: request.Request):
40
+ payload = serializers.ManifestRequest.map(data=request.data).data
41
+
42
+ manifest = gateway.Manifests.create(payload, context=request)
43
+
44
+ return response.Response(
45
+ serializers.ManifestResponse(manifest).data, status=status.HTTP_200_OK
46
+ )
47
+
48
+
49
+ router.router.urls.append(
50
+ urls.path(
51
+ "proxy/manifest",
52
+ ManifestingAPI.as_view(),
53
+ name="shipment-manifest",
54
+ )
55
+ )
@@ -0,0 +1,151 @@
1
+ import logging
2
+ from django.urls import path
3
+ from rest_framework import status
4
+ from rest_framework.response import Response
5
+ from rest_framework.request import Request
6
+
7
+ import karrio.server.openapi as openapi
8
+ import karrio.server.core.utils as utils
9
+ import karrio.server.core.dataunits as dataunits
10
+ import karrio.server.providers.models as providers
11
+ from karrio.server.proxy.router import router
12
+ from karrio.server.core.gateway import Pickups
13
+ from karrio.server.core.views.api import APIView
14
+ from karrio.server.core.serializers import (
15
+ PickupCancelRequest,
16
+ PickupUpdateRequest,
17
+ OperationResponse,
18
+ PickupResponse,
19
+ PickupRequest,
20
+ ErrorResponse,
21
+ ErrorMessages,
22
+ )
23
+
24
+ logger = logging.getLogger(__name__)
25
+ ENDPOINT_ID = "@" # This endpoint id is used to make operation ids unique make sure not to duplicate
26
+
27
+
28
+ class PickupSchedule(APIView):
29
+ throttle_scope = "carrier_request"
30
+
31
+ @openapi.extend_schema(
32
+ tags=["Proxy"],
33
+ operation_id=f"{ENDPOINT_ID}schedule_pickup",
34
+ extensions={"x-operationId": "pickupSchedule"},
35
+ summary="Schedule a pickup",
36
+ request=PickupRequest(),
37
+ responses={
38
+ 201: PickupResponse(),
39
+ 400: ErrorResponse(),
40
+ 424: ErrorMessages(),
41
+ 500: ErrorResponse(),
42
+ },
43
+ parameters=[
44
+ openapi.OpenApiParameter(
45
+ "carrier_name",
46
+ location=openapi.OpenApiParameter.PATH,
47
+ type=openapi.OpenApiTypes.STR,
48
+ enum=dataunits.CARRIER_NAMES,
49
+ ),
50
+ ],
51
+ )
52
+ def post(self, request: Request, carrier_name: str):
53
+ """
54
+ Schedule one or many parcels pickup
55
+ """
56
+ payload = PickupRequest.map(data=request.data).data
57
+
58
+ response = Pickups.schedule(payload, context=request, carrier_name=carrier_name)
59
+
60
+ return Response(PickupResponse(response).data, status=status.HTTP_201_CREATED)
61
+
62
+
63
+ class PickupUpdate(APIView):
64
+ throttle_scope = "carrier_request"
65
+
66
+ @openapi.extend_schema(
67
+ tags=["Proxy"],
68
+ operation_id=f"{ENDPOINT_ID}update_pickup",
69
+ extensions={"x-operationId": "pickupUpdate"},
70
+ summary="Update a pickup",
71
+ request=PickupUpdateRequest(),
72
+ responses={
73
+ 200: PickupResponse(),
74
+ 400: ErrorResponse(),
75
+ 424: ErrorMessages(),
76
+ 500: ErrorResponse(),
77
+ },
78
+ parameters=[
79
+ openapi.OpenApiParameter(
80
+ "carrier_name",
81
+ location=openapi.OpenApiParameter.PATH,
82
+ type=openapi.OpenApiTypes.STR,
83
+ enum=dataunits.CARRIER_NAMES,
84
+ ),
85
+ ],
86
+ )
87
+ def post(self, request: Request, carrier_name: str):
88
+ """
89
+ Modify a scheduled pickup
90
+ """
91
+ payload = PickupUpdateRequest.map(data=request.data).data
92
+
93
+ response = Pickups.update(payload, context=request, carrier_name=carrier_name)
94
+
95
+ return Response(PickupResponse(response).data, status=status.HTTP_200_OK)
96
+
97
+
98
+ class PickupCancel(APIView):
99
+ throttle_scope = "carrier_request"
100
+
101
+ @openapi.extend_schema(
102
+ tags=["Proxy"],
103
+ operation_id=f"{ENDPOINT_ID}cancel_pickup",
104
+ extensions={"x-operationId": "pickupCancel"},
105
+ summary="Cancel a pickup",
106
+ request=PickupCancelRequest(),
107
+ responses={
108
+ 200: OperationResponse(),
109
+ 400: ErrorResponse(),
110
+ 424: ErrorMessages(),
111
+ 500: ErrorResponse(),
112
+ },
113
+ parameters=[
114
+ openapi.OpenApiParameter(
115
+ "carrier_name",
116
+ location=openapi.OpenApiParameter.PATH,
117
+ type=openapi.OpenApiTypes.STR,
118
+ enum=dataunits.CARRIER_NAMES,
119
+ ),
120
+ ],
121
+ )
122
+ def post(self, request: Request, carrier_name: str):
123
+ """
124
+ Cancel a pickup previously scheduled
125
+ """
126
+ payload = PickupCancelRequest.map(data=request.data).data
127
+
128
+ response = Pickups.cancel(payload, context=request, carrier_name=carrier_name)
129
+
130
+ return Response(OperationResponse(response).data, status=status.HTTP_200_OK)
131
+
132
+
133
+ router.urls.append(
134
+ path(
135
+ "proxy/pickups/<carrier_name>", PickupSchedule.as_view(), name="pickup-schedule"
136
+ )
137
+ )
138
+ router.urls.append(
139
+ path(
140
+ "proxy/pickups/<carrier_name>/update",
141
+ PickupUpdate.as_view(),
142
+ name="pickup-details",
143
+ )
144
+ )
145
+ router.urls.append(
146
+ path(
147
+ "proxy/pickups/<carrier_name>/cancel",
148
+ PickupCancel.as_view(),
149
+ name="pickup-cancel",
150
+ )
151
+ )