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