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.
@@ -0,0 +1,156 @@
1
+ """Karrio Hermes tracking API implementation."""
2
+
3
+ import typing
4
+ import karrio.lib as lib
5
+ import karrio.core.models as models
6
+ import karrio.providers.hermes.error as error
7
+ import karrio.providers.hermes.utils as provider_utils
8
+ import karrio.providers.hermes.units as provider_units
9
+ import karrio.schemas.hermes.tracking_response as hermes_res
10
+
11
+
12
+ def _match_status(code: str) -> typing.Optional[str]:
13
+ """Match Hermes event code against TrackingStatus enum values."""
14
+ if not code:
15
+ return None
16
+ for status in list(provider_units.TrackingStatus):
17
+ if code in status.value:
18
+ return status.name
19
+ return None
20
+
21
+
22
+ def _match_reason(code: str) -> typing.Optional[str]:
23
+ """Match Hermes event code against TrackingIncidentReason enum values."""
24
+ if not code:
25
+ return None
26
+ for reason in list(provider_units.TrackingIncidentReason):
27
+ if code in reason.value:
28
+ return reason.name
29
+ return None
30
+
31
+
32
+ def parse_tracking_response(
33
+ _response: lib.Deserializable[dict],
34
+ settings: provider_utils.Settings,
35
+ ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
36
+ """Parse tracking response from Hermes Shipment Info API."""
37
+ response = _response.deserialize()
38
+
39
+ # Parse the response using generated schema
40
+ tracking_response = lib.to_object(hermes_res.TrackingResponseType, response)
41
+
42
+ # Collect error messages
43
+ messages: typing.List[models.Message] = []
44
+
45
+ # Extract tracking details for each shipment
46
+ tracking_details: typing.List[models.TrackingDetails] = []
47
+
48
+ for shipment_info in tracking_response.shipmentinfo or []:
49
+ # Check for errors in individual shipment result
50
+ if shipment_info.result and shipment_info.result.code:
51
+ if shipment_info.result.code.startswith("e"):
52
+ messages.append(
53
+ models.Message(
54
+ carrier_id=settings.carrier_id,
55
+ carrier_name=settings.carrier_name,
56
+ code=shipment_info.result.code,
57
+ message=shipment_info.result.message or "",
58
+ details=dict(shipment_id=shipment_info.shipmentID),
59
+ )
60
+ )
61
+ continue
62
+
63
+ # Extract tracking details
64
+ details = _extract_details(shipment_info, settings)
65
+ if details:
66
+ tracking_details.append(details)
67
+
68
+ return tracking_details, messages
69
+
70
+
71
+ def _extract_details(
72
+ shipment_info: hermes_res.ShipmentinfoType,
73
+ settings: provider_utils.Settings,
74
+ ) -> typing.Optional[models.TrackingDetails]:
75
+ """Extract tracking details from Hermes shipment info."""
76
+ if not shipment_info.shipmentID:
77
+ return None
78
+
79
+ # Get status events (already in chronological order, most recent last in API)
80
+ # Reverse to have most recent first for Karrio convention
81
+ status_list = list(reversed(shipment_info.status or []))
82
+
83
+ # Get latest event code for overall status
84
+ latest_code = status_list[0].code if status_list else None
85
+ overall_status = _match_status(latest_code) or provider_units.TrackingStatus.in_transit.name
86
+
87
+ # Build tracking events with all required fields per CARRIER_INTEGRATION_GUIDE.md
88
+ events = [
89
+ models.TrackingEvent(
90
+ date=lib.fdate(event.timestamp, "%Y-%m-%dT%H:%M:%S%z"),
91
+ time=lib.flocaltime(event.timestamp, "%Y-%m-%dT%H:%M:%S%z"),
92
+ description=event.description or "",
93
+ code=event.code,
94
+ location=lib.join(
95
+ event.scanningUnit.city if event.scanningUnit else None,
96
+ event.scanningUnit.countryCode if event.scanningUnit else None,
97
+ join=True,
98
+ separator=", ",
99
+ ),
100
+ # REQUIRED: timestamp in ISO 8601 format (already provided by Hermes)
101
+ timestamp=event.timestamp,
102
+ # REQUIRED: normalized status at event level
103
+ status=_match_status(event.code),
104
+ # Incident reason for exception events
105
+ reason=_match_reason(event.code),
106
+ )
107
+ for event in status_list
108
+ ]
109
+
110
+ # Build delivery forecast info if available
111
+ estimated_delivery = None
112
+ if shipment_info.deliveryForecast and shipment_info.deliveryForecast.date:
113
+ estimated_delivery = shipment_info.deliveryForecast.date
114
+
115
+ return models.TrackingDetails(
116
+ carrier_id=settings.carrier_id,
117
+ carrier_name=settings.carrier_name,
118
+ tracking_number=shipment_info.shipmentID,
119
+ events=events,
120
+ delivered=overall_status == "delivered",
121
+ status=overall_status,
122
+ estimated_delivery=estimated_delivery,
123
+ info=models.TrackingInfo(
124
+ carrier_tracking_link=shipment_info.trackingLink,
125
+ customer_name=None,
126
+ shipment_destination_country=(
127
+ shipment_info.receiverAddress.countryCode
128
+ if shipment_info.receiverAddress
129
+ else None
130
+ ),
131
+ shipment_destination_postal_code=(
132
+ str(shipment_info.receiverAddress.zipCode)
133
+ if shipment_info.receiverAddress and shipment_info.receiverAddress.zipCode
134
+ else None
135
+ ),
136
+ ),
137
+ meta=dict(
138
+ client_id=shipment_info.clientID,
139
+ client_reference=shipment_info.clientReference,
140
+ client_reference2=shipment_info.clientReference2,
141
+ part_number=shipment_info.partNumber,
142
+ international_shipment_id=shipment_info.internationalShipmentID,
143
+ ),
144
+ )
145
+
146
+
147
+ def tracking_request(
148
+ payload: models.TrackingRequest,
149
+ settings: provider_utils.Settings,
150
+ ) -> lib.Serializable:
151
+ """Create tracking request for Hermes Shipment Info API.
152
+
153
+ Hermes uses GET requests with query parameters, so we just return
154
+ the tracking numbers to be used as shipmentID query params.
155
+ """
156
+ return lib.Serializable(payload.tracking_numbers)
@@ -66,41 +66,41 @@ class ShippingOption(lib.Enum):
66
66
 
67
67
  # Hermes services as options
68
68
  hermes_tan_service = lib.OptionEnum("tanService", bool)
69
- hermes_limited_quantities = lib.OptionEnum("limitedQuantitiesService", bool)
69
+ hermes_limited_quantities = lib.OptionEnum("limitedQuantitiesService", bool, meta=dict(category="DANGEROUS_GOOD"))
70
70
  hermes_bulk_goods = lib.OptionEnum("bulkGoodService", bool)
71
- hermes_household_signature = lib.OptionEnum("householdSignatureService", bool)
71
+ hermes_household_signature = lib.OptionEnum("householdSignatureService", bool, meta=dict(category="SIGNATURE"))
72
72
  hermes_compact_parcel = lib.OptionEnum("compactParcelService", bool)
73
- hermes_next_day = lib.OptionEnum("nextDayService", bool)
74
- hermes_signature = lib.OptionEnum("signatureService", bool)
75
- hermes_redirection_prohibited = lib.OptionEnum("redirectionProhibitedService", bool)
76
- hermes_exclude_parcel_shop_auth = lib.OptionEnum("excludeParcelShopAuthorization", bool)
73
+ hermes_next_day = lib.OptionEnum("nextDayService", bool, meta=dict(category="DELIVERY_OPTIONS"))
74
+ hermes_signature = lib.OptionEnum("signatureService", bool, meta=dict(category="SIGNATURE"))
75
+ hermes_redirection_prohibited = lib.OptionEnum("redirectionProhibitedService", bool, meta=dict(category="DELIVERY_OPTIONS"))
76
+ hermes_exclude_parcel_shop_auth = lib.OptionEnum("excludeParcelShopAuthorization", bool, meta=dict(category="PUDO"))
77
77
  hermes_late_injection = lib.OptionEnum("lateInjectionService", bool)
78
78
 
79
79
  # Cash on delivery
80
- hermes_cod_amount = lib.OptionEnum("codAmount", float)
81
- hermes_cod_currency = lib.OptionEnum("codCurrency", str)
80
+ hermes_cod_amount = lib.OptionEnum("codAmount", float, meta=dict(category="COD"))
81
+ hermes_cod_currency = lib.OptionEnum("codCurrency", str, meta=dict(category="COD"))
82
82
 
83
83
  # Customer alert service
84
- hermes_notification_email = lib.OptionEnum("notificationEmail", str)
85
- hermes_notification_type = lib.OptionEnum("notificationType", str) # EMAIL, SMS, EMAIL_SMS
84
+ hermes_notification_email = lib.OptionEnum("notificationEmail", str, meta=dict(category="NOTIFICATION"))
85
+ hermes_notification_type = lib.OptionEnum("notificationType", str, meta=dict(category="NOTIFICATION")) # EMAIL, SMS, EMAIL_SMS
86
86
 
87
87
  # Stated day service
88
- hermes_stated_day = lib.OptionEnum("statedDay", str) # YYYY-MM-DD format
88
+ hermes_stated_day = lib.OptionEnum("statedDay", str, meta=dict(category="DELIVERY_OPTIONS")) # YYYY-MM-DD format
89
89
 
90
90
  # Stated time service
91
- hermes_time_slot = lib.OptionEnum("timeSlot", str) # FORENOON, NOON, AFTERNOON, EVENING
91
+ hermes_time_slot = lib.OptionEnum("timeSlot", str, meta=dict(category="DELIVERY_OPTIONS")) # FORENOON, NOON, AFTERNOON, EVENING
92
92
 
93
93
  # Ident service
94
- hermes_ident_id = lib.OptionEnum("identID", str)
95
- hermes_ident_type = lib.OptionEnum("identType", str) # GERMAN_IDENTITY_CARD, etc.
96
- hermes_ident_fsk = lib.OptionEnum("identVerifyFsk", str) # 18
97
- hermes_ident_birthday = lib.OptionEnum("identVerifyBirthday", str) # YYYY-MM-DD
94
+ hermes_ident_id = lib.OptionEnum("identID", str, meta=dict(category="SIGNATURE"))
95
+ hermes_ident_type = lib.OptionEnum("identType", str, meta=dict(category="SIGNATURE")) # GERMAN_IDENTITY_CARD, etc.
96
+ hermes_ident_fsk = lib.OptionEnum("identVerifyFsk", str, meta=dict(category="SIGNATURE")) # 18
97
+ hermes_ident_birthday = lib.OptionEnum("identVerifyBirthday", str, meta=dict(category="SIGNATURE")) # YYYY-MM-DD
98
98
 
99
99
  # Parcel shop delivery
100
- hermes_parcel_shop_id = lib.OptionEnum("psID", str)
101
- hermes_parcel_shop_selection_rule = lib.OptionEnum("psSelectionRule", str) # SELECT_BY_ID, SELECT_BY_RECEIVER_ADDRESS
102
- hermes_parcel_shop_customer_firstname = lib.OptionEnum("psCustomerFirstName", str)
103
- hermes_parcel_shop_customer_lastname = lib.OptionEnum("psCustomerLastName", str)
100
+ hermes_parcel_shop_id = lib.OptionEnum("psID", str, meta=dict(category="PUDO"))
101
+ hermes_parcel_shop_selection_rule = lib.OptionEnum("psSelectionRule", str, meta=dict(category="PUDO")) # SELECT_BY_ID, SELECT_BY_RECEIVER_ADDRESS
102
+ hermes_parcel_shop_customer_firstname = lib.OptionEnum("psCustomerFirstName", str, meta=dict(category="PUDO"))
103
+ hermes_parcel_shop_customer_lastname = lib.OptionEnum("psCustomerLastName", str, meta=dict(category="PUDO"))
104
104
 
105
105
  # Multipart service
106
106
  hermes_part_number = lib.OptionEnum("partNumber", int)
@@ -128,15 +128,247 @@ def shipping_options_initializer(
128
128
 
129
129
 
130
130
  class TrackingStatus(lib.Enum):
131
- """Hermes tracking status mapping."""
132
-
133
- on_hold = ["on_hold"]
134
- delivered = ["delivered"]
135
- in_transit = ["in_transit"]
136
- delivery_failed = ["delivery_failed"]
137
- delivery_delayed = ["delivery_delayed"]
138
- out_for_delivery = ["out_for_delivery"]
139
- ready_for_pickup = ["ready_for_pickup"]
131
+ """Hermes tracking status mapping.
132
+
133
+ Maps Hermes 2x2 event codes (4-digit) to Karrio unified statuses.
134
+ Based on Hermes Germany Eventcodes.csv.
135
+ """
136
+
137
+ pending = [
138
+ "0000", # Die Sendung wurde Hermes elektronisch angekündigt
139
+ "0600", # Shipment has not arrived at the depot (1st notice)
140
+ "0700", # Shipment has not arrived at the depot (2nd notice)
141
+ "0800", # Shipment has not arrived at the depot (7 days)
142
+ "0900", # Shipment has not arrived at the depot (28 days)
143
+ ]
144
+ picked_up = [
145
+ "1000", # Die Sendung hat das Lager des Auftraggebers verlassen
146
+ "1900", # Shipment accepted after collection
147
+ "1901", # Shipment arrived at branch by self-delivery
148
+ "1910", # Shipment received on tour
149
+ ]
150
+ in_transit = [
151
+ "1510", # Sendung am LC umgeschlagen (sorted at logistics center)
152
+ "1610", # Shipment in international transit
153
+ "1710", # Customs export created
154
+ "1720", # Customs clearance completed
155
+ "1810", # Handed to partner carrier (international)
156
+ "1820", # Handed to partner carrier (international)
157
+ "2000", # Die Sendung ist eingetroffen (arrived at depot)
158
+ "2100", # Arrived without advice data
159
+ "2300", # Automatic shipment received
160
+ "2400", # Shipment handed in at ParcelShop
161
+ ]
162
+ out_for_delivery = [
163
+ "3000", # Die Sendung ist auf Zustelltour gegangen
164
+ "3010", # Shipment has left depot on tour
165
+ "3300", # Sorted for delivery tour (automatic)
166
+ ]
167
+ ready_for_pickup = [
168
+ "3410", # Die Sendung liegt im PaketShop zur Abholung bereit
169
+ "3430", # Handed over to island carrier
170
+ ]
171
+ delivered = [
172
+ "3500", # Die Sendung wurde zugestellt
173
+ "3510", # Shipment delivered (with scanner)
174
+ "3511", # Delivered in letterbox
175
+ "3520", # Shipment delivered (without scanner)
176
+ "3530", # Collected by recipient from ParcelShop
177
+ "7500", # Return shipment arrived at client
178
+ ]
179
+ delivery_failed = [
180
+ "3710", # Annahmeverweigerung (refused)
181
+ "3715", # COD not paid
182
+ "3720", # Address not found
183
+ "3731", # Recipient not present (1st attempt)
184
+ "3732", # Recipient not present (2nd attempt)
185
+ "3733", # Recipient not present (3rd attempt)
186
+ "3734", # Recipient not present (4th attempt)
187
+ "3740", # Damage detected
188
+ "3750", # Tour cancellation
189
+ "3751", # Incorrect TAN (1st attempt)
190
+ "3752", # Incorrect TAN (2nd attempt)
191
+ "3753", # Incorrect TAN (3rd attempt)
192
+ "3754", # Incorrect TAN (4th attempt)
193
+ "3760", # Return shipment collected
194
+ "3761", # Return shipment taken
195
+ "3780", # Misdirected
196
+ "3782", # Ident failed - photo mismatch
197
+ "3783", # Ident failed - name mismatch
198
+ "3784", # Ident failed - DOB mismatch
199
+ "3785", # Ident failed - document mismatch
200
+ "3786", # Ident failed - PIN code
201
+ "3787", # Ident failed - age verification
202
+ "3795", # Shipment stopped
203
+ ]
204
+ on_hold = [
205
+ "1730", # Held by customs
206
+ "1751", # Rejected by customs
207
+ "4100", # Sendung wird aufbewahrt (shipment stored)
208
+ "4500", # Stored (stocktaking)
209
+ "4610", # ParcelShop - high volume, cannot pick up
210
+ "4620", # ParcelShop - shipment not available
211
+ "4630", # ParcelShop - shipment not found
212
+ "4690", # Status corrected at ParcelShop
213
+ ]
214
+ delivery_delayed = [
215
+ "4010", # Return - refused
216
+ "4015", # Return - COD not paid
217
+ "4020", # Return - address not found
218
+ "4024", # Return - too large/heavy for ParcelShop
219
+ "4025", # Shipment returned to depot
220
+ "4031", # Return - N1 (1st attempt failed)
221
+ "4032", # Return - N2 (2nd attempt failed)
222
+ "4033", # Return - N3 (3rd attempt failed)
223
+ "4034", # Return - N4 (4th attempt failed)
224
+ "4035", # Return - not collected from ParcelShop
225
+ "4040", # Return - damage
226
+ "4050", # Return - tour cancellation
227
+ "4051", # Return - TAN 1
228
+ "4052", # Return - TAN 2
229
+ "4053", # Return - TAN 3
230
+ "4054", # Return - TAN 4
231
+ "4060", # Return shipment received (pickup)
232
+ "4061", # Return shipment received (take-away)
233
+ "4062", # Return handed in at ParcelShop
234
+ "4070", # Tour departure cancelled
235
+ "4072", # Return cancelled (correction)
236
+ "4080", # Misdirected
237
+ "4081", # Ident failed
238
+ "4082", # Ident failed - photo
239
+ "4083", # Ident failed - name
240
+ "4084", # Ident failed - DOB
241
+ "4085", # Ident failed - document
242
+ "4086", # Ident failed - PIN
243
+ "4087", # Ident failed - age
244
+ "4095", # Delivery stopped
245
+ ]
246
+ return_to_sender = [
247
+ "1520", # Return shipment sorted
248
+ "6080", # Rückversand (return shipment)
249
+ "6081", # Return - refused
250
+ "6082", # Return - address not readable
251
+ "6083", # Return - address not found
252
+ "6084", # Return - receiver not met
253
+ "6085", # Return - damage
254
+ "6086", # Return - sorting error
255
+ "6087", # Return - technical issue
256
+ "6088", # Redirected at receiver request
257
+ "6089", # Return - returns
258
+ "6090", # Forwarded to logistics center
259
+ "6092", # Return - ident failed
260
+ "6093", # Return - not collected from ParcelShop
261
+ "6094", # Return - COD not paid
262
+ "6096", # Return - too large/heavy for ParcelShop
263
+ "6098", # Return - incorrect TAN
264
+ "6099", # Return - delivery stopped
265
+ ]
266
+
267
+
268
+ class TrackingIncidentReason(lib.Enum):
269
+ """Maps Hermes exception codes to normalized incident reasons.
270
+
271
+ Based on Hermes Germany Eventcodes.csv.
272
+ Maps carrier-specific exception/status codes to standardized
273
+ incident reasons for tracking events.
274
+ """
275
+
276
+ # Consignee-caused issues
277
+ consignee_refused = [
278
+ "3710", # Annahmeverweigerung
279
+ "4010", # Return - refused
280
+ "6081", # Return - refused
281
+ ]
282
+ consignee_not_home = [
283
+ "3731", # Recipient not present (1st attempt)
284
+ "3732", # Recipient not present (2nd attempt)
285
+ "3733", # Recipient not present (3rd attempt)
286
+ "3734", # Recipient not present (4th attempt)
287
+ "4031", # Return - N1
288
+ "4032", # Return - N2
289
+ "4033", # Return - N3
290
+ "4034", # Return - N4
291
+ "6084", # Return - receiver not met
292
+ ]
293
+ consignee_incorrect_address = [
294
+ "3720", # Address not found
295
+ "4020", # Return - address not found
296
+ "6082", # Return - address not readable
297
+ "6083", # Return - address not found
298
+ ]
299
+ consignee_not_available = [
300
+ "4035", # Not collected from ParcelShop
301
+ "6093", # Return - not collected from ParcelShop
302
+ ]
303
+ consignee_cod_unpaid = [
304
+ "3715", # COD not paid
305
+ "4015", # Return - COD not paid
306
+ "6094", # Return - COD not paid
307
+ ]
308
+ consignee_id_failed = [
309
+ "3782", # Ident failed - photo mismatch
310
+ "3783", # Ident failed - name mismatch
311
+ "3784", # Ident failed - DOB mismatch
312
+ "3785", # Ident failed - document mismatch
313
+ "3786", # Ident failed - PIN code
314
+ "3787", # Ident failed - age verification
315
+ "4081", # Ident failed
316
+ "4082", # Ident failed - photo
317
+ "4083", # Ident failed - name
318
+ "4084", # Ident failed - DOB
319
+ "4085", # Ident failed - document
320
+ "4086", # Ident failed - PIN
321
+ "4087", # Ident failed - age
322
+ "6092", # Return - ident failed
323
+ ]
324
+ consignee_tan_invalid = [
325
+ "3751", # Incorrect TAN (1st attempt)
326
+ "3752", # Incorrect TAN (2nd attempt)
327
+ "3753", # Incorrect TAN (3rd attempt)
328
+ "3754", # Incorrect TAN (4th attempt)
329
+ "4051", # Return - TAN 1
330
+ "4052", # Return - TAN 2
331
+ "4053", # Return - TAN 3
332
+ "4054", # Return - TAN 4
333
+ "6098", # Return - incorrect TAN
334
+ ]
335
+
336
+ # Carrier-caused issues
337
+ carrier_damaged_parcel = [
338
+ "3740", # Damage detected
339
+ "4040", # Return - damage
340
+ "6085", # Return - damage
341
+ ]
342
+ carrier_sorting_error = [
343
+ "3780", # Misdirected
344
+ "4080", # Misdirected
345
+ "6086", # Return - sorting error
346
+ "6087", # Return - technical issue
347
+ ]
348
+ carrier_not_enough_time = [
349
+ "3750", # Tour cancellation
350
+ "4050", # Return - tour cancellation
351
+ "4070", # Tour departure cancelled
352
+ ]
353
+ carrier_parcel_too_large = [
354
+ "4024", # Return - too large/heavy for ParcelShop
355
+ "6096", # Return - too large/heavy for ParcelShop
356
+ ]
357
+
358
+ # Customs-related issues
359
+ customs_delay = [
360
+ "1730", # Held by customs
361
+ ]
362
+ customs_rejected = [
363
+ "1751", # Rejected by customs
364
+ ]
365
+
366
+ # Shipment stopped
367
+ shipment_stopped = [
368
+ "3795", # Shipment stopped
369
+ "4095", # Delivery stopped
370
+ "6099", # Return - delivery stopped
371
+ ]
140
372
 
141
373
 
142
374
  class LabelType(lib.StrEnum):
@@ -100,3 +100,29 @@ def login(settings: Settings):
100
100
  seconds=float(response.get("expires_in", 3600))
101
101
  )
102
102
  return {**response, "expiry": lib.fdatetime(expiry)}
103
+
104
+
105
+ def get_access_token(settings: Settings) -> str:
106
+ """Get access token from settings."""
107
+ token_data = settings.access_token
108
+ return token_data.get("access_token") if isinstance(token_data, dict) else token_data
109
+
110
+
111
+ def prepare_shipment_data(request: lib.Serializable) -> tuple:
112
+ """Prepare shipment request data and multi-piece flag."""
113
+ requests_data = request.serialize()
114
+ is_multi_piece = request.ctx.get("is_multi_piece", False) if request.ctx else False
115
+ requests_data = requests_data if isinstance(requests_data, list) else [requests_data]
116
+ return requests_data, is_multi_piece
117
+
118
+
119
+ def inject_parent_shipment_id(req_data: dict, parent_id: str) -> dict:
120
+ """Inject parentShipmentOrderID for multi-piece packages 2+."""
121
+ if req_data.get("service", {}).get("multipartService"):
122
+ req_data["service"]["multipartService"]["parentShipmentOrderID"] = parent_id
123
+ return req_data
124
+
125
+
126
+ def extract_shipment_order_id(response: dict) -> str:
127
+ """Extract shipmentOrderID from response for multi-piece linking."""
128
+ return response.get("shipmentOrderID")
@@ -41,3 +41,13 @@ from karrio.schemas.hermes.pickup_create_request import (
41
41
  from karrio.schemas.hermes.pickup_create_response import PickupCreateResponseType
42
42
  from karrio.schemas.hermes.pickup_cancel_request import PickupCancelRequestType
43
43
  from karrio.schemas.hermes.pickup_cancel_response import PickupCancelResponseType
44
+ from karrio.schemas.hermes.tracking_response import (
45
+ TrackingResponseType,
46
+ ShipmentinfoType,
47
+ ResultType,
48
+ StatusType,
49
+ ScanningUnitType,
50
+ ReceiverAddressType as TrackingReceiverAddressType,
51
+ DeliveryForecastType,
52
+ TimeSlotType,
53
+ )
@@ -0,0 +1,64 @@
1
+ import attr
2
+ import jstruct
3
+ import typing
4
+
5
+
6
+ @attr.s(auto_attribs=True)
7
+ class TimeSlotType:
8
+ timeSlotfrom: typing.Optional[str] = None
9
+ to: typing.Optional[str] = None
10
+
11
+
12
+ @attr.s(auto_attribs=True)
13
+ class DeliveryForecastType:
14
+ fixed: typing.Optional[bool] = None
15
+ date: typing.Optional[str] = None
16
+ timeSlot: typing.Optional[TimeSlotType] = jstruct.JStruct[TimeSlotType]
17
+
18
+
19
+ @attr.s(auto_attribs=True)
20
+ class ReceiverAddressType:
21
+ city: typing.Optional[str] = None
22
+ zipCode: typing.Optional[int] = None
23
+ countryCode: typing.Optional[str] = None
24
+
25
+
26
+ @attr.s(auto_attribs=True)
27
+ class ResultType:
28
+ code: typing.Optional[str] = None
29
+ message: typing.Optional[str] = None
30
+
31
+
32
+ @attr.s(auto_attribs=True)
33
+ class ScanningUnitType:
34
+ name: typing.Optional[str] = None
35
+ city: typing.Optional[str] = None
36
+ countryCode: typing.Optional[str] = None
37
+
38
+
39
+ @attr.s(auto_attribs=True)
40
+ class StatusType:
41
+ timestamp: typing.Optional[str] = None
42
+ code: typing.Optional[str] = None
43
+ description: typing.Optional[str] = None
44
+ scanningUnit: typing.Optional[ScanningUnitType] = jstruct.JStruct[ScanningUnitType]
45
+
46
+
47
+ @attr.s(auto_attribs=True)
48
+ class ShipmentinfoType:
49
+ shipmentID: typing.Optional[str] = None
50
+ partNumber: typing.Optional[int] = None
51
+ clientID: typing.Optional[int] = None
52
+ clientReference: typing.Optional[str] = None
53
+ clientReference2: typing.Optional[int] = None
54
+ internationalShipmentID: typing.Optional[str] = None
55
+ trackingLink: typing.Optional[str] = None
56
+ result: typing.Optional[ResultType] = jstruct.JStruct[ResultType]
57
+ receiverAddress: typing.Optional[ReceiverAddressType] = jstruct.JStruct[ReceiverAddressType]
58
+ deliveryForecast: typing.Optional[DeliveryForecastType] = jstruct.JStruct[DeliveryForecastType]
59
+ status: typing.Optional[typing.List[StatusType]] = jstruct.JList[StatusType]
60
+
61
+
62
+ @attr.s(auto_attribs=True)
63
+ class TrackingResponseType:
64
+ shipmentinfo: typing.Optional[typing.List[ShipmentinfoType]] = jstruct.JList[ShipmentinfoType]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karrio_hermes
3
- Version: 2026.1
3
+ Version: 2026.1.3
4
4
  Summary: Karrio - Hermes Shipping Extension
5
5
  Author-email: karrio <hello@karrio.io>
6
6
  License-Expression: LGPL-3.0
@@ -0,0 +1,29 @@
1
+ karrio/mappers/hermes/__init__.py,sha256=3WcvIpOkQAy1p54BhLGfmi1hyY9Dhe0qSG2yZY9mPes,145
2
+ karrio/mappers/hermes/mapper.py,sha256=4xOrR9eFX-MKv6LKB32WD8hZuhnscB_OTgeKVVrGo6Q,2700
3
+ karrio/mappers/hermes/proxy.py,sha256=zGbLwJTwunrWp8b1Lg_hs3AwxPSd8Ogy6sydl99YaSs,4492
4
+ karrio/mappers/hermes/settings.py,sha256=4BTM7ncSQqTBP6J_oJTksBDGhaFHOWoHDnpq0iOWHVo,1151
5
+ karrio/plugins/hermes/__init__.py,sha256=zUfOhtzuErd1H1ICL4rQt68e6BWRQf_B6_kW5UVzd9M,1020
6
+ karrio/providers/hermes/__init__.py,sha256=wMiVnPz7EgKdbu1pGBuvalfyuHj5QwSeMijjvEP6q4w,590
7
+ karrio/providers/hermes/error.py,sha256=YWmYC_l7_nyJCxiqM0Kgf6yV9IpJ04tkdZyyMT7QnkY,2974
8
+ karrio/providers/hermes/tracking.py,sha256=F_NYX_G7hnQe04zPbeXfc_IuN69XLOjYC9v9ezHB0Kg,5958
9
+ karrio/providers/hermes/units.py,sha256=voG1oHUfF2Fjzo0w7FOVqrNGmGLTCWDai1zlMng-9mQ,18148
10
+ karrio/providers/hermes/utils.py,sha256=NOfS_K0Zw7_wxJGBJfkdXwZP6hs0bsaZye5uypztGc4,4163
11
+ karrio/providers/hermes/pickup/__init__.py,sha256=E59ks-qJAsNmfQgaZ7X0tZuBetITsdRSMJUAZnqxabg,307
12
+ karrio/providers/hermes/pickup/cancel.py,sha256=JYVGL9decPB0QOHsjrYU5cwT_4vzrpsaEjapC9F3t7A,1587
13
+ karrio/providers/hermes/pickup/create.py,sha256=ICSG7HgF45h8XY0Ypc8voA5TWFSVmysGerh1F-fha24,3993
14
+ karrio/providers/hermes/shipment/__init__.py,sha256=B_zc5eaS4st1bnIyB_RwYY9XJJ9DyjB1Tb0SaIEJRJ8,209
15
+ karrio/providers/hermes/shipment/create.py,sha256=DAkC5O9XHs6QOlLKBTQ8zKQlLNf6h5Ty29_qFhMSBWM,16677
16
+ karrio/schemas/hermes/__init__.py,sha256=zcJLsZ0HvWk9q7J07BAel3ib_eXFoN6_5joxbdzBLV0,1530
17
+ karrio/schemas/hermes/error_response.py,sha256=BJhI7P8wursdYsYACfDLT8cs-bsnKlJDs0OzurYMPR4,343
18
+ karrio/schemas/hermes/pickup_cancel_request.py,sha256=rZgm3BpL3iziO_TC_y-cAELMsWjXyvRYWmXYyExhdR0,148
19
+ karrio/schemas/hermes/pickup_cancel_response.py,sha256=9-STL_Aq0EvXWrNMfvc4DiPK658e1JPp0uhW695XOGI,397
20
+ karrio/schemas/hermes/pickup_create_request.py,sha256=0V14LMl0XVLOZ_zXRzoTLUZ8nB_TZHCmr2G03NPl4tw,1375
21
+ karrio/schemas/hermes/pickup_create_response.py,sha256=sAgq92J0TZOUolhMcvDkp6qSRvxOqWCIZHNQWQN7KeU,397
22
+ karrio/schemas/hermes/shipment_request.py,sha256=FMqrSlCDG4eGDkuqPDc_DDCbtV_vGRyrRBFCiNTFkmM,7415
23
+ karrio/schemas/hermes/shipment_response.py,sha256=BI2kUemG-wTzAFpok9KEnlwjm6eK_LemBA_Cyga_e6A,3477
24
+ karrio/schemas/hermes/tracking_response.py,sha256=Ye4Uo6UEnEy-A5VX4MDmyyePXZMM_meMiecJlht9qMc,2013
25
+ karrio_hermes-2026.1.3.dist-info/METADATA,sha256=P2iafkpDRgz5fQhi9oQvv_GWYDj2N23CwqdgPnnqvhc,982
26
+ karrio_hermes-2026.1.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
27
+ karrio_hermes-2026.1.3.dist-info/entry_points.txt,sha256=R8XDrYBqWXTPqaO0iTi4Buz8iQiiBiuHkJVNtZIXTkU,57
28
+ karrio_hermes-2026.1.3.dist-info/top_level.txt,sha256=9Nasa6abG7pPPG8MGzlemnqw1ohIqgouzQ7HGBnOFLg,27
29
+ karrio_hermes-2026.1.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,27 +0,0 @@
1
- karrio/mappers/hermes/__init__.py,sha256=3WcvIpOkQAy1p54BhLGfmi1hyY9Dhe0qSG2yZY9mPes,145
2
- karrio/mappers/hermes/mapper.py,sha256=0fZbXPHGg5R4bbhRyJbzfEGNiKXSINW1FJQnhz-4MRM,2258
3
- karrio/mappers/hermes/proxy.py,sha256=FG_OcaMEXiZxZp37m25pgrOtcdCziVk3Kft9HjoEnT8,2858
4
- karrio/mappers/hermes/settings.py,sha256=4BTM7ncSQqTBP6J_oJTksBDGhaFHOWoHDnpq0iOWHVo,1151
5
- karrio/plugins/hermes/__init__.py,sha256=zUfOhtzuErd1H1ICL4rQt68e6BWRQf_B6_kW5UVzd9M,1020
6
- karrio/providers/hermes/__init__.py,sha256=lwP0P714ipPISsbd_6xXZ4oaCK32yTEHktP8yxmF4NQ,490
7
- karrio/providers/hermes/error.py,sha256=YWmYC_l7_nyJCxiqM0Kgf6yV9IpJ04tkdZyyMT7QnkY,2974
8
- karrio/providers/hermes/units.py,sha256=MO1ap8ItQUlwfTTx7_CqxJqx96KY-FyLm4pMSIXRg8g,8665
9
- karrio/providers/hermes/utils.py,sha256=84N70gICmOmP2vjYmnzJbjzLhFaEHvXLKgHALZiE-2k,3077
10
- karrio/providers/hermes/pickup/__init__.py,sha256=E59ks-qJAsNmfQgaZ7X0tZuBetITsdRSMJUAZnqxabg,307
11
- karrio/providers/hermes/pickup/cancel.py,sha256=JYVGL9decPB0QOHsjrYU5cwT_4vzrpsaEjapC9F3t7A,1587
12
- karrio/providers/hermes/pickup/create.py,sha256=q4AbBqI-_oXglD2DffJpqBp93zZo2hz-gf32DtX5lg8,3556
13
- karrio/providers/hermes/shipment/__init__.py,sha256=B_zc5eaS4st1bnIyB_RwYY9XJJ9DyjB1Tb0SaIEJRJ8,209
14
- karrio/providers/hermes/shipment/create.py,sha256=UcKTavylyrdbeFmzXY7pqCUI1lfOxmMO2Cr07xoB1YY,13046
15
- karrio/schemas/hermes/__init__.py,sha256=yw5kUm6WMQ_UJEVW-LQUY-916m7Y-qxp0yLbqIMb2b4,1272
16
- karrio/schemas/hermes/error_response.py,sha256=BJhI7P8wursdYsYACfDLT8cs-bsnKlJDs0OzurYMPR4,343
17
- karrio/schemas/hermes/pickup_cancel_request.py,sha256=rZgm3BpL3iziO_TC_y-cAELMsWjXyvRYWmXYyExhdR0,148
18
- karrio/schemas/hermes/pickup_cancel_response.py,sha256=9-STL_Aq0EvXWrNMfvc4DiPK658e1JPp0uhW695XOGI,397
19
- karrio/schemas/hermes/pickup_create_request.py,sha256=0V14LMl0XVLOZ_zXRzoTLUZ8nB_TZHCmr2G03NPl4tw,1375
20
- karrio/schemas/hermes/pickup_create_response.py,sha256=sAgq92J0TZOUolhMcvDkp6qSRvxOqWCIZHNQWQN7KeU,397
21
- karrio/schemas/hermes/shipment_request.py,sha256=FMqrSlCDG4eGDkuqPDc_DDCbtV_vGRyrRBFCiNTFkmM,7415
22
- karrio/schemas/hermes/shipment_response.py,sha256=BI2kUemG-wTzAFpok9KEnlwjm6eK_LemBA_Cyga_e6A,3477
23
- karrio_hermes-2026.1.dist-info/METADATA,sha256=yF6_59XBxGBqORO5gj9Gjp5TJTdk4GOUJ_TxlGzA4eE,980
24
- karrio_hermes-2026.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
- karrio_hermes-2026.1.dist-info/entry_points.txt,sha256=R8XDrYBqWXTPqaO0iTi4Buz8iQiiBiuHkJVNtZIXTkU,57
26
- karrio_hermes-2026.1.dist-info/top_level.txt,sha256=9Nasa6abG7pPPG8MGzlemnqw1ohIqgouzQ7HGBnOFLg,27
27
- karrio_hermes-2026.1.dist-info/RECORD,,