karrio-teleship 2025.5__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/teleship/__init__.py +4 -0
- karrio/mappers/teleship/hooks.py +27 -0
- karrio/mappers/teleship/mapper.py +114 -0
- karrio/mappers/teleship/proxy.py +239 -0
- karrio/mappers/teleship/settings.py +21 -0
- karrio/plugins/teleship/__init__.py +32 -0
- karrio/providers/teleship/__init__.py +41 -0
- karrio/providers/teleship/duties.py +115 -0
- karrio/providers/teleship/error.py +44 -0
- karrio/providers/teleship/hooks/__init__.py +5 -0
- karrio/providers/teleship/hooks/event.py +163 -0
- karrio/providers/teleship/hooks/oauth.py +103 -0
- karrio/providers/teleship/manifest.py +68 -0
- karrio/providers/teleship/pickup/__init__.py +8 -0
- karrio/providers/teleship/pickup/cancel.py +43 -0
- karrio/providers/teleship/pickup/schedule.py +66 -0
- karrio/providers/teleship/rate.py +287 -0
- karrio/providers/teleship/shipment/__init__.py +9 -0
- karrio/providers/teleship/shipment/cancel.py +48 -0
- karrio/providers/teleship/shipment/create.py +322 -0
- karrio/providers/teleship/tracking.py +100 -0
- karrio/providers/teleship/units.py +154 -0
- karrio/providers/teleship/utils.py +57 -0
- karrio/providers/teleship/webhook/__init__.py +8 -0
- karrio/providers/teleship/webhook/deregister.py +47 -0
- karrio/providers/teleship/webhook/register.py +48 -0
- karrio/schemas/teleship/__init__.py +0 -0
- karrio/schemas/teleship/duties_taxes_request.py +82 -0
- karrio/schemas/teleship/duties_taxes_response.py +28 -0
- karrio/schemas/teleship/error_response.py +17 -0
- karrio/schemas/teleship/manifest_request.py +39 -0
- karrio/schemas/teleship/manifest_response.py +31 -0
- karrio/schemas/teleship/pickup_request.py +31 -0
- karrio/schemas/teleship/pickup_response.py +48 -0
- karrio/schemas/teleship/rate_request.py +128 -0
- karrio/schemas/teleship/rate_response.py +66 -0
- karrio/schemas/teleship/shipment_cancel_request.py +8 -0
- karrio/schemas/teleship/shipment_cancel_response.py +17 -0
- karrio/schemas/teleship/shipment_request.py +137 -0
- karrio/schemas/teleship/shipment_response.py +247 -0
- karrio/schemas/teleship/tracking_request.py +8 -0
- karrio/schemas/teleship/tracking_response.py +52 -0
- karrio/schemas/teleship/webhook_request.py +18 -0
- karrio/schemas/teleship/webhook_response.py +20 -0
- karrio_teleship-2025.5.dist-info/METADATA +44 -0
- karrio_teleship-2025.5.dist-info/RECORD +49 -0
- karrio_teleship-2025.5.dist-info/WHEEL +5 -0
- karrio_teleship-2025.5.dist-info/entry_points.txt +2 -0
- karrio_teleship-2025.5.dist-info/top_level.txt +4 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Karrio Teleship client hooks."""
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
import karrio.lib as lib
|
|
5
|
+
import karrio.core.models as models
|
|
6
|
+
import karrio.api.hooks as hooks
|
|
7
|
+
import karrio.providers.teleship as provider
|
|
8
|
+
import karrio.mappers.teleship.settings as provider_settings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Hooks(hooks.Hooks):
|
|
12
|
+
settings: provider_settings.Settings
|
|
13
|
+
|
|
14
|
+
def on_webhook_event(
|
|
15
|
+
self, payload: models.RequestPayload
|
|
16
|
+
) -> typing.Tuple[models.WebhookEventDetails, typing.List[models.Message]]:
|
|
17
|
+
return provider.on_webhook_event(payload, self.settings)
|
|
18
|
+
|
|
19
|
+
def on_oauth_authorize(
|
|
20
|
+
self, payload: models.OAuthAuthorizePayload
|
|
21
|
+
) -> typing.Tuple[models.OAuthAuthorizeRequest, typing.List[models.Message]]:
|
|
22
|
+
return provider.on_oauth_authorize(payload, self.settings)
|
|
23
|
+
|
|
24
|
+
def on_oauth_callback(
|
|
25
|
+
self, payload: models.RequestPayload
|
|
26
|
+
) -> typing.Tuple[typing.List[typing.Dict], typing.List[models.Message]]:
|
|
27
|
+
return provider.on_oauth_callback(payload, self.settings)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Karrio Teleship 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.teleship as provider
|
|
8
|
+
import karrio.mappers.teleship.settings as provider_settings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Mapper(mapper.Mapper):
|
|
12
|
+
settings: provider_settings.Settings
|
|
13
|
+
|
|
14
|
+
def create_rate_request(
|
|
15
|
+
self, payload: models.RateRequest
|
|
16
|
+
) -> lib.Serializable:
|
|
17
|
+
return provider.rate_request(payload, self.settings)
|
|
18
|
+
|
|
19
|
+
def create_tracking_request(
|
|
20
|
+
self, payload: models.TrackingRequest
|
|
21
|
+
) -> lib.Serializable:
|
|
22
|
+
return provider.tracking_request(payload, self.settings)
|
|
23
|
+
|
|
24
|
+
def create_shipment_request(
|
|
25
|
+
self, payload: models.ShipmentRequest
|
|
26
|
+
) -> lib.Serializable:
|
|
27
|
+
return provider.shipment_request(payload, self.settings)
|
|
28
|
+
|
|
29
|
+
def create_cancel_shipment_request(
|
|
30
|
+
self, payload: models.ShipmentCancelRequest
|
|
31
|
+
) -> lib.Serializable[str]:
|
|
32
|
+
return provider.shipment_cancel_request(payload, self.settings)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def parse_cancel_shipment_response(
|
|
36
|
+
self, response: lib.Deserializable[str]
|
|
37
|
+
) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
|
|
38
|
+
return provider.parse_shipment_cancel_response(response, self.settings)
|
|
39
|
+
|
|
40
|
+
def parse_rate_response(
|
|
41
|
+
self, response: lib.Deserializable[str]
|
|
42
|
+
) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
|
|
43
|
+
return provider.parse_rate_response(response, self.settings)
|
|
44
|
+
|
|
45
|
+
def parse_shipment_response(
|
|
46
|
+
self, response: lib.Deserializable[str]
|
|
47
|
+
) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
|
|
48
|
+
return provider.parse_shipment_response(response, self.settings)
|
|
49
|
+
|
|
50
|
+
def parse_tracking_response(
|
|
51
|
+
self, response: lib.Deserializable[str]
|
|
52
|
+
) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
|
|
53
|
+
return provider.parse_tracking_response(response, self.settings)
|
|
54
|
+
|
|
55
|
+
def create_pickup_request(
|
|
56
|
+
self, payload: models.PickupRequest
|
|
57
|
+
) -> lib.Serializable:
|
|
58
|
+
return provider.pickup_request(payload, self.settings)
|
|
59
|
+
|
|
60
|
+
def create_cancel_pickup_request(
|
|
61
|
+
self, payload: models.PickupCancelRequest
|
|
62
|
+
) -> lib.Serializable:
|
|
63
|
+
return provider.cancel_pickup_request(payload, self.settings)
|
|
64
|
+
|
|
65
|
+
def parse_pickup_response(
|
|
66
|
+
self, response: lib.Deserializable[str]
|
|
67
|
+
) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
|
|
68
|
+
return provider.parse_pickup_response(response, self.settings)
|
|
69
|
+
|
|
70
|
+
def parse_cancel_pickup_response(
|
|
71
|
+
self, response: lib.Deserializable[str]
|
|
72
|
+
) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
|
|
73
|
+
return provider.parse_cancel_pickup_response(response, self.settings)
|
|
74
|
+
|
|
75
|
+
def create_manifest_request(
|
|
76
|
+
self, payload: models.ManifestRequest
|
|
77
|
+
) -> lib.Serializable:
|
|
78
|
+
return provider.manifest_request(payload, self.settings)
|
|
79
|
+
|
|
80
|
+
def parse_manifest_response(
|
|
81
|
+
self, response: lib.Deserializable[str]
|
|
82
|
+
) -> typing.Tuple[models.ManifestDetails, typing.List[models.Message]]:
|
|
83
|
+
return provider.parse_manifest_response(response, self.settings)
|
|
84
|
+
|
|
85
|
+
def create_duties_calculation_request(
|
|
86
|
+
self, payload: models.DutiesCalculationRequest
|
|
87
|
+
) -> lib.Serializable:
|
|
88
|
+
return provider.duties_calculation_request(payload, self.settings)
|
|
89
|
+
|
|
90
|
+
def parse_duties_calculation_response(
|
|
91
|
+
self, response: lib.Deserializable[str]
|
|
92
|
+
) -> typing.Tuple[models.DutiesCalculationDetails, typing.List[models.Message]]:
|
|
93
|
+
return provider.parse_duties_calculation_response(response, self.settings)
|
|
94
|
+
|
|
95
|
+
def create_webhook_registration_request(
|
|
96
|
+
self, payload: models.WebhookRegistrationRequest
|
|
97
|
+
) -> lib.Serializable:
|
|
98
|
+
return provider.webhook_registration_request(payload, self.settings)
|
|
99
|
+
|
|
100
|
+
def parse_webhook_registration_response(
|
|
101
|
+
self, response: lib.Deserializable[str]
|
|
102
|
+
) -> typing.Tuple[models.WebhookRegistrationDetails, typing.List[models.Message]]:
|
|
103
|
+
return provider.parse_webhook_registration_response(response, self.settings)
|
|
104
|
+
|
|
105
|
+
def create_webhook_deregistration_request(
|
|
106
|
+
self, payload: models.WebhookDeregistrationRequest
|
|
107
|
+
) -> lib.Serializable:
|
|
108
|
+
return provider.webhook_deregistration_request(payload, self.settings)
|
|
109
|
+
|
|
110
|
+
def parse_webhook_deregistration_response(
|
|
111
|
+
self, response: lib.Deserializable[str]
|
|
112
|
+
) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
|
|
113
|
+
return provider.parse_webhook_deregistration_response(response, self.settings)
|
|
114
|
+
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""Karrio Teleship client proxy."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import karrio.lib as lib
|
|
5
|
+
import karrio.api.proxy as proxy
|
|
6
|
+
import karrio.core.errors as errors
|
|
7
|
+
import karrio.core.models as models
|
|
8
|
+
import karrio.providers.teleship.error as provider_error
|
|
9
|
+
import karrio.mappers.teleship.settings as provider_settings
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Proxy(proxy.Proxy):
|
|
13
|
+
settings: provider_settings.Settings
|
|
14
|
+
|
|
15
|
+
def authenticate(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
16
|
+
"""Retrieve access token using thread-safe token manager.
|
|
17
|
+
|
|
18
|
+
Returns the cached token if available and not expired,
|
|
19
|
+
otherwise fetches a new token from the OAuth endpoint.
|
|
20
|
+
"""
|
|
21
|
+
cache_key = f"{self.settings.carrier_name}|{self.settings.client_id}|{self.settings.client_secret}"
|
|
22
|
+
|
|
23
|
+
def get_token():
|
|
24
|
+
response = lib.request(
|
|
25
|
+
url=f"{self.settings.server_url}/oauth/token",
|
|
26
|
+
method="POST",
|
|
27
|
+
headers={"content-Type": "application/x-www-form-urlencoded"},
|
|
28
|
+
data=lib.to_query_string(
|
|
29
|
+
dict(
|
|
30
|
+
grant_type="client_credentials",
|
|
31
|
+
clientId=self.settings.client_id,
|
|
32
|
+
clientSecret=self.settings.client_secret,
|
|
33
|
+
)
|
|
34
|
+
),
|
|
35
|
+
decoder=lib.to_dict,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
messages = provider_error.parse_error_response(response, self.settings)
|
|
39
|
+
|
|
40
|
+
if any(messages):
|
|
41
|
+
raise errors.ParsedMessagesError(messages)
|
|
42
|
+
|
|
43
|
+
expiry = datetime.datetime.now() + datetime.timedelta(
|
|
44
|
+
seconds=float(response.get("expiresIn", 0))
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return {**response, "expiry": lib.fdatetime(expiry)}
|
|
48
|
+
|
|
49
|
+
token = self.settings.connection_cache.thread_safe(
|
|
50
|
+
refresh_func=get_token,
|
|
51
|
+
cache_key=cache_key,
|
|
52
|
+
buffer_minutes=30,
|
|
53
|
+
token_field="accessToken",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return lib.Deserializable(token.get_state())
|
|
57
|
+
|
|
58
|
+
def get_rates(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
59
|
+
access_token = self.authenticate(request).deserialize()
|
|
60
|
+
|
|
61
|
+
responses = lib.run_asynchronously(
|
|
62
|
+
lambda payload: lib.request(
|
|
63
|
+
url=f"{self.settings.server_url}/api/rates/quotes",
|
|
64
|
+
data=lib.to_json(payload),
|
|
65
|
+
trace=self.trace_as("json"),
|
|
66
|
+
method="POST",
|
|
67
|
+
headers={
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
"Authorization": f"Bearer {access_token}",
|
|
70
|
+
},
|
|
71
|
+
),
|
|
72
|
+
request.serialize(),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return lib.Deserializable(
|
|
76
|
+
responses,
|
|
77
|
+
lambda __: [lib.to_dict(_) for _ in __],
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
81
|
+
access_token = self.authenticate(request).deserialize()
|
|
82
|
+
|
|
83
|
+
responses = lib.run_asynchronously(
|
|
84
|
+
lambda payload: lib.request(
|
|
85
|
+
url=f"{self.settings.server_url}/api/shipments/labels",
|
|
86
|
+
data=lib.to_json(payload),
|
|
87
|
+
trace=self.trace_as("json"),
|
|
88
|
+
method="POST",
|
|
89
|
+
headers={
|
|
90
|
+
"Content-Type": "application/json",
|
|
91
|
+
"Authorization": f"Bearer {access_token}",
|
|
92
|
+
},
|
|
93
|
+
),
|
|
94
|
+
request.serialize(),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return lib.Deserializable(
|
|
98
|
+
responses,
|
|
99
|
+
lambda __: [lib.to_dict(_) for _ in __],
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
103
|
+
access_token = self.authenticate(request).deserialize()
|
|
104
|
+
shipment_id = request.serialize().get("shipmentId")
|
|
105
|
+
|
|
106
|
+
response = lib.request(
|
|
107
|
+
url=f"{self.settings.server_url}/api/shipments/labels/{shipment_id}/void",
|
|
108
|
+
trace=self.trace_as("json"),
|
|
109
|
+
method="POST",
|
|
110
|
+
headers={
|
|
111
|
+
"Content-Type": "application/json",
|
|
112
|
+
"Authorization": f"Bearer {access_token}",
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return lib.Deserializable(response, lib.to_dict)
|
|
117
|
+
|
|
118
|
+
def get_tracking(self, request: lib.Serializable) -> lib.Deserializable:
|
|
119
|
+
access_token = self.authenticate(request).deserialize()
|
|
120
|
+
|
|
121
|
+
def _get_tracking(tracking_number: str):
|
|
122
|
+
return tracking_number, lib.request(
|
|
123
|
+
url=f"{self.settings.server_url}/api/tracking/{tracking_number}",
|
|
124
|
+
trace=self.trace_as("json"),
|
|
125
|
+
method="GET",
|
|
126
|
+
headers={
|
|
127
|
+
"Content-Type": "application/json",
|
|
128
|
+
"Authorization": f"Bearer {access_token}",
|
|
129
|
+
},
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Use concurrent requests for multiple tracking numbers
|
|
133
|
+
responses = lib.run_concurently(_get_tracking, request.serialize())
|
|
134
|
+
|
|
135
|
+
return lib.Deserializable(
|
|
136
|
+
responses,
|
|
137
|
+
lambda res: [
|
|
138
|
+
(num, lib.to_dict(track)) for num, track in res if any(track.strip())
|
|
139
|
+
],
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def schedule_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
143
|
+
access_token = self.authenticate(request).deserialize()
|
|
144
|
+
|
|
145
|
+
response = lib.request(
|
|
146
|
+
url=f"{self.settings.server_url}/api/pickups",
|
|
147
|
+
data=lib.to_json(request.serialize()),
|
|
148
|
+
trace=self.trace_as("json"),
|
|
149
|
+
method="POST",
|
|
150
|
+
headers={
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
"Authorization": f"Bearer {access_token}",
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return lib.Deserializable(response, lib.to_dict)
|
|
157
|
+
|
|
158
|
+
def cancel_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
159
|
+
access_token = self.authenticate(request).deserialize()
|
|
160
|
+
pickup_id = request.serialize().get("pickupId")
|
|
161
|
+
|
|
162
|
+
response = lib.request(
|
|
163
|
+
url=f"{self.settings.server_url}/api/pickups/{pickup_id}/cancel",
|
|
164
|
+
trace=self.trace_as("json"),
|
|
165
|
+
method="POST",
|
|
166
|
+
headers={
|
|
167
|
+
"Content-Type": "application/json",
|
|
168
|
+
"Authorization": f"Bearer {access_token}",
|
|
169
|
+
},
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return lib.Deserializable(response, lib.to_dict)
|
|
173
|
+
|
|
174
|
+
def create_manifest(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
175
|
+
access_token = self.authenticate(request).deserialize()
|
|
176
|
+
|
|
177
|
+
response = lib.request(
|
|
178
|
+
url=f"{self.settings.server_url}/api/manifests",
|
|
179
|
+
data=lib.to_json(request.serialize()),
|
|
180
|
+
trace=self.trace_as("json"),
|
|
181
|
+
method="POST",
|
|
182
|
+
headers={
|
|
183
|
+
"Content-Type": "application/json",
|
|
184
|
+
"Authorization": f"Bearer {access_token}",
|
|
185
|
+
},
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
return lib.Deserializable(response, lib.to_dict)
|
|
189
|
+
|
|
190
|
+
def calculate_duties(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
191
|
+
access_token = self.authenticate(request).deserialize()
|
|
192
|
+
|
|
193
|
+
response = lib.request(
|
|
194
|
+
url=f"{self.settings.server_url}/api/trade-engine/duties-taxes",
|
|
195
|
+
data=lib.to_json(request.serialize()),
|
|
196
|
+
trace=self.trace_as("json"),
|
|
197
|
+
method="POST",
|
|
198
|
+
headers={
|
|
199
|
+
"Content-Type": "application/json",
|
|
200
|
+
"Authorization": f"Bearer {access_token}",
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return lib.Deserializable(response, lib.to_dict)
|
|
205
|
+
|
|
206
|
+
def register_webhook(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
207
|
+
access_token = self.authenticate(request).deserialize()
|
|
208
|
+
|
|
209
|
+
response = lib.request(
|
|
210
|
+
url=f"{self.settings.server_url}/api/webhooks",
|
|
211
|
+
data=lib.to_json(request.serialize()),
|
|
212
|
+
trace=self.trace_as("json"),
|
|
213
|
+
method="POST",
|
|
214
|
+
headers={
|
|
215
|
+
"Content-Type": "application/json",
|
|
216
|
+
"Authorization": f"Bearer {access_token}",
|
|
217
|
+
},
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return lib.Deserializable(response, lib.to_dict)
|
|
221
|
+
|
|
222
|
+
def deregister_webhook(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
223
|
+
access_token = self.authenticate(request).deserialize()
|
|
224
|
+
webhook_id = request.serialize().get("webhookId")
|
|
225
|
+
|
|
226
|
+
response = lib.request(
|
|
227
|
+
url=f"{self.settings.server_url}/api/webhooks/{webhook_id}",
|
|
228
|
+
trace=self.trace_as("json"),
|
|
229
|
+
method="DELETE",
|
|
230
|
+
headers={
|
|
231
|
+
"Authorization": f"Bearer {access_token}",
|
|
232
|
+
},
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# DELETE returns 204 No Content on success - handle empty response
|
|
236
|
+
return lib.Deserializable(
|
|
237
|
+
response,
|
|
238
|
+
lambda r: lib.to_dict(r) if r and r.strip() else {},
|
|
239
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Karrio Teleship client settings."""
|
|
2
|
+
|
|
3
|
+
import attr
|
|
4
|
+
import karrio.providers.teleship.utils as provider_utils
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@attr.s(auto_attribs=True)
|
|
8
|
+
class Settings(provider_utils.Settings):
|
|
9
|
+
"""Teleship connection settings."""
|
|
10
|
+
|
|
11
|
+
# Add carrier specific API connection properties here
|
|
12
|
+
client_id: str
|
|
13
|
+
client_secret: str
|
|
14
|
+
|
|
15
|
+
# generic properties
|
|
16
|
+
id: str = None
|
|
17
|
+
test_mode: bool = False
|
|
18
|
+
carrier_id: str = "teleship"
|
|
19
|
+
account_country_code: str = None
|
|
20
|
+
metadata: dict = {}
|
|
21
|
+
config: dict = {}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from karrio.core.metadata import PluginMetadata
|
|
2
|
+
|
|
3
|
+
from karrio.mappers.teleship.mapper import Mapper
|
|
4
|
+
from karrio.mappers.teleship.proxy import Proxy
|
|
5
|
+
from karrio.mappers.teleship.settings import Settings
|
|
6
|
+
from karrio.mappers.teleship.hooks import Hooks
|
|
7
|
+
import karrio.providers.teleship.units as units
|
|
8
|
+
import karrio.providers.teleship.utils as utils
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# This METADATA object is used by Karrio to discover and register this plugin
|
|
12
|
+
# when loaded through Python entrypoints or local plugin directories.
|
|
13
|
+
# The entrypoint is defined in pyproject.toml under [project.entry-points."karrio.plugins"]
|
|
14
|
+
METADATA = PluginMetadata(
|
|
15
|
+
id="teleship",
|
|
16
|
+
label="Teleship",
|
|
17
|
+
description="Teleship is an international shipping platform providing end-to-end logistics solutions with real-time rates, automated customs compliance, and shipment tracking.",
|
|
18
|
+
# Integrations
|
|
19
|
+
Mapper=Mapper,
|
|
20
|
+
Proxy=Proxy,
|
|
21
|
+
Settings=Settings,
|
|
22
|
+
Hooks=Hooks,
|
|
23
|
+
# Data Units
|
|
24
|
+
is_hub=False,
|
|
25
|
+
options=units.ShippingOption,
|
|
26
|
+
services=units.ShippingService,
|
|
27
|
+
connection_configs=units.ConnectionConfig,
|
|
28
|
+
system_config=units.SYSTEM_CONFIG,
|
|
29
|
+
# Extra info
|
|
30
|
+
website="https://www.teleship.com",
|
|
31
|
+
documentation="https://developers.teleship.com",
|
|
32
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Karrio Teleship provider imports."""
|
|
2
|
+
from karrio.providers.teleship.utils import Settings
|
|
3
|
+
from karrio.providers.teleship.rate import (
|
|
4
|
+
parse_rate_response,
|
|
5
|
+
rate_request,
|
|
6
|
+
)
|
|
7
|
+
from karrio.providers.teleship.shipment import (
|
|
8
|
+
parse_shipment_cancel_response,
|
|
9
|
+
parse_shipment_response,
|
|
10
|
+
shipment_cancel_request,
|
|
11
|
+
shipment_request,
|
|
12
|
+
)
|
|
13
|
+
from karrio.providers.teleship.tracking import (
|
|
14
|
+
parse_tracking_response,
|
|
15
|
+
tracking_request,
|
|
16
|
+
)
|
|
17
|
+
from karrio.providers.teleship.pickup import (
|
|
18
|
+
pickup_request,
|
|
19
|
+
parse_pickup_response,
|
|
20
|
+
cancel_pickup_request,
|
|
21
|
+
parse_cancel_pickup_response,
|
|
22
|
+
)
|
|
23
|
+
from karrio.providers.teleship.manifest import (
|
|
24
|
+
manifest_request,
|
|
25
|
+
parse_manifest_response,
|
|
26
|
+
)
|
|
27
|
+
from karrio.providers.teleship.duties import (
|
|
28
|
+
duties_calculation_request,
|
|
29
|
+
parse_duties_calculation_response,
|
|
30
|
+
)
|
|
31
|
+
from karrio.providers.teleship.webhook import (
|
|
32
|
+
webhook_registration_request,
|
|
33
|
+
parse_webhook_registration_response,
|
|
34
|
+
webhook_deregistration_request,
|
|
35
|
+
parse_webhook_deregistration_response,
|
|
36
|
+
)
|
|
37
|
+
from karrio.providers.teleship.hooks import (
|
|
38
|
+
on_webhook_event,
|
|
39
|
+
on_oauth_authorize,
|
|
40
|
+
on_oauth_callback,
|
|
41
|
+
)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Karrio Teleship duties calculation implementation."""
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
import karrio.schemas.teleship.duties_taxes_request as teleship
|
|
5
|
+
import karrio.schemas.teleship.duties_taxes_response as duties
|
|
6
|
+
import karrio.lib as lib
|
|
7
|
+
import karrio.core.models as models
|
|
8
|
+
import karrio.providers.teleship.error as error
|
|
9
|
+
import karrio.providers.teleship.utils as provider_utils
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def parse_duties_calculation_response(
|
|
13
|
+
_response: lib.Deserializable[str],
|
|
14
|
+
settings: provider_utils.Settings,
|
|
15
|
+
) -> typing.Tuple[models.DutiesCalculationDetails, typing.List[models.Message]]:
|
|
16
|
+
response = _response.deserialize()
|
|
17
|
+
messages = error.parse_error_response(response, settings)
|
|
18
|
+
details = lib.to_object(duties.DutiesTaxesResponseType, response)
|
|
19
|
+
|
|
20
|
+
duties_details = lib.identity(
|
|
21
|
+
models.DutiesCalculationDetails(
|
|
22
|
+
carrier_id=settings.carrier_id,
|
|
23
|
+
carrier_name=settings.carrier_name,
|
|
24
|
+
total_charge=lib.to_money(details.price),
|
|
25
|
+
currency=details.currency,
|
|
26
|
+
charges=[
|
|
27
|
+
models.ChargeDetails(
|
|
28
|
+
name=charge.name,
|
|
29
|
+
amount=lib.to_money(charge.amount),
|
|
30
|
+
currency=charge.currency,
|
|
31
|
+
)
|
|
32
|
+
for charge in (details.charges or [])
|
|
33
|
+
],
|
|
34
|
+
meta=lib.to_dict(details),
|
|
35
|
+
)
|
|
36
|
+
if details and details.price
|
|
37
|
+
else None
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return duties_details, messages
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def duties_calculation_request(
|
|
44
|
+
payload: models.DutiesCalculationRequest,
|
|
45
|
+
settings: provider_utils.Settings,
|
|
46
|
+
) -> lib.Serializable:
|
|
47
|
+
shipper = lib.to_address(payload.shipper)
|
|
48
|
+
recipient = lib.to_address(payload.recipient)
|
|
49
|
+
customs = lib.to_customs_info(payload.customs)
|
|
50
|
+
commodities = lib.to_commodities(customs.commodities or [])
|
|
51
|
+
|
|
52
|
+
request = teleship.DutiesTaxesRequestType(
|
|
53
|
+
shipTo=teleship.ShipType(
|
|
54
|
+
name=recipient.person_name,
|
|
55
|
+
email=recipient.email,
|
|
56
|
+
phone=recipient.phone_number,
|
|
57
|
+
company=recipient.company_name,
|
|
58
|
+
address=teleship.AddressType(
|
|
59
|
+
line1=recipient.address_line1,
|
|
60
|
+
line2=recipient.address_line2,
|
|
61
|
+
city=recipient.city,
|
|
62
|
+
state=recipient.state_code,
|
|
63
|
+
postcode=recipient.postal_code,
|
|
64
|
+
country=recipient.country_code,
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
shipFrom=teleship.ShipType(
|
|
68
|
+
name=shipper.person_name,
|
|
69
|
+
email=shipper.email,
|
|
70
|
+
phone=shipper.phone_number,
|
|
71
|
+
company=shipper.company_name,
|
|
72
|
+
address=teleship.AddressType(
|
|
73
|
+
line1=shipper.address_line1,
|
|
74
|
+
line2=shipper.address_line2,
|
|
75
|
+
city=shipper.city,
|
|
76
|
+
state=shipper.state_code,
|
|
77
|
+
postcode=shipper.postal_code,
|
|
78
|
+
country=shipper.country_code,
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
commodities=[
|
|
82
|
+
teleship.CommodityType(
|
|
83
|
+
quantity=commodity.quantity,
|
|
84
|
+
title=commodity.title or commodities.description,
|
|
85
|
+
description=lib.identity(
|
|
86
|
+
commodity.description if commodity.title else None
|
|
87
|
+
),
|
|
88
|
+
hsCode=commodity.hs_code,
|
|
89
|
+
sku=commodity.sku,
|
|
90
|
+
value=teleship.ConsigneeChargesType(
|
|
91
|
+
amount=commodity.value_amount,
|
|
92
|
+
currency=commodity.value_currency,
|
|
93
|
+
),
|
|
94
|
+
unitWeight=teleship.UnitWeightType(
|
|
95
|
+
unit=commodity.weight_unit or "kg",
|
|
96
|
+
value=commodity.weight,
|
|
97
|
+
),
|
|
98
|
+
countryOfOrigin=commodity.origin_country,
|
|
99
|
+
category=commodity.category,
|
|
100
|
+
)
|
|
101
|
+
for commodity in commodities
|
|
102
|
+
],
|
|
103
|
+
customs=teleship.CustomsType(
|
|
104
|
+
contentType=customs.content_type or "CommercialGoods",
|
|
105
|
+
incoterms=lib.failsafe(lambda: customs.options.incoterms.state),
|
|
106
|
+
invoiceNumber=customs.invoice,
|
|
107
|
+
invoiceDate=customs.invoice_date,
|
|
108
|
+
EORI=lib.failsafe(lambda: customs.options.eori_number.state),
|
|
109
|
+
VAT=lib.failsafe(lambda: customs.options.vat.state),
|
|
110
|
+
),
|
|
111
|
+
currency=payload.options.get("currency") if payload.options else None,
|
|
112
|
+
orderTrackingReference=payload.reference,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return lib.Serializable(request, lib.to_dict)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
import karrio.lib as lib
|
|
3
|
+
import karrio.core.models as models
|
|
4
|
+
import karrio.providers.teleship.utils as provider_utils
|
|
5
|
+
import karrio.schemas.teleship.error_response as teleship
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parse_error_response(
|
|
9
|
+
response: typing.Union[dict, list],
|
|
10
|
+
settings: provider_utils.Settings,
|
|
11
|
+
**kwargs,
|
|
12
|
+
) -> typing.List[models.Message]:
|
|
13
|
+
"""Parse Teleship error response"""
|
|
14
|
+
|
|
15
|
+
errors = lib.failsafe(lambda: response.get("messages")) or []
|
|
16
|
+
|
|
17
|
+
# Handle single error format
|
|
18
|
+
if isinstance(response, dict) and response.get("error"):
|
|
19
|
+
error_obj = response.get("error")
|
|
20
|
+
if isinstance(error_obj, dict):
|
|
21
|
+
errors = [error_obj]
|
|
22
|
+
elif isinstance(error_obj, list):
|
|
23
|
+
errors = error_obj
|
|
24
|
+
|
|
25
|
+
# Handle messages array format
|
|
26
|
+
if isinstance(errors, list) and any(errors):
|
|
27
|
+
return [
|
|
28
|
+
models.Message(
|
|
29
|
+
carrier_id=settings.carrier_id,
|
|
30
|
+
carrier_name=settings.carrier_name,
|
|
31
|
+
code=str(error.get("code", "")),
|
|
32
|
+
message=error.get("message", ""),
|
|
33
|
+
details=lib.to_dict(
|
|
34
|
+
{
|
|
35
|
+
**kwargs,
|
|
36
|
+
"timestamp": error.get("timestamp"),
|
|
37
|
+
"details": error.get("details"),
|
|
38
|
+
}
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
for error in errors
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
return []
|