karrio-canadapost 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.
- karrio/mappers/canadapost/__init__.py +3 -0
- karrio/mappers/canadapost/mapper.py +88 -0
- karrio/mappers/canadapost/proxy.py +373 -0
- karrio/mappers/canadapost/settings.py +23 -0
- karrio/plugins/canadapost/__init__.py +23 -0
- karrio/providers/canadapost/__init__.py +25 -0
- karrio/providers/canadapost/error.py +42 -0
- karrio/providers/canadapost/manifest.py +127 -0
- karrio/providers/canadapost/pickup/__init__.py +3 -0
- karrio/providers/canadapost/pickup/cancel.py +33 -0
- karrio/providers/canadapost/pickup/create.py +217 -0
- karrio/providers/canadapost/pickup/update.py +55 -0
- karrio/providers/canadapost/rate.py +192 -0
- karrio/providers/canadapost/shipment/__init__.py +8 -0
- karrio/providers/canadapost/shipment/cancel.py +53 -0
- karrio/providers/canadapost/shipment/create.py +308 -0
- karrio/providers/canadapost/tracking.py +75 -0
- karrio/providers/canadapost/units.py +285 -0
- karrio/providers/canadapost/utils.py +92 -0
- karrio/schemas/canadapost/__init__.py +0 -0
- karrio/schemas/canadapost/authreturn.py +3389 -0
- karrio/schemas/canadapost/common.py +2037 -0
- karrio/schemas/canadapost/customerinfo.py +2307 -0
- karrio/schemas/canadapost/discovery.py +3016 -0
- karrio/schemas/canadapost/manifest.py +3704 -0
- karrio/schemas/canadapost/merchantregistration.py +1498 -0
- karrio/schemas/canadapost/messages.py +1431 -0
- karrio/schemas/canadapost/ncshipment.py +7231 -0
- karrio/schemas/canadapost/openreturn.py +2438 -0
- karrio/schemas/canadapost/pickup.py +1407 -0
- karrio/schemas/canadapost/pickuprequest.py +6794 -0
- karrio/schemas/canadapost/postoffice.py +2240 -0
- karrio/schemas/canadapost/rating.py +5308 -0
- karrio/schemas/canadapost/serviceinfo.py +1505 -0
- karrio/schemas/canadapost/shipment.py +9982 -0
- karrio/schemas/canadapost/track.py +3100 -0
- karrio_canadapost-2025.5rc1.dist-info/METADATA +44 -0
- karrio_canadapost-2025.5rc1.dist-info/RECORD +41 -0
- karrio_canadapost-2025.5rc1.dist-info/WHEEL +5 -0
- karrio_canadapost-2025.5rc1.dist-info/entry_points.txt +2 -0
- karrio_canadapost-2025.5rc1.dist-info/top_level.txt +3 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
"""Karrio Canada Post client mapper."""
|
2
|
+
|
3
|
+
import typing
|
4
|
+
import karrio.lib as lib
|
5
|
+
import karrio.api.mapper as mapper
|
6
|
+
import karrio.core.models as models
|
7
|
+
import karrio.providers.canadapost as provider
|
8
|
+
import karrio.mappers.canadapost.settings as provider_settings
|
9
|
+
|
10
|
+
|
11
|
+
class Mapper(mapper.Mapper):
|
12
|
+
settings: provider_settings.Settings
|
13
|
+
|
14
|
+
def create_rate_request(self, payload: models.RateRequest) -> lib.Serializable:
|
15
|
+
return provider.rate_request(payload, self.settings)
|
16
|
+
|
17
|
+
def create_tracking_request(
|
18
|
+
self, payload: models.TrackingRequest
|
19
|
+
) -> lib.Serializable:
|
20
|
+
return provider.tracking_request(payload, self.settings)
|
21
|
+
|
22
|
+
def create_shipment_request(
|
23
|
+
self, payload: models.ShipmentRequest
|
24
|
+
) -> lib.Serializable:
|
25
|
+
return provider.shipment_request(payload, self.settings)
|
26
|
+
|
27
|
+
def create_cancel_shipment_request(
|
28
|
+
self, payload: models.ShipmentCancelRequest
|
29
|
+
) -> lib.Serializable[str]:
|
30
|
+
return provider.shipment_cancel_request(payload, self.settings)
|
31
|
+
|
32
|
+
def create_manifest_request(
|
33
|
+
self, payload: models.ManifestRequest
|
34
|
+
) -> lib.Serializable:
|
35
|
+
return provider.manifest_request(payload, self.settings)
|
36
|
+
|
37
|
+
def create_pickup_request(self, payload: models.PickupRequest) -> lib.Serializable:
|
38
|
+
return provider.pickup_request(payload, self.settings)
|
39
|
+
|
40
|
+
def create_pickup_update_request(
|
41
|
+
self, payload: models.PickupUpdateRequest
|
42
|
+
) -> lib.Serializable:
|
43
|
+
return provider.pickup_update_request(payload, self.settings)
|
44
|
+
|
45
|
+
def create_cancel_pickup_request(
|
46
|
+
self, payload: models.PickupCancelRequest
|
47
|
+
) -> lib.Serializable:
|
48
|
+
return provider.pickup_cancel_request(payload, self.settings)
|
49
|
+
|
50
|
+
def parse_cancel_shipment_response(
|
51
|
+
self, response: lib.Deserializable[str]
|
52
|
+
) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
|
53
|
+
return provider.parse_shipment_cancel_response(response, self.settings)
|
54
|
+
|
55
|
+
def parse_rate_response(
|
56
|
+
self, response: lib.Deserializable[str]
|
57
|
+
) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
|
58
|
+
return provider.parse_rate_response(response, self.settings)
|
59
|
+
|
60
|
+
def parse_shipment_response(
|
61
|
+
self, response: lib.Deserializable[str]
|
62
|
+
) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
|
63
|
+
return provider.parse_shipment_response(response, self.settings)
|
64
|
+
|
65
|
+
def parse_tracking_response(
|
66
|
+
self, response: lib.Deserializable[str]
|
67
|
+
) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
|
68
|
+
return provider.parse_tracking_response(response, self.settings)
|
69
|
+
|
70
|
+
def parse_manifest_response(
|
71
|
+
self, response: lib.Deserializable[str]
|
72
|
+
) -> typing.Tuple[models.ManifestDetails, typing.List[models.Message]]:
|
73
|
+
return provider.parse_manifest_response(response, self.settings)
|
74
|
+
|
75
|
+
def parse_pickup_response(
|
76
|
+
self, response: lib.Deserializable[str]
|
77
|
+
) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
|
78
|
+
return provider.parse_pickup_response(response, self.settings)
|
79
|
+
|
80
|
+
def parse_pickup_update_response(
|
81
|
+
self, response: lib.Deserializable[str]
|
82
|
+
) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
|
83
|
+
return provider.parse_pickup_update_response(response, self.settings)
|
84
|
+
|
85
|
+
def parse_pickup_cancel_response(
|
86
|
+
self, response: lib.Deserializable[str]
|
87
|
+
) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
|
88
|
+
return provider.parse_pickup_cancel_response(response, self.settings)
|
@@ -0,0 +1,373 @@
|
|
1
|
+
import time
|
2
|
+
import typing
|
3
|
+
import karrio.lib as lib
|
4
|
+
import karrio.api.proxy as proxy
|
5
|
+
import karrio.providers.canadapost.utils as provider_utils
|
6
|
+
import karrio.mappers.canadapost.settings as provider_settings
|
7
|
+
|
8
|
+
|
9
|
+
class Proxy(proxy.Proxy):
|
10
|
+
settings: provider_settings.Settings
|
11
|
+
|
12
|
+
def get_rates(self, requests: lib.Serializable) -> lib.Deserializable:
|
13
|
+
responses = lib.run_asynchronously(
|
14
|
+
lambda data: lib.request(
|
15
|
+
url=f"{self.settings.server_url}/rs/ship/price",
|
16
|
+
trace=self.trace_as("xml"),
|
17
|
+
method="POST",
|
18
|
+
data=data,
|
19
|
+
headers={
|
20
|
+
"Content-Type": "application/vnd.cpc.ship.rate-v4+xml",
|
21
|
+
"Accept": "application/vnd.cpc.ship.rate-v4+xml",
|
22
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
23
|
+
"Accept-language": f"{self.settings.language}-CA",
|
24
|
+
},
|
25
|
+
),
|
26
|
+
requests.serialize(),
|
27
|
+
)
|
28
|
+
|
29
|
+
return lib.Deserializable(
|
30
|
+
responses,
|
31
|
+
lambda __: [lib.to_element(_) for _ in __],
|
32
|
+
)
|
33
|
+
|
34
|
+
def get_tracking(self, request: lib.Serializable) -> lib.Deserializable:
|
35
|
+
"""get_tracking make parallel request for each pin"""
|
36
|
+
_throttle = 0.0
|
37
|
+
|
38
|
+
def _track(tracking_pin: str) -> str:
|
39
|
+
nonlocal _throttle
|
40
|
+
time.sleep(_throttle)
|
41
|
+
_throttle += 0.025
|
42
|
+
|
43
|
+
return lib.request(
|
44
|
+
url=f"{self.settings.server_url}/vis/track/pin/{tracking_pin}/detail",
|
45
|
+
trace=self.trace_as("xml"),
|
46
|
+
method="GET",
|
47
|
+
headers={
|
48
|
+
"Accept": "application/vnd.cpc.track-v2+xml",
|
49
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
50
|
+
"Accept-language": f"{self.settings.language}-CA",
|
51
|
+
},
|
52
|
+
)
|
53
|
+
|
54
|
+
responses: typing.List[str] = lib.run_asynchronously(
|
55
|
+
_track,
|
56
|
+
request.serialize(),
|
57
|
+
)
|
58
|
+
|
59
|
+
return lib.Deserializable(responses, lib.to_element)
|
60
|
+
|
61
|
+
def create_shipment(self, requests: lib.Serializable) -> lib.Deserializable:
|
62
|
+
shipment_responses = lib.run_asynchronously(
|
63
|
+
lambda data: lib.request(
|
64
|
+
url=f"{self.settings.server_url}/rs/{self.settings.customer_number}/{self.settings.customer_number}/shipment",
|
65
|
+
data=data,
|
66
|
+
trace=self.trace_as("xml"),
|
67
|
+
method="POST",
|
68
|
+
headers={
|
69
|
+
"Content-Type": "application/vnd.cpc.shipment-v8+xml",
|
70
|
+
"Accept": "application/vnd.cpc.shipment-v8+xml",
|
71
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
72
|
+
"Accept-language": f"{self.settings.language}-CA",
|
73
|
+
},
|
74
|
+
),
|
75
|
+
requests.serialize(),
|
76
|
+
)
|
77
|
+
responses: typing.List[dict] = lib.run_asynchronously(
|
78
|
+
lambda data: dict(
|
79
|
+
shipment=data["shipment"],
|
80
|
+
label=(
|
81
|
+
lib.request(
|
82
|
+
decoder=lib.encode_base64,
|
83
|
+
url=data["href"],
|
84
|
+
method="GET",
|
85
|
+
headers={
|
86
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
87
|
+
"Accept": data["media"],
|
88
|
+
},
|
89
|
+
)
|
90
|
+
if data["href"] is not None
|
91
|
+
else None
|
92
|
+
),
|
93
|
+
),
|
94
|
+
[
|
95
|
+
{**provider_utils.parse_label_references(_), "shipment": _}
|
96
|
+
for _ in shipment_responses
|
97
|
+
],
|
98
|
+
)
|
99
|
+
|
100
|
+
return lib.Deserializable(
|
101
|
+
responses,
|
102
|
+
lambda __: [(lib.to_element(_["shipment"]), _["label"]) for _ in __],
|
103
|
+
requests.ctx,
|
104
|
+
)
|
105
|
+
|
106
|
+
def cancel_shipment(self, requests: lib.Serializable) -> lib.Deserializable:
|
107
|
+
# retrieve shipment infos to check if refund is necessary
|
108
|
+
infos: typing.List[typing.Tuple[str, str]] = lib.run_asynchronously(
|
109
|
+
lambda shipment_id: (
|
110
|
+
shipment_id,
|
111
|
+
lib.request(
|
112
|
+
url=f"{self.settings.server_url}/rs/{self.settings.customer_number}/{self.settings.customer_number}/shipment/{shipment_id}",
|
113
|
+
trace=self.trace_as("xml"),
|
114
|
+
method="GET",
|
115
|
+
headers={
|
116
|
+
"Content-Type": "application/vnd.cpc.shipment-v8+xml",
|
117
|
+
"Accept": "application/vnd.cpc.shipment-v8+xml",
|
118
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
119
|
+
"Accept-language": f"{self.settings.language}-CA",
|
120
|
+
},
|
121
|
+
),
|
122
|
+
),
|
123
|
+
requests.serialize(),
|
124
|
+
)
|
125
|
+
|
126
|
+
# make refund requests for submitted shipments
|
127
|
+
refunds: typing.List[typing.Tuple[str, str]] = lib.run_asynchronously(
|
128
|
+
lambda payload: (
|
129
|
+
payload["id"],
|
130
|
+
(
|
131
|
+
lib.request(
|
132
|
+
url=f"{self.settings.server_url}/rs/{self.settings.customer_number}/{self.settings.customer_number}/shipment/{payload['id']}/refund",
|
133
|
+
trace=self.trace_as("xml"),
|
134
|
+
data=payload["data"],
|
135
|
+
method="POST",
|
136
|
+
headers={
|
137
|
+
"Content-Type": "application/vnd.cpc.shipment-v8+xml",
|
138
|
+
"Accept": "application/vnd.cpc.shipment-v8+xml",
|
139
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
140
|
+
"Accept-language": f"{self.settings.language}-CA",
|
141
|
+
},
|
142
|
+
)
|
143
|
+
if payload["data"] is not None
|
144
|
+
else payload["info"]
|
145
|
+
),
|
146
|
+
),
|
147
|
+
[
|
148
|
+
dict(
|
149
|
+
id=_,
|
150
|
+
info=info,
|
151
|
+
data=provider_utils.parse_submitted_shipment(info, requests.ctx),
|
152
|
+
)
|
153
|
+
for _, info in infos
|
154
|
+
],
|
155
|
+
)
|
156
|
+
|
157
|
+
# make cancel requests for non-submitted shipments
|
158
|
+
responses: typing.List[typing.Tuple[str, str]] = lib.run_asynchronously(
|
159
|
+
lambda payload: (
|
160
|
+
payload["id"],
|
161
|
+
(
|
162
|
+
lib.request(
|
163
|
+
url=f"{self.settings.server_url}/rs/{self.settings.customer_number}/{self.settings.customer_number}/shipment/{payload['id']}",
|
164
|
+
trace=self.trace_as("xml"),
|
165
|
+
method="DELETE",
|
166
|
+
headers={
|
167
|
+
"Content-Type": "application/vnd.cpc.shipment-v8+xml",
|
168
|
+
"Accept": "application/vnd.cpc.shipment-v8+xml",
|
169
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
170
|
+
"Accept-language": f"{self.settings.language}-CA",
|
171
|
+
},
|
172
|
+
)
|
173
|
+
if payload["refunded"]
|
174
|
+
else payload["response"]
|
175
|
+
),
|
176
|
+
),
|
177
|
+
[
|
178
|
+
dict(
|
179
|
+
id=_,
|
180
|
+
response=response,
|
181
|
+
refunded=(
|
182
|
+
getattr(lib.to_element(response), "tag", None)
|
183
|
+
!= "shipment-refund-request-info"
|
184
|
+
),
|
185
|
+
)
|
186
|
+
for _, response in refunds
|
187
|
+
],
|
188
|
+
)
|
189
|
+
|
190
|
+
return lib.Deserializable(
|
191
|
+
responses, lambda ___: [(_, lib.to_element(__)) for _, __ in ___]
|
192
|
+
)
|
193
|
+
|
194
|
+
def schedule_pickup(self, request: lib.Serializable) -> lib.Deserializable:
|
195
|
+
def _availability(job: lib.Job) -> str:
|
196
|
+
return lib.request(
|
197
|
+
url=f"{self.settings.server_url}/ad/pickup/pickupavailability/{job.data}",
|
198
|
+
trace=self.trace_as("xml"),
|
199
|
+
method="GET",
|
200
|
+
headers={
|
201
|
+
"Accept": "application/vnd.cpc.pickup+xml",
|
202
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
203
|
+
"Accept-language": f"{self.settings.language}-CA",
|
204
|
+
},
|
205
|
+
)
|
206
|
+
|
207
|
+
def _create_pickup(job: lib.Job) -> str:
|
208
|
+
return lib.request(
|
209
|
+
url=f"{self.settings.server_url}/enab/{self.settings.customer_number}/pickuprequest",
|
210
|
+
data=job.data.serialize(),
|
211
|
+
trace=self.trace_as("xml"),
|
212
|
+
method="POST",
|
213
|
+
headers={
|
214
|
+
"Accept": "application/vnd.cpc.pickuprequest+xml",
|
215
|
+
"Content-Type": "application/vnd.cpc.pickuprequest+xml",
|
216
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
217
|
+
"Accept-language": f"{self.settings.language}-CA",
|
218
|
+
},
|
219
|
+
)
|
220
|
+
|
221
|
+
def _process(job: lib.Job):
|
222
|
+
if job.data is None:
|
223
|
+
return job.fallback
|
224
|
+
|
225
|
+
subprocess = {
|
226
|
+
"create_pickup": _create_pickup,
|
227
|
+
"availability": _availability,
|
228
|
+
}
|
229
|
+
if job.id not in subprocess:
|
230
|
+
raise lib.ShippingSDKError(f"Unknown pickup request job id: {job.id}")
|
231
|
+
|
232
|
+
return subprocess[job.id](job)
|
233
|
+
|
234
|
+
pipeline: lib.Pipeline = request.serialize()
|
235
|
+
responses = pipeline.apply(_process)
|
236
|
+
|
237
|
+
return lib.Deserializable(responses, lib.to_element)
|
238
|
+
|
239
|
+
def modify_pickup(self, request: lib.Serializable) -> lib.Deserializable:
|
240
|
+
def _get_pickup(job: lib.Job) -> str:
|
241
|
+
return lib.request(
|
242
|
+
url=f"{self.settings.server_url}{job.data.serialize()}",
|
243
|
+
trace=self.trace_as("xml"),
|
244
|
+
method="GET",
|
245
|
+
headers={
|
246
|
+
"Accept": "application/vnd.cpc.pickup+xml",
|
247
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
248
|
+
"Accept-language": f"{self.settings.language}-CA",
|
249
|
+
},
|
250
|
+
)
|
251
|
+
|
252
|
+
def _update_pickup(job: lib.Job) -> str:
|
253
|
+
payload = job.data.serialize()
|
254
|
+
return lib.request(
|
255
|
+
url=f"{self.settings.server_url}/enab/{self.settings.customer_number}/pickuprequest/{payload['pickuprequest']}",
|
256
|
+
data=payload["data"],
|
257
|
+
trace=self.trace_as("xml"),
|
258
|
+
method="PUT",
|
259
|
+
headers={
|
260
|
+
"Accept": "application/vnd.cpc.pickuprequest+xml",
|
261
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
262
|
+
"Accept-language": f"{self.settings.language}-CA",
|
263
|
+
},
|
264
|
+
)
|
265
|
+
|
266
|
+
def _process(job: lib.Job):
|
267
|
+
if job.data is None:
|
268
|
+
return job.fallback
|
269
|
+
|
270
|
+
subprocess = {
|
271
|
+
"update_pickup": _update_pickup,
|
272
|
+
"get_pickup": _get_pickup,
|
273
|
+
}
|
274
|
+
if job.id not in subprocess:
|
275
|
+
raise lib.ShippingSDKError(f"Unknown pickup request job id: {job.id}")
|
276
|
+
|
277
|
+
return subprocess[job.id](job)
|
278
|
+
|
279
|
+
pipeline: lib.Pipeline = request.serialize()
|
280
|
+
responses = pipeline.apply(_process)
|
281
|
+
|
282
|
+
return lib.Deserializable(responses, lib.to_element)
|
283
|
+
|
284
|
+
def cancel_pickup(self, request: lib.Serializable) -> lib.Deserializable:
|
285
|
+
pickuprequest = request.serialize()
|
286
|
+
response = lib.request(
|
287
|
+
url=f"{self.settings.server_url}/enab/{self.settings.customer_number}/pickuprequest/{pickuprequest}",
|
288
|
+
trace=self.trace_as("xml"),
|
289
|
+
method="DELETE",
|
290
|
+
headers={
|
291
|
+
"Accept": "application/vnd.cpc.pickuprequest+xml",
|
292
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
293
|
+
"Accept-language": f"{self.settings.language}-CA",
|
294
|
+
},
|
295
|
+
)
|
296
|
+
|
297
|
+
return lib.Deserializable(response or "<wrapper></wrapper>", lib.to_element)
|
298
|
+
|
299
|
+
def create_manifest(self, request: lib.Serializable) -> lib.Deserializable:
|
300
|
+
ctx = request.ctx.copy()
|
301
|
+
data = request.serialize()
|
302
|
+
|
303
|
+
if ctx["retrieve_shipments"]:
|
304
|
+
shipments = lib.run_asynchronously(
|
305
|
+
lambda pin: lib.request(
|
306
|
+
url=f"{self.settings.server_url}/{self.settings.customer_number}/{self.settings.customer_number}/shipment/{pin}/details",
|
307
|
+
trace=self.trace_as("xml"),
|
308
|
+
method="GET",
|
309
|
+
headers={
|
310
|
+
"Accept": "application/vnd.cpc.shipment-v8+xml",
|
311
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
312
|
+
"Accept-language": f"{self.settings.language}-CA",
|
313
|
+
},
|
314
|
+
),
|
315
|
+
ctx["shipment_identifiers"],
|
316
|
+
)
|
317
|
+
|
318
|
+
group_ids = [
|
319
|
+
_.text for _ in lib.find_element("group-id", lib.to_element(shipments))
|
320
|
+
]
|
321
|
+
ctx.update(group_ids=[*set([*ctx["group_ids"], *group_ids])])
|
322
|
+
|
323
|
+
response = lib.request(
|
324
|
+
url=f"{self.settings.server_url}/rs/{self.settings.customer_number}/{self.settings.customer_number}/manifest",
|
325
|
+
data=data.replace(
|
326
|
+
"<group-id>[GROUP_IDS]</group-id>",
|
327
|
+
"".join(f"<group-id>{_}</group-id>" for _ in ctx["group_ids"]),
|
328
|
+
),
|
329
|
+
trace=self.trace_as("xml"),
|
330
|
+
method="POST",
|
331
|
+
headers={
|
332
|
+
"Content-Type": "application/vnd.cpc.manifest-v8+xml",
|
333
|
+
"Accept": "application/vnd.cpc.manifest-v8+xml",
|
334
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
335
|
+
"Accept-language": f"{self.settings.language}-CA",
|
336
|
+
},
|
337
|
+
)
|
338
|
+
|
339
|
+
manifests = lib.run_asynchronously(
|
340
|
+
lambda link: lib.request(
|
341
|
+
url=link.get("href"),
|
342
|
+
trace=self.trace_as("xml"),
|
343
|
+
headers={
|
344
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
345
|
+
"Accept": (
|
346
|
+
lib.text(link.get("media-type"))
|
347
|
+
or "application/vnd.cpc.manifest-v8+xml"
|
348
|
+
),
|
349
|
+
},
|
350
|
+
),
|
351
|
+
lib.find_element("link", lib.to_element(response)),
|
352
|
+
)
|
353
|
+
|
354
|
+
if any(manifests):
|
355
|
+
ctx.update(
|
356
|
+
files=lib.run_asynchronously(
|
357
|
+
lambda link: lib.request(
|
358
|
+
decoder=lib.encode_base64,
|
359
|
+
url=link.get("href"),
|
360
|
+
headers={
|
361
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
362
|
+
"Accept": link.get("media-type"),
|
363
|
+
},
|
364
|
+
),
|
365
|
+
[
|
366
|
+
_
|
367
|
+
for _ in lib.find_element("link", lib.to_element(manifests))
|
368
|
+
if _.get("rel") == "artifact"
|
369
|
+
],
|
370
|
+
)
|
371
|
+
)
|
372
|
+
|
373
|
+
return lib.Deserializable(response, lib.to_element, ctx)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
"""Karrio Canada post client settings."""
|
2
|
+
|
3
|
+
import attr
|
4
|
+
import karrio.lib as lib
|
5
|
+
import karrio.providers.canadapost.utils as utils
|
6
|
+
|
7
|
+
|
8
|
+
@attr.s(auto_attribs=True)
|
9
|
+
class Settings(utils.Settings):
|
10
|
+
"""Canada post connection settings."""
|
11
|
+
|
12
|
+
username: str
|
13
|
+
password: str
|
14
|
+
customer_number: str = None
|
15
|
+
contract_id: str = None
|
16
|
+
language: utils.LanguageEnum = "en" # type: ignore
|
17
|
+
|
18
|
+
id: str = None
|
19
|
+
test_mode: bool = False
|
20
|
+
carrier_id: str = "canadapost"
|
21
|
+
account_country_code: str = "CA"
|
22
|
+
metadata: dict = {}
|
23
|
+
config: dict = {}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import karrio.core.metadata as metadata
|
2
|
+
import karrio.mappers.canadapost as mappers
|
3
|
+
import karrio.providers.canadapost.units as units
|
4
|
+
|
5
|
+
|
6
|
+
METADATA = metadata.PluginMetadata(
|
7
|
+
status="production-ready",
|
8
|
+
id="canadapost",
|
9
|
+
label="Canada Post",
|
10
|
+
# Integrations
|
11
|
+
Mapper=mappers.Mapper,
|
12
|
+
Proxy=mappers.Proxy,
|
13
|
+
Settings=mappers.Settings,
|
14
|
+
# Data Units
|
15
|
+
options=units.ShippingOption,
|
16
|
+
package_presets=units.PackagePresets,
|
17
|
+
services=units.ServiceType,
|
18
|
+
connection_configs=units.ConnectionConfig,
|
19
|
+
# New fields
|
20
|
+
website="https://www.canadapost-postescanada.ca/cpc/en/home.page",
|
21
|
+
documentation="https://www.canadapost-postescanada.ca/information/app/drc/home",
|
22
|
+
description="Mailing and shipping for Personal and Business.",
|
23
|
+
)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from karrio.providers.canadapost.utils import Settings
|
2
|
+
from karrio.providers.canadapost.error import process_error
|
3
|
+
from karrio.providers.canadapost.rate import parse_rate_response, rate_request
|
4
|
+
from karrio.providers.canadapost.shipment import (
|
5
|
+
parse_shipment_cancel_response,
|
6
|
+
parse_shipment_response,
|
7
|
+
shipment_cancel_request,
|
8
|
+
shipment_request,
|
9
|
+
)
|
10
|
+
from karrio.providers.canadapost.pickup import (
|
11
|
+
parse_pickup_cancel_response,
|
12
|
+
parse_pickup_update_response,
|
13
|
+
parse_pickup_response,
|
14
|
+
pickup_update_request,
|
15
|
+
pickup_cancel_request,
|
16
|
+
pickup_request,
|
17
|
+
)
|
18
|
+
from karrio.providers.canadapost.tracking import (
|
19
|
+
parse_tracking_response,
|
20
|
+
tracking_request,
|
21
|
+
)
|
22
|
+
from karrio.providers.canadapost.manifest import (
|
23
|
+
parse_manifest_response,
|
24
|
+
manifest_request,
|
25
|
+
)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import karrio.schemas.canadapost.messages as canadapost
|
2
|
+
|
3
|
+
import typing
|
4
|
+
import karrio.lib as lib
|
5
|
+
import karrio.core.models as models
|
6
|
+
from karrio.providers.canadapost import Settings
|
7
|
+
from urllib.error import HTTPError
|
8
|
+
|
9
|
+
|
10
|
+
def parse_error_response(
|
11
|
+
responses: typing.Union[lib.Element, typing.List[lib.Element]],
|
12
|
+
settings: Settings,
|
13
|
+
**kwargs,
|
14
|
+
) -> typing.List[models.Message]:
|
15
|
+
messages: typing.List[canadapost.messageType] = sum(
|
16
|
+
[
|
17
|
+
lib.find_element("message", response, canadapost.messageType)
|
18
|
+
for response in lib.to_list(responses)
|
19
|
+
],
|
20
|
+
start=[],
|
21
|
+
)
|
22
|
+
|
23
|
+
return [
|
24
|
+
models.Message(
|
25
|
+
code=message.code,
|
26
|
+
message=message.description,
|
27
|
+
carrier_name=settings.carrier_name,
|
28
|
+
carrier_id=settings.carrier_id,
|
29
|
+
details={**kwargs},
|
30
|
+
)
|
31
|
+
for message in messages
|
32
|
+
]
|
33
|
+
|
34
|
+
|
35
|
+
def process_error(error: HTTPError) -> str:
|
36
|
+
return f"""<messages xmlns="http://www.canadapost.ca/ws/messages">
|
37
|
+
<message>
|
38
|
+
<code>{error.code}</code>
|
39
|
+
<description>{typing.cast(typing.Any, error).msg}</description>
|
40
|
+
</message>
|
41
|
+
</messages>
|
42
|
+
"""
|