karrio-dhl-poland 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,3 @@
1
+ from karrio.mappers.dhl_poland.mapper import Mapper
2
+ from karrio.mappers.dhl_poland.proxy import Proxy
3
+ from karrio.mappers.dhl_poland.settings import Settings
@@ -0,0 +1,51 @@
1
+ """Karrio DHL Poland 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.universal.providers.rating as universal_provider
8
+ import karrio.providers.dhl_poland as provider
9
+ import karrio.mappers.dhl_poland.settings as provider_settings
10
+
11
+
12
+ class Mapper(mapper.Mapper):
13
+ settings: provider_settings.Settings
14
+
15
+ def create_rate_request(self, payload: models.RateRequest) -> lib.Serializable:
16
+ return universal_provider.rate_request(payload, self.settings)
17
+
18
+ def create_tracking_request(
19
+ self, payload: models.TrackingRequest
20
+ ) -> lib.Serializable:
21
+ return provider.tracking_request(payload, self.settings)
22
+
23
+ def create_shipment_request(
24
+ self, payload: models.ShipmentRequest
25
+ ) -> lib.Serializable:
26
+ return provider.shipment_request(payload, self.settings)
27
+
28
+ def create_cancel_shipment_request(
29
+ self, payload: models.ShipmentCancelRequest
30
+ ) -> lib.Serializable:
31
+ return provider.shipment_cancel_request(payload, self.settings)
32
+
33
+ def parse_cancel_shipment_response(
34
+ self, response: lib.Deserializable
35
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
36
+ return provider.parse_shipment_cancel_response(response, self.settings)
37
+
38
+ def parse_rate_response(
39
+ self, response: lib.Deserializable
40
+ ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
41
+ return universal_provider.parse_rate_response(response, self.settings)
42
+
43
+ def parse_shipment_response(
44
+ self, response: lib.Deserializable
45
+ ) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
46
+ return provider.parse_shipment_response(response, self.settings)
47
+
48
+ def parse_tracking_response(
49
+ self, response: lib.Deserializable
50
+ ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
51
+ return provider.parse_tracking_response(response, self.settings)
@@ -0,0 +1,59 @@
1
+ """Karrio DHL Poland client proxy module."""
2
+ import karrio.lib as lib
3
+ import karrio.api.proxy as proxy
4
+ import karrio.universal.mappers.rating_proxy as rating_proxy
5
+ import karrio.mappers.dhl_poland.settings as provider_settings
6
+
7
+
8
+ class Proxy(rating_proxy.RatingMixinProxy, proxy.Proxy):
9
+ settings: provider_settings.Settings
10
+
11
+ def _send_request(self, request: lib.Serializable, soapaction: str) -> str:
12
+ return lib.request(
13
+ url=f"{self.settings.server_url}",
14
+ data=request.serialize(),
15
+ trace=self.trace_as("xml"),
16
+ method="POST",
17
+ headers={
18
+ "Content-Type": "text/xml; charset=utf-8",
19
+ "soapaction": soapaction,
20
+ },
21
+ )
22
+
23
+ def get_rates(self, request: lib.Serializable) -> lib.Deserializable:
24
+ return super().get_rates(request)
25
+
26
+ def get_tracking(self, requests: lib.Serializable) -> lib.Deserializable:
27
+ responses = lib.run_asynchronously(
28
+ lambda request: dict(
29
+ number=request[0],
30
+ data=self._send_request(
31
+ lib.Serializable(request[1]),
32
+ soapaction=f"{self.settings.server_url}#getTrackAndTraceInfo",
33
+ ),
34
+ ),
35
+ requests.serialize().items(),
36
+ )
37
+
38
+ return lib.Deserializable(
39
+ responses,
40
+ lambda results: {
41
+ result["number"]: lib.to_element(result["data"]) for result in results
42
+ },
43
+ )
44
+
45
+ def create_shipment(self, request: lib.Serializable) -> lib.Deserializable:
46
+ response = self._send_request(
47
+ request,
48
+ soapaction=f"{self.settings.server_url}#createShipment",
49
+ )
50
+
51
+ return lib.Deserializable(response, lib.to_element)
52
+
53
+ def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable:
54
+ response = self._send_request(
55
+ request,
56
+ soapaction=f"{self.settings.server_url}#deleteShipment",
57
+ )
58
+
59
+ return lib.Deserializable(response, lib.to_element)
@@ -0,0 +1,34 @@
1
+ """Karrio DHL Parcel Poland client settings."""
2
+
3
+ import attr
4
+ import typing
5
+ import jstruct
6
+ import karrio.core.models as models
7
+ import karrio.providers.dhl_poland.units as provider_units
8
+ import karrio.providers.dhl_poland.utils as provider_utils
9
+ from karrio.universal.mappers.rating_proxy import RatingMixinSettings
10
+
11
+
12
+ @attr.s(auto_attribs=True)
13
+ class Settings(provider_utils.Settings, RatingMixinSettings):
14
+ """DHL Parcel Poland connection settings."""
15
+
16
+ username: str # type: ignore
17
+ password: str # type: ignore
18
+ account_number: str = None
19
+
20
+ id: str = None
21
+ test_mode: bool = False
22
+ carrier_id: str = "dhl_poland"
23
+ account_country_code: str = "PL"
24
+ metadata: dict = {}
25
+ config: dict = {}
26
+
27
+ services: typing.List[models.ServiceLevel] = jstruct.JList[models.ServiceLevel, False, dict(default=provider_units.DEFAULT_SERVICES)] # type: ignore
28
+
29
+ @property
30
+ def shipping_services(self) -> typing.List[models.ServiceLevel]:
31
+ if any(self.services or []):
32
+ return self.services
33
+
34
+ return provider_units.DEFAULT_SERVICES
@@ -0,0 +1,23 @@
1
+ import karrio.core.metadata as metadata
2
+ import karrio.mappers.dhl_poland as mappers
3
+ from karrio.providers.dhl_poland import units
4
+
5
+
6
+ METADATA = metadata.PluginMetadata(
7
+ status="production-ready",
8
+ id="dhl_poland",
9
+ label="DHL Parcel Poland",
10
+ # Integrations
11
+ Mapper=mappers.Mapper,
12
+ Proxy=mappers.Proxy,
13
+ Settings=mappers.Settings,
14
+ # Data Units
15
+ services=units.Service,
16
+ options=units.ShippingOption,
17
+ packaging_types=units.PackagingType,
18
+ service_levels=units.DEFAULT_SERVICES,
19
+ # New fields
20
+ website="https://dhl24.com.pl/en",
21
+ documentation="https://dhl24.com.pl/en/webapi2/doc.html",
22
+ description="Global Logistics and International Shipping Poland.",
23
+ )
@@ -0,0 +1,11 @@
1
+ from karrio.providers.dhl_poland.utils import Settings
2
+ from karrio.providers.dhl_poland.shipment import (
3
+ parse_shipment_cancel_response,
4
+ parse_shipment_response,
5
+ shipment_cancel_request,
6
+ shipment_request,
7
+ )
8
+ from karrio.providers.dhl_poland.tracking import (
9
+ parse_tracking_response,
10
+ tracking_request,
11
+ )
@@ -0,0 +1,21 @@
1
+ from typing import List
2
+ from pysoap.envelope import Fault
3
+ from karrio.core.models import Message
4
+ from karrio.core.utils import Element, XP
5
+ from karrio.providers.dhl_poland.utils import Settings
6
+
7
+
8
+ def parse_error_response(
9
+ response: Element, settings: Settings, details: dict = None
10
+ ) -> List[Message]:
11
+ errors = XP.find("Fault", response, Fault)
12
+ return [
13
+ Message(
14
+ carrier_id=settings.carrier_id,
15
+ carrier_name=settings.carrier_name,
16
+ message=error.faultstring,
17
+ code=error.faultcode,
18
+ details=details,
19
+ )
20
+ for error in errors
21
+ ]
@@ -0,0 +1,8 @@
1
+ from karrio.providers.dhl_poland.shipment.create import (
2
+ parse_shipment_response,
3
+ shipment_request,
4
+ )
5
+ from karrio.providers.dhl_poland.shipment.cancel import (
6
+ parse_shipment_cancel_response,
7
+ shipment_cancel_request,
8
+ )
@@ -0,0 +1,55 @@
1
+ from typing import List, Tuple
2
+ from karrio.schemas.dhl_poland.services import (
3
+ deleteShipment,
4
+ DeleteShipmentRequest,
5
+ )
6
+ from karrio.core.models import ShipmentCancelRequest, ConfirmationDetails, Message
7
+ from karrio.core.utils import (
8
+ create_envelope,
9
+ Envelope,
10
+ Element,
11
+ Serializable,
12
+ )
13
+ from karrio.providers.dhl_poland.error import parse_error_response
14
+ from karrio.providers.dhl_poland.utils import Settings
15
+ import karrio.lib as lib
16
+
17
+
18
+ def parse_shipment_cancel_response(
19
+ _response: lib.Deserializable[Element], settings: Settings
20
+ ) -> Tuple[ConfirmationDetails, List[Message]]:
21
+ response = _response.deserialize()
22
+ errors = parse_error_response(response, settings)
23
+ success = len(errors) == 0
24
+ confirmation: ConfirmationDetails = (
25
+ ConfirmationDetails(
26
+ carrier_id=settings.carrier_id,
27
+ carrier_name=settings.carrier_name,
28
+ success=success,
29
+ operation="Cancel Shipment",
30
+ )
31
+ if success
32
+ else None
33
+ )
34
+
35
+ return confirmation, errors
36
+
37
+
38
+ def shipment_cancel_request(
39
+ payload: ShipmentCancelRequest, settings: Settings
40
+ ) -> Serializable:
41
+ request = create_envelope(
42
+ body_content=deleteShipment(
43
+ authData=settings.auth_data,
44
+ shipment=DeleteShipmentRequest(
45
+ shipmentIdentificationNumber=payload.shipment_identifier
46
+ ),
47
+ )
48
+ )
49
+
50
+ return Serializable(
51
+ request,
52
+ lambda request: settings.serialize(
53
+ request, "deleteShipment", settings.server_url
54
+ ),
55
+ )
@@ -0,0 +1,307 @@
1
+ import karrio.schemas.dhl_poland.services as dhl
2
+ import time
3
+ import typing
4
+ import karrio.lib as lib
5
+ import karrio.core.units as units
6
+ import karrio.core.models as models
7
+ import karrio.providers.dhl_poland.error as provider_error
8
+ import karrio.providers.dhl_poland.units as provider_units
9
+ import karrio.providers.dhl_poland.utils as provider_utils
10
+
11
+
12
+ def parse_shipment_response(
13
+ _response: lib.Deserializable[lib.Element], settings: provider_utils.Settings
14
+ ) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
15
+ response = _response.deserialize()
16
+ errors = provider_error.parse_error_response(response, settings)
17
+ shipment = (
18
+ _extract_details(response, settings)
19
+ if lib.find_element("createShipmentResult", response, first=True) is not None
20
+ else None
21
+ )
22
+
23
+ return shipment, errors
24
+
25
+
26
+ def _extract_details(
27
+ response: lib.Element,
28
+ settings: provider_utils.Settings,
29
+ ) -> models.ShipmentDetails:
30
+ shipment: dhl.CreateShipmentResponse = lib.find_element(
31
+ "createShipmentResult", response, dhl.CreateShipmentResponse, first=True
32
+ )
33
+
34
+ return models.ShipmentDetails(
35
+ carrier_id=settings.carrier_id,
36
+ carrier_name=settings.carrier_name,
37
+ tracking_number=shipment.shipmentNotificationNumber,
38
+ shipment_identifier=shipment.shipmentTrackingNumber,
39
+ docs=models.Documents(
40
+ label=shipment.label.labelContent,
41
+ invoice=shipment.label.fvProformaContent,
42
+ ),
43
+ meta=dict(
44
+ carrier_tracking_link=settings.tracking_url.format(
45
+ shipment.shipmentNotificationNumber
46
+ ),
47
+ ),
48
+ )
49
+
50
+
51
+ def shipment_request(
52
+ payload: models.ShipmentRequest,
53
+ settings: provider_utils.Settings,
54
+ ) -> lib.Serializable:
55
+ packages = lib.to_packages(
56
+ payload.parcels,
57
+ required=["weight"],
58
+ package_option_type=provider_units.ShippingOption,
59
+ )
60
+ shipper = lib.to_address(payload.shipper)
61
+ recipient = lib.to_address(payload.recipient)
62
+ customs = lib.to_customs_info(payload.customs, weight_unit=packages.weight_unit)
63
+ options = lib.to_shipping_options(
64
+ payload.options,
65
+ package_options=packages.options,
66
+ initializer=provider_units.shipping_options_initializer,
67
+ )
68
+
69
+ is_international = shipper.country_code != recipient.country_code
70
+ service_type = provider_units.Service.map(payload.service).value_or_key or (
71
+ provider_units.Service.dhl_poland_polska.value
72
+ if is_international
73
+ else provider_units.Service.dhl_poland_09.value
74
+ )
75
+ label_type = provider_units.LabelType.map(payload.label_type or "PDF").value
76
+ payment = payload.payment or models.Payment()
77
+ quantity = len(customs.commodities or []) if customs.is_defined else 1
78
+
79
+ request = lib.create_envelope(
80
+ body_content=dhl.createShipment(
81
+ authData=settings.auth_data,
82
+ shipment=dhl.CreateShipmentRequest(
83
+ shipmentInfo=dhl.ShipmentInfo(
84
+ wayBill=None,
85
+ dropOffType="REGULAR_PICKUP",
86
+ serviceType=service_type,
87
+ billing=dhl.Billing(
88
+ shippingPaymentType=provider_units.PaymentType[
89
+ payment.paid_by
90
+ ].value,
91
+ billingAccountNumber=(
92
+ payment.account_number or settings.account_number
93
+ ),
94
+ paymentType="BANK_TRANSFER",
95
+ costsCenter=None,
96
+ ),
97
+ specialServices=(
98
+ dhl.ArrayOfService(
99
+ item=[
100
+ dhl.Service(
101
+ serviceType=option.code,
102
+ serviceValue=lib.to_money(option.state),
103
+ textInstruction=None,
104
+ collectOnDeliveryForm=None,
105
+ )
106
+ for _, option in options.items()
107
+ ]
108
+ )
109
+ if any(options.items())
110
+ else None
111
+ ),
112
+ shipmentTime=(
113
+ dhl.ShipmentTime(
114
+ shipmentDate=(
115
+ options.shipment_date.state or time.strftime("%Y-%m-%d")
116
+ ),
117
+ shipmentStartHour="10:00",
118
+ shipmentEndHour="10:00",
119
+ )
120
+ ),
121
+ labelType=label_type,
122
+ ),
123
+ content=payload.parcels[0].content or "N/A",
124
+ comment=None,
125
+ reference=payload.reference,
126
+ ship=dhl.Ship(
127
+ shipper=dhl.Addressat(
128
+ preaviso=(
129
+ dhl.PreavisoContact(
130
+ personName=shipper.person_name,
131
+ phoneNumber=shipper.phone_number,
132
+ emailAddress=shipper.email,
133
+ )
134
+ if any(
135
+ [
136
+ shipper.person_name,
137
+ shipper.phone_number,
138
+ shipper.email,
139
+ ]
140
+ )
141
+ else None
142
+ ),
143
+ contact=(
144
+ dhl.PreavisoContact(
145
+ personName=shipper.person_name,
146
+ phoneNumber=shipper.phone_number,
147
+ emailAddress=shipper.email,
148
+ )
149
+ if any(
150
+ [
151
+ shipper.person_name,
152
+ shipper.phone_number,
153
+ shipper.email,
154
+ ]
155
+ )
156
+ else None
157
+ ),
158
+ address=dhl.Address(
159
+ name=(shipper.company_name or shipper.person_name),
160
+ postalCode=(shipper.postal_code or "").replace("-", ""),
161
+ city=shipper.city,
162
+ street=shipper.address_line1,
163
+ houseNumber=(shipper.street_number or "N/A"),
164
+ apartmentNumber=shipper.address_line2,
165
+ ),
166
+ ),
167
+ receiver=dhl.ReceiverAddressat(
168
+ preaviso=(
169
+ dhl.PreavisoContact(
170
+ personName=recipient.person_name,
171
+ phoneNumber=recipient.phone_number,
172
+ emailAddress=recipient.email,
173
+ )
174
+ if any(
175
+ [
176
+ recipient.person_name,
177
+ recipient.phone_number,
178
+ recipient.email,
179
+ ]
180
+ )
181
+ else None
182
+ ),
183
+ contact=(
184
+ dhl.PreavisoContact(
185
+ personName=recipient.person_name,
186
+ phoneNumber=recipient.phone_number,
187
+ emailAddress=recipient.email,
188
+ )
189
+ if any(
190
+ [
191
+ recipient.person_name,
192
+ recipient.phone_number,
193
+ recipient.email,
194
+ ]
195
+ )
196
+ else None
197
+ ),
198
+ address=dhl.ReceiverAddress(
199
+ country=recipient.country_code,
200
+ isPackstation=None,
201
+ isPostfiliale=None,
202
+ postnummer=None,
203
+ addressType=("C" if recipient.residential else "B"),
204
+ name=(recipient.company_name or recipient.person_name),
205
+ postalCode=(recipient.postal_code or "").replace("-", ""),
206
+ city=recipient.city,
207
+ street=recipient.address_line1,
208
+ houseNumber=(shipper.street_number or "N/A"),
209
+ apartmentNumber=recipient.address_line2,
210
+ ),
211
+ ),
212
+ neighbour=None,
213
+ ),
214
+ pieceList=dhl.ArrayOfPackage(
215
+ item=[
216
+ dhl.Package(
217
+ type_=provider_units.PackagingType[
218
+ package.packaging_type or "your_packaging"
219
+ ].value,
220
+ euroReturn=None,
221
+ weight=package.weight.KG,
222
+ width=package.width.CM,
223
+ height=package.height.CM,
224
+ length=package.length.CM,
225
+ quantity=quantity,
226
+ nonStandard=None,
227
+ blpPieceId=None,
228
+ )
229
+ for package in packages
230
+ ]
231
+ ),
232
+ customs=(
233
+ dhl.CustomsData(
234
+ customsType="S",
235
+ firstName=getattr(
236
+ getattr(customs.duty, "bil_to", shipper.company_name),
237
+ "company_name",
238
+ "N/A",
239
+ ),
240
+ secondaryName=getattr(
241
+ getattr(customs.duty, "bil_to", shipper.person_name),
242
+ "person_name",
243
+ "N/A",
244
+ ),
245
+ costsOfShipment=(
246
+ getattr(customs.duty, "declared_value", None)
247
+ or options.declard_value.state
248
+ ),
249
+ currency=(
250
+ getattr(customs.duty, "currency", None)
251
+ or options.currency.state
252
+ ),
253
+ nipNr=customs.options.nip_number.state,
254
+ eoriNr=customs.options.eori_number.state,
255
+ vatRegistrationNumber=customs.options.vat_registration_number.state,
256
+ categoryOfItem=provider_units.CustomsContentType[
257
+ customs.content_type or "other"
258
+ ].value,
259
+ invoiceNr=customs.invoice,
260
+ invoice=None,
261
+ invoiceDate=customs.invoice_date,
262
+ countryOfOrigin=shipper.country_code,
263
+ additionalInfo=None,
264
+ grossWeight=packages.weight.KG,
265
+ customsItem=(
266
+ dhl.ArrayOfCustomsitemdata(
267
+ item=[
268
+ dhl.CustomsItemData(
269
+ nameEn=lib.text(
270
+ item.title or item.description or "N/A",
271
+ max=35,
272
+ ),
273
+ namePl=lib.text(
274
+ item.title or item.description or "N/A",
275
+ max=35,
276
+ ),
277
+ quantity=item.quantity,
278
+ weight=units.Weight(
279
+ item.weight,
280
+ units.WeightUnit[item.weight_unit or "KG"],
281
+ ).KG,
282
+ value=item.value_amount,
283
+ tariffCode=item.hs_code or item.sku,
284
+ )
285
+ for item in customs.commodities
286
+ ]
287
+ )
288
+ if any(customs.commodities)
289
+ else None
290
+ ),
291
+ customAgreements=dhl.CustomsAgreementData(
292
+ notExceedValue=True,
293
+ notProhibitedGoods=True,
294
+ notRestrictedGoods=True,
295
+ ),
296
+ )
297
+ if customs.is_defined
298
+ else None
299
+ ),
300
+ ),
301
+ )
302
+ )
303
+
304
+ return lib.Serializable(
305
+ request,
306
+ lambda req: settings.serialize(req, "createShipment", settings.server_url),
307
+ )
@@ -0,0 +1,91 @@
1
+ import karrio.schemas.dhl_poland.services as dhl
2
+ import typing
3
+ import karrio.lib as lib
4
+ import karrio.core.models as models
5
+ import karrio.providers.dhl_poland.error as provider_error
6
+ import karrio.providers.dhl_poland.utils as provider_utils
7
+
8
+
9
+ def parse_tracking_response(
10
+ _response: lib.Deserializable[typing.Dict[str, lib.Element]],
11
+ settings: provider_utils.Settings,
12
+ ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
13
+ response = _response.deserialize()
14
+ details = [
15
+ _extract_tracking_details(node, settings)
16
+ for node in response.values()
17
+ if lib.find_element("getTrackAndTraceInfoResult", node, first=True) is not None
18
+ ]
19
+ errors: typing.List[models.Message] = sum(
20
+ [
21
+ provider_error.parse_error_response(
22
+ node, settings, dict(tracking_number=number)
23
+ )
24
+ for number, node in response.items()
25
+ if lib.find_element("Fault", node, first=True) is not None
26
+ ],
27
+ [],
28
+ )
29
+
30
+ return details, errors
31
+
32
+
33
+ def _extract_tracking_details(
34
+ node: lib.Element,
35
+ settings: provider_utils.Settings,
36
+ ) -> models.TrackingDetails:
37
+ track: dhl.TrackAndTraceResponse = lib.find_element(
38
+ "getTrackAndTraceInfoResult", node, dhl.TrackAndTraceResponse, first=True
39
+ )
40
+ events = [
41
+ lib.to_object(dhl.TrackAndTraceEvent, item)
42
+ for item in lib.find_element("item", node)
43
+ ]
44
+
45
+ return models.TrackingDetails(
46
+ carrier_name=settings.carrier_name,
47
+ carrier_id=settings.carrier_id,
48
+ tracking_number=track.shipmentId,
49
+ events=[
50
+ models.TrackingEvent(
51
+ date=lib.fdate(event.timestamp, "%Y-%m-%d %H:%M:%S"),
52
+ description=event.description,
53
+ location=event.terminal,
54
+ code=event.status,
55
+ time=lib.flocaltime(event.timestamp, "%Y-%m-%d %H:%M:%S"),
56
+ )
57
+ for event in events
58
+ ],
59
+ delivered=any(track.receivedBy or ""),
60
+ info=models.TrackingInfo(
61
+ carrier_tracking_link=settings.tracking_url.format(track.shipmentId),
62
+ ),
63
+ )
64
+
65
+
66
+ def tracking_request(
67
+ payload: models.TrackingRequest,
68
+ settings: provider_utils.Settings,
69
+ ) -> lib.Serializable:
70
+ requests = {
71
+ tracking_number: lib.create_envelope(
72
+ body_prefix="",
73
+ body_content=dhl.getTrackAndTraceInfo(
74
+ authData=settings.auth_data,
75
+ shipmentId=tracking_number,
76
+ ),
77
+ )
78
+ for tracking_number in payload.tracking_numbers
79
+ }
80
+
81
+ return lib.Serializable(
82
+ requests,
83
+ lambda requests: {
84
+ number: settings.serialize(
85
+ request,
86
+ "getTrackAndTraceInfo",
87
+ settings.server_url,
88
+ )
89
+ for number, request in requests.items()
90
+ },
91
+ )