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.
- karrio/mappers/sapient/__init__.py +3 -0
- karrio/mappers/sapient/mapper.py +69 -0
- karrio/mappers/sapient/proxy.py +87 -0
- karrio/mappers/sapient/settings.py +36 -0
- karrio/plugins/sapient/__init__.py +20 -0
- karrio/providers/sapient/__init__.py +18 -0
- karrio/providers/sapient/error.py +28 -0
- karrio/providers/sapient/pickup/__init__.py +4 -0
- karrio/providers/sapient/pickup/cancel.py +57 -0
- karrio/providers/sapient/pickup/create.py +87 -0
- karrio/providers/sapient/pickup/update.py +87 -0
- karrio/providers/sapient/shipment/__init__.py +9 -0
- karrio/providers/sapient/shipment/cancel.py +57 -0
- karrio/providers/sapient/shipment/create.py +247 -0
- karrio/providers/sapient/units.py +638 -0
- karrio/providers/sapient/utils.py +122 -0
- karrio/schemas/sapient/__init__.py +0 -0
- karrio/schemas/sapient/error_response.py +16 -0
- karrio/schemas/sapient/pickup_request.py +10 -0
- karrio/schemas/sapient/pickup_response.py +9 -0
- karrio/schemas/sapient/shipment_requests.py +125 -0
- karrio/schemas/sapient/shipment_response.py +24 -0
- karrio_sapient-2025.5rc1.dist-info/METADATA +45 -0
- karrio_sapient-2025.5rc1.dist-info/RECORD +27 -0
- karrio_sapient-2025.5rc1.dist-info/WHEEL +5 -0
- karrio_sapient-2025.5rc1.dist-info/entry_points.txt +2 -0
- karrio_sapient-2025.5rc1.dist-info/top_level.txt +3 -0
@@ -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
|
+
)
|