karrio-sapient 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.
@@ -0,0 +1,249 @@
1
+ """Karrio SAPIENT shipment API implementation."""
2
+
3
+ from re import L
4
+ import karrio.schemas.sapient.shipment_requests as sapient
5
+ import karrio.schemas.sapient.shipment_response as shipping
6
+
7
+ import typing
8
+ import datetime
9
+ import karrio.lib as lib
10
+ import karrio.core.units as units
11
+ import karrio.core.models as models
12
+ import karrio.providers.sapient.error as error
13
+ import karrio.providers.sapient.utils as provider_utils
14
+ import karrio.providers.sapient.units as provider_units
15
+
16
+
17
+ def parse_shipment_response(
18
+ _response: lib.Deserializable[dict],
19
+ settings: provider_utils.Settings,
20
+ ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
21
+ response = _response.deserialize()
22
+
23
+ messages = error.parse_error_response(response, settings)
24
+ shipment = lib.identity(
25
+ _extract_details(response, settings, _response.ctx)
26
+ if "Packages" in response
27
+ else None
28
+ )
29
+
30
+ return shipment, messages
31
+
32
+
33
+ def _extract_details(
34
+ data: dict,
35
+ settings: provider_utils.Settings,
36
+ ctx: dict = None,
37
+ ) -> models.ShipmentDetails:
38
+ details = lib.to_object(shipping.ShipmentResponseType, data)
39
+ tracking_numbers = [_.TrackingNumber for _ in details.Packages]
40
+ shipment_ids = [_.ShipmentId for _ in details.Packages]
41
+ label = details.Labels
42
+
43
+ return models.ShipmentDetails(
44
+ carrier_id=settings.carrier_id,
45
+ carrier_name=settings.carrier_name,
46
+ tracking_number=tracking_numbers[0],
47
+ shipment_identifier=shipment_ids[0],
48
+ label_type=details.LabelFormat,
49
+ docs=models.Documents(label=label),
50
+ meta=dict(
51
+ carrier_tracking_link=lib.failsafe(lambda: details.Packages[0].TrackingUrl),
52
+ shipment_ids=shipment_ids,
53
+ tracking_numbers=tracking_numbers,
54
+ sapient_shipment_id=shipment_ids[0],
55
+ rate_provider=ctx.get("rate_provider"),
56
+ sapient_carrier_code=ctx.get("carrier_code"),
57
+ ),
58
+ )
59
+
60
+
61
+ def shipment_request(
62
+ payload: models.ShipmentRequest,
63
+ settings: provider_utils.Settings,
64
+ ) -> lib.Serializable:
65
+ shipper = lib.to_address(payload.shipper)
66
+ recipient = lib.to_address(payload.recipient)
67
+ return_address = lib.to_address(payload.return_address)
68
+ packages = lib.to_packages(payload.parcels)
69
+ service = provider_units.ShippingService.map(payload.service).value_or_key
70
+ carrier_code = lib.identity(
71
+ provider_units.ShippingService.carrier_code(service)
72
+ or settings.sapient_carrier_code
73
+ )
74
+ rate_provider = provider_units.ShippingCarrier.map(carrier_code).name_or_key
75
+ options = lib.to_shipping_options(
76
+ payload.options,
77
+ package_options=packages.options,
78
+ initializer=provider_units.shipping_options_initializer,
79
+ )
80
+ is_intl = payload.recipient.country_code != payload.shipper.country_code
81
+ customs = lib.to_customs_info(
82
+ payload.customs,
83
+ weight_unit="KG",
84
+ shipper=payload.shipper,
85
+ recipient=payload.recipient,
86
+ )
87
+ commodities: units.Products = lib.identity(
88
+ customs.commodities if payload.customs else packages.items
89
+ )
90
+ utc_shipping_date = lib.to_date(
91
+ options.shipping_date.state or datetime.datetime.now(),
92
+ current_format="%Y-%m-%dT%H:%M",
93
+ ).astimezone(datetime.timezone.utc)
94
+ shipping_date = lib.to_next_business_datetime(utc_shipping_date, "%Y-%m-%d")
95
+
96
+ # map data to convert karrio model to sapient specific type
97
+ request = sapient.ShipmentRequestType(
98
+ ShipmentInformation=sapient.ShipmentInformationType(
99
+ ContentType=lib.identity("DOX" if packages.is_document else "NDX"),
100
+ Action="Process",
101
+ LabelFormat=provider_units.LabelType.map(payload.label_type).value or "PDF",
102
+ ServiceCode=service or "CRL1",
103
+ DescriptionOfGoods=lib.text(
104
+ packages.description or packages.items.description or "N/A", max=70
105
+ ),
106
+ ShipmentDate=lib.fdate(shipping_date, "%Y-%m-%d %H:%M:%S"),
107
+ CurrencyCode=options.currency.state or "GBP",
108
+ WeightUnitOfMeasure="KG",
109
+ DimensionsUnitOfMeasure="CM",
110
+ ContainerId=options.sapient_container_id.state,
111
+ DeclaredWeight=packages.weight.KG,
112
+ BusinessTransactionType=options.sapient_business_transaction_type.state,
113
+ ),
114
+ Shipper=sapient.ShipperType(
115
+ Address=sapient.AddressType(
116
+ ContactName=lib.text(shipper.contact or "N/A", max=40),
117
+ CompanyName=lib.text(shipper.company_name, max=40),
118
+ ContactEmail=shipper.email,
119
+ ContactPhone=shipper.phone_number,
120
+ Line1=lib.text(shipper.address_line1, max=40),
121
+ Line2=lib.text(shipper.address_line2, max=40),
122
+ Line3=None,
123
+ Town=shipper.city,
124
+ Postcode=shipper.postal_code,
125
+ County=None,
126
+ CountryCode=shipper.country_code,
127
+ ),
128
+ ShippingAccountId=lib.identity(
129
+ settings.shipping_account_id
130
+ or options.connection_config.mailer_id.state
131
+ ),
132
+ ShippingLocationId=None,
133
+ Reference1=lib.text(payload.reference, max=30),
134
+ Reference2=lib.text(options.sapient_reference_2.state, max=30),
135
+ DepartmentNumber=None,
136
+ EoriNumber=customs.options.eori_number.state,
137
+ VatNumber=lib.identity(
138
+ customs.options.vat_registration_number.state or shipper.tax_id
139
+ ),
140
+ ),
141
+ Destination=sapient.DestinationType(
142
+ Address=sapient.AddressType(
143
+ ContactName=lib.text(recipient.contact or "N/A", max=40),
144
+ CompanyName=lib.text(recipient.company_name, max=40),
145
+ ContactEmail=recipient.email,
146
+ ContactPhone=recipient.phone_number,
147
+ Line1=lib.text(recipient.address_line1, max=40),
148
+ Line2=lib.text(recipient.address_line2, max=40),
149
+ Line3=None,
150
+ Town=recipient.city,
151
+ Postcode=recipient.postal_code,
152
+ County=None,
153
+ CountryCode=recipient.country_code,
154
+ ),
155
+ EoriNumber=None,
156
+ VatNumber=recipient.tax_id,
157
+ ),
158
+ CarrierSpecifics=sapient.CarrierSpecificsType(
159
+ ServiceLevel=settings.connection_config.service_level.state or "02",
160
+ EbayVtn=options.sapient_ebay_vtn.state,
161
+ ServiceEnhancements=[
162
+ sapient.ServiceEnhancementType(
163
+ Code=option.code,
164
+ SafeplaceLocation=lib.identity(
165
+ option.state if option.code == "Safeplace" else None
166
+ ),
167
+ )
168
+ for _, option in options.items()
169
+ if _ not in provider_units.CUSTOM_OPTIONS
170
+ ],
171
+ ),
172
+ ReturnToSender=lib.identity(
173
+ sapient.ReturnToSenderType(
174
+ Address=sapient.AddressType(
175
+ ContactName=lib.text(return_address.contact or "N/A", max=40),
176
+ CompanyName=lib.text(return_address.company_name, max=40),
177
+ ContactEmail=return_address.email,
178
+ ContactPhone=return_address.phone_number,
179
+ Line1=lib.text(return_address.address_line1, max=40),
180
+ Line2=lib.text(return_address.address_line2, max=40),
181
+ Line3=None,
182
+ Town=return_address.city,
183
+ Postcode=return_address.postal_code,
184
+ County=None,
185
+ CountryCode=return_address.country_code,
186
+ ),
187
+ )
188
+ if payload.return_address
189
+ else None
190
+ ),
191
+ Packages=[
192
+ sapient.PackageType(
193
+ PackageType=lib.identity(
194
+ provider_units.PackagingType.map(package.packaging_type).value
195
+ or "Parcel"
196
+ ),
197
+ PackageOccurrence=(index if len(packages) > 1 else None),
198
+ DeclaredWeight=package.weight.KG,
199
+ Dimensions=sapient.DimensionsType(
200
+ Length=package.length.CM,
201
+ Width=package.width.CM,
202
+ Height=package.height.CM,
203
+ ),
204
+ DeclaredValue=package.total_value,
205
+ )
206
+ for index, package in typing.cast(
207
+ typing.List[typing.Tuple[int, units.Package]],
208
+ enumerate(packages, start=1),
209
+ )
210
+ ],
211
+ Items=[
212
+ sapient.ItemType(
213
+ SkuCode=lib.text(item.sku, max=30),
214
+ PackageOccurrence=None,
215
+ Quantity=item.quantity,
216
+ Description=lib.text(item.title or item.description, max=35),
217
+ Value=item.value_amount,
218
+ Weight=item.weight,
219
+ HSCode=lib.text(item.hs_code, max=13),
220
+ CountryOfOrigin=item.origin_country,
221
+ )
222
+ for item in commodities
223
+ ],
224
+ Customs=lib.identity(
225
+ sapient.CustomsType(
226
+ ReasonForExport=provider_units.CustomsContentType.map(
227
+ customs.content_type
228
+ ).value,
229
+ Incoterms=customs.incoterm,
230
+ PreRegistrationNumber=customs.options.sapient_pre_registration_number.state,
231
+ PreRegistrationType=customs.options.sapient_pre_registration_type.state,
232
+ ShippingCharges=options.sapient_shipping_charges.state,
233
+ OtherCharges=options.insurance.state,
234
+ QuotedLandedCost=options.sapient_quoted_landed_cost.state,
235
+ InvoiceNumber=customs.invoice,
236
+ InvoiceDate=lib.fdate(customs.invoice_date, "%Y-%m-%d"),
237
+ ExportLicenceRequired=options.export_licence_required.state,
238
+ Airn=customs.options.sapient_airn.state,
239
+ )
240
+ if payload.customs and is_intl
241
+ else None
242
+ ),
243
+ )
244
+
245
+ return lib.Serializable(
246
+ request,
247
+ lib.to_dict,
248
+ dict(carrier_code=carrier_code, rate_provider=rate_provider),
249
+ )