karrio-hermes 2026.1__py3-none-any.whl → 2026.1.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.
- karrio/mappers/hermes/mapper.py +11 -0
- karrio/mappers/hermes/proxy.py +60 -37
- karrio/providers/hermes/__init__.py +4 -0
- karrio/providers/hermes/pickup/create.py +8 -0
- karrio/providers/hermes/shipment/create.py +278 -245
- karrio/providers/hermes/tracking.py +156 -0
- karrio/providers/hermes/units.py +261 -29
- karrio/providers/hermes/utils.py +26 -0
- karrio/schemas/hermes/__init__.py +10 -0
- karrio/schemas/hermes/tracking_response.py +64 -0
- {karrio_hermes-2026.1.dist-info → karrio_hermes-2026.1.3.dist-info}/METADATA +1 -1
- karrio_hermes-2026.1.3.dist-info/RECORD +29 -0
- {karrio_hermes-2026.1.dist-info → karrio_hermes-2026.1.3.dist-info}/WHEEL +1 -1
- karrio_hermes-2026.1.dist-info/RECORD +0 -27
- {karrio_hermes-2026.1.dist-info → karrio_hermes-2026.1.3.dist-info}/entry_points.txt +0 -0
- {karrio_hermes-2026.1.dist-info → karrio_hermes-2026.1.3.dist-info}/top_level.txt +0 -0
karrio/mappers/hermes/mapper.py
CHANGED
|
@@ -53,6 +53,17 @@ class Mapper(mapper.Mapper):
|
|
|
53
53
|
) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
|
|
54
54
|
return provider.parse_pickup_cancel_response(response, self.settings)
|
|
55
55
|
|
|
56
|
+
# Tracking operations
|
|
57
|
+
def create_tracking_request(
|
|
58
|
+
self, payload: models.TrackingRequest
|
|
59
|
+
) -> lib.Serializable:
|
|
60
|
+
return provider.tracking_request(payload, self.settings)
|
|
61
|
+
|
|
62
|
+
def parse_tracking_response(
|
|
63
|
+
self, response: lib.Deserializable[str]
|
|
64
|
+
) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
|
|
65
|
+
return provider.parse_tracking_response(response, self.settings)
|
|
66
|
+
|
|
56
67
|
# Note: Hermes API does not support:
|
|
57
68
|
# - cancel_shipment (no DELETE endpoint for shipments)
|
|
58
69
|
# - pickup_update (no PUT endpoint for pickups)
|
karrio/mappers/hermes/proxy.py
CHANGED
|
@@ -4,6 +4,7 @@ import karrio.lib as lib
|
|
|
4
4
|
import karrio.api.proxy as proxy
|
|
5
5
|
import karrio.mappers.hermes.settings as provider_settings
|
|
6
6
|
import karrio.universal.mappers.rating_proxy as rating_proxy
|
|
7
|
+
import karrio.providers.hermes.utils as provider_utils
|
|
7
8
|
from karrio.providers.hermes.units import LabelType
|
|
8
9
|
|
|
9
10
|
|
|
@@ -13,58 +14,56 @@ class Proxy(rating_proxy.RatingMixinProxy, proxy.Proxy):
|
|
|
13
14
|
def get_rates(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
14
15
|
return super().get_rates(request)
|
|
15
16
|
|
|
16
|
-
def _get_headers(self, accept: str = "application/json") -> dict:
|
|
17
|
-
"""Get common headers for Hermes API requests."""
|
|
18
|
-
token_data = self.settings.access_token
|
|
19
|
-
# Handle case where token_data might not be a dict
|
|
20
|
-
access_token = token_data.get("access_token") if isinstance(token_data, dict) else token_data
|
|
21
|
-
language = self.settings.connection_config.language.state or "DE"
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
"Content-Type": "application/json",
|
|
25
|
-
"Accept": accept,
|
|
26
|
-
"Accept-Language": language,
|
|
27
|
-
"Authorization": f"Bearer {access_token}",
|
|
28
|
-
}
|
|
29
|
-
|
|
30
17
|
def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
31
|
-
"""Create
|
|
32
|
-
|
|
33
|
-
Endpoint: POST /shipmentorders/labels
|
|
34
|
-
"""
|
|
18
|
+
"""Create shipment order(s) with label(s). Sequential for multi-piece linking."""
|
|
19
|
+
requests_data, is_multi_piece = provider_utils.prepare_shipment_data(request)
|
|
35
20
|
label_type = self.settings.connection_config.label_type.state or "PDF"
|
|
36
21
|
accept_header = LabelType.map(label_type).value or "application/pdf"
|
|
37
22
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
23
|
+
responses, parent_id = [], None
|
|
24
|
+
for idx, req_data in enumerate(requests_data):
|
|
25
|
+
if is_multi_piece and idx > 0 and parent_id:
|
|
26
|
+
req_data = provider_utils.inject_parent_shipment_id(req_data, parent_id)
|
|
27
|
+
|
|
28
|
+
response = lib.request(
|
|
29
|
+
url=f"{self.settings.server_url}/shipmentorders/labels",
|
|
30
|
+
data=lib.to_json(req_data),
|
|
31
|
+
trace=self.trace_as("json"),
|
|
32
|
+
method="POST",
|
|
33
|
+
headers={
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
"Accept": accept_header,
|
|
36
|
+
"Accept-Language": self.settings.connection_config.language.state or "DE",
|
|
37
|
+
"Authorization": f"Bearer {provider_utils.get_access_token(self.settings)}",
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
response_dict = lib.to_dict(response)
|
|
42
|
+
responses.append(response_dict)
|
|
43
|
+
if idx == 0 and is_multi_piece:
|
|
44
|
+
parent_id = provider_utils.extract_shipment_order_id(response_dict)
|
|
45
|
+
|
|
46
|
+
return lib.Deserializable(responses, lambda res: res)
|
|
47
47
|
|
|
48
48
|
def schedule_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
49
|
-
"""Create a pickup order.
|
|
50
|
-
|
|
51
|
-
Endpoint: POST /pickuporders
|
|
52
|
-
"""
|
|
49
|
+
"""Create a pickup order. Endpoint: POST /pickuporders"""
|
|
53
50
|
response = lib.request(
|
|
54
51
|
url=f"{self.settings.server_url}/pickuporders",
|
|
55
52
|
data=lib.to_json(request.serialize()),
|
|
56
53
|
trace=self.trace_as("json"),
|
|
57
54
|
method="POST",
|
|
58
|
-
headers=
|
|
55
|
+
headers={
|
|
56
|
+
"Content-Type": "application/json",
|
|
57
|
+
"Accept": "application/json",
|
|
58
|
+
"Accept-Language": self.settings.connection_config.language.state or "DE",
|
|
59
|
+
"Authorization": f"Bearer {provider_utils.get_access_token(self.settings)}",
|
|
60
|
+
},
|
|
59
61
|
)
|
|
60
62
|
|
|
61
63
|
return lib.Deserializable(response, lib.to_dict)
|
|
62
64
|
|
|
63
65
|
def cancel_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
64
|
-
"""Cancel a pickup order.
|
|
65
|
-
|
|
66
|
-
Endpoint: DELETE /pickuporders/{pickupOrderID}
|
|
67
|
-
"""
|
|
66
|
+
"""Cancel a pickup order. Endpoint: DELETE /pickuporders/{pickupOrderID}"""
|
|
68
67
|
payload = request.serialize()
|
|
69
68
|
pickup_order_id = payload.get("pickupOrderID")
|
|
70
69
|
|
|
@@ -72,7 +71,31 @@ class Proxy(rating_proxy.RatingMixinProxy, proxy.Proxy):
|
|
|
72
71
|
url=f"{self.settings.server_url}/pickuporders/{pickup_order_id}",
|
|
73
72
|
trace=self.trace_as("json"),
|
|
74
73
|
method="DELETE",
|
|
75
|
-
headers=
|
|
74
|
+
headers={
|
|
75
|
+
"Content-Type": "application/json",
|
|
76
|
+
"Accept": "application/json",
|
|
77
|
+
"Accept-Language": self.settings.connection_config.language.state or "DE",
|
|
78
|
+
"Authorization": f"Bearer {provider_utils.get_access_token(self.settings)}",
|
|
79
|
+
},
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return lib.Deserializable(response, lib.to_dict)
|
|
83
|
+
|
|
84
|
+
def get_tracking(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
85
|
+
"""Get tracking information. Endpoint: GET /shipmentinfo"""
|
|
86
|
+
tracking_numbers = request.serialize()
|
|
87
|
+
query_params = "&".join([f"shipmentID={num}" for num in tracking_numbers])
|
|
88
|
+
|
|
89
|
+
response = lib.request(
|
|
90
|
+
url=f"{self.settings.server_url}/shipmentinfo?{query_params}",
|
|
91
|
+
trace=self.trace_as("json"),
|
|
92
|
+
method="GET",
|
|
93
|
+
headers={
|
|
94
|
+
"Content-Type": "application/json",
|
|
95
|
+
"Accept": "application/json",
|
|
96
|
+
"Accept-Language": self.settings.connection_config.language.state or "DE",
|
|
97
|
+
"Authorization": f"Bearer {provider_utils.get_access_token(self.settings)}",
|
|
98
|
+
},
|
|
76
99
|
)
|
|
77
100
|
|
|
78
101
|
return lib.Deserializable(response, lib.to_dict)
|
|
@@ -10,6 +10,10 @@ from karrio.providers.hermes.pickup import (
|
|
|
10
10
|
pickup_cancel_request,
|
|
11
11
|
pickup_request,
|
|
12
12
|
)
|
|
13
|
+
from karrio.providers.hermes.tracking import (
|
|
14
|
+
parse_tracking_response,
|
|
15
|
+
tracking_request,
|
|
16
|
+
)
|
|
13
17
|
|
|
14
18
|
# Note: Hermes API does not support:
|
|
15
19
|
# - shipment cancellation (no DELETE endpoint for shipments)
|
|
@@ -48,6 +48,14 @@ def pickup_request(
|
|
|
48
48
|
settings: provider_utils.Settings,
|
|
49
49
|
) -> lib.Serializable:
|
|
50
50
|
"""Create a Hermes pickup request."""
|
|
51
|
+
# Hermes only supports one-time pickups via API
|
|
52
|
+
pickup_type = getattr(payload, "pickup_type", "one_time") or "one_time"
|
|
53
|
+
if pickup_type not in ("one_time", None):
|
|
54
|
+
raise lib.exceptions.FieldError({
|
|
55
|
+
"pickup_type": f"Hermes only supports 'one_time' pickups via API. Received: '{pickup_type}'. "
|
|
56
|
+
"For daily/recurring pickups, please contact Hermes to set up a regular pickup schedule."
|
|
57
|
+
})
|
|
58
|
+
|
|
51
59
|
address = lib.to_address(payload.address)
|
|
52
60
|
|
|
53
61
|
# Parse parcel counts by size (XS, S, M, L, XL)
|