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,122 @@
|
|
1
|
+
import base64
|
2
|
+
import datetime
|
3
|
+
import karrio.lib as lib
|
4
|
+
import karrio.core as core
|
5
|
+
import karrio.core.errors as errors
|
6
|
+
|
7
|
+
|
8
|
+
SapientCarrierCode = lib.units.create_enum(
|
9
|
+
"SapientCarrierCode",
|
10
|
+
["DX", "EVRI", "RM", "UPS", "YODEL"],
|
11
|
+
)
|
12
|
+
|
13
|
+
|
14
|
+
class Settings(core.Settings):
|
15
|
+
"""SAPIENT connection settings."""
|
16
|
+
|
17
|
+
# Add carrier specific api connection properties here
|
18
|
+
client_id: str
|
19
|
+
client_secret: str
|
20
|
+
shipping_account_id: str
|
21
|
+
sapient_carrier_code: SapientCarrierCode = "RM" # type: ignore
|
22
|
+
|
23
|
+
@property
|
24
|
+
def carrier_name(self):
|
25
|
+
return "sapient"
|
26
|
+
|
27
|
+
@property
|
28
|
+
def server_url(self):
|
29
|
+
return "https://api.intersoftsapient.net"
|
30
|
+
|
31
|
+
# """uncomment the following code block to expose a carrier tracking url."""
|
32
|
+
# @property
|
33
|
+
# def tracking_url(self):
|
34
|
+
# return "https://www.carrier.com/tracking?tracking-id={}"
|
35
|
+
|
36
|
+
# """uncomment the following code block to implement the Basic auth."""
|
37
|
+
# @property
|
38
|
+
# def authorization(self):
|
39
|
+
# pair = "%s:%s" % (self.username, self.password)
|
40
|
+
# return base64.b64encode(pair.encode("utf-8")).decode("ascii")
|
41
|
+
|
42
|
+
@property
|
43
|
+
def connection_config(self) -> lib.units.Options:
|
44
|
+
return lib.to_connection_config(
|
45
|
+
self.config or {},
|
46
|
+
option_type=ConnectionConfig,
|
47
|
+
)
|
48
|
+
|
49
|
+
"""uncomment the following code block to implement the oauth login."""
|
50
|
+
|
51
|
+
@property
|
52
|
+
def access_token(self):
|
53
|
+
"""Retrieve the access_token using the client_id|client_secret pair
|
54
|
+
or collect it from the cache if an unexpired access_token exist.
|
55
|
+
"""
|
56
|
+
cache_key = f"{self.carrier_name}|{self.client_id}|{self.client_secret}"
|
57
|
+
now = datetime.datetime.now() + datetime.timedelta(minutes=30)
|
58
|
+
|
59
|
+
auth = self.connection_cache.get(cache_key) or {}
|
60
|
+
token = auth.get("access_token")
|
61
|
+
expiry = lib.to_date(auth.get("expiry"), current_format="%Y-%m-%d %H:%M:%S")
|
62
|
+
|
63
|
+
if token is not None and expiry is not None and expiry > now:
|
64
|
+
return token
|
65
|
+
|
66
|
+
self.connection_cache.set(cache_key, lambda: login(self))
|
67
|
+
new_auth = self.connection_cache.get(cache_key)
|
68
|
+
|
69
|
+
return new_auth["access_token"]
|
70
|
+
|
71
|
+
|
72
|
+
"""uncomment the following code block to implement the oauth login."""
|
73
|
+
|
74
|
+
|
75
|
+
def login(settings: Settings):
|
76
|
+
import karrio.providers.sapient.error as error
|
77
|
+
|
78
|
+
result = lib.request(
|
79
|
+
url=f"https://authentication.intersoftsapient.net/connect/token",
|
80
|
+
method="POST",
|
81
|
+
headers={
|
82
|
+
"content-Type": "application/x-www-form-urlencoded",
|
83
|
+
"user-agent": "Karrio/1.0",
|
84
|
+
},
|
85
|
+
data=lib.to_query_string(
|
86
|
+
dict(
|
87
|
+
grant_type="client_credentials",
|
88
|
+
client_id=settings.client_id,
|
89
|
+
client_secret=settings.client_secret,
|
90
|
+
)
|
91
|
+
),
|
92
|
+
on_error=parse_error_response,
|
93
|
+
)
|
94
|
+
|
95
|
+
response = lib.to_dict(result)
|
96
|
+
messages = error.parse_error_response(response, settings)
|
97
|
+
|
98
|
+
if any(messages):
|
99
|
+
raise errors.ParsedMessagesError(messages)
|
100
|
+
|
101
|
+
expiry = datetime.datetime.now() + datetime.timedelta(
|
102
|
+
seconds=float(response.get("expires_in", 0))
|
103
|
+
)
|
104
|
+
return {**response, "expiry": lib.fdatetime(expiry)}
|
105
|
+
|
106
|
+
|
107
|
+
class ConnectionConfig(lib.Enum):
|
108
|
+
service_level = lib.OptionEnum("service_level", str)
|
109
|
+
shipping_options = lib.OptionEnum("shipping_options", list)
|
110
|
+
shipping_services = lib.OptionEnum("shipping_services", list)
|
111
|
+
|
112
|
+
|
113
|
+
def parse_error_response(response):
|
114
|
+
"""Parse the error response from the SAPIENT API."""
|
115
|
+
content = lib.failsafe(lambda: lib.decode(response.read()))
|
116
|
+
|
117
|
+
if any(content or ""):
|
118
|
+
return content
|
119
|
+
|
120
|
+
return lib.to_json(
|
121
|
+
dict(Errors=[dict(ErrorCode=str(response.code), Message=response.reason)])
|
122
|
+
)
|
File without changes
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import attr
|
2
|
+
import jstruct
|
3
|
+
import typing
|
4
|
+
|
5
|
+
|
6
|
+
@attr.s(auto_attribs=True)
|
7
|
+
class ErrorType:
|
8
|
+
Message: typing.Optional[str] = None
|
9
|
+
Cause: typing.Optional[str] = None
|
10
|
+
ErrorCode: typing.Optional[str] = None
|
11
|
+
|
12
|
+
|
13
|
+
@attr.s(auto_attribs=True)
|
14
|
+
class ErrorResponseType:
|
15
|
+
Message: typing.Optional[str] = None
|
16
|
+
Errors: typing.Optional[typing.List[ErrorType]] = jstruct.JList[ErrorType]
|
@@ -0,0 +1,125 @@
|
|
1
|
+
import attr
|
2
|
+
import jstruct
|
3
|
+
import typing
|
4
|
+
|
5
|
+
|
6
|
+
@attr.s(auto_attribs=True)
|
7
|
+
class ServiceEnhancementType:
|
8
|
+
Code: typing.Optional[str] = None
|
9
|
+
SafeplaceLocation: typing.Optional[str] = None
|
10
|
+
|
11
|
+
|
12
|
+
@attr.s(auto_attribs=True)
|
13
|
+
class CarrierSpecificsType:
|
14
|
+
ServiceLevel: typing.Optional[str] = None
|
15
|
+
EbayVtn: typing.Optional[str] = None
|
16
|
+
ServiceEnhancements: typing.Optional[typing.List[ServiceEnhancementType]] = jstruct.JList[ServiceEnhancementType]
|
17
|
+
|
18
|
+
|
19
|
+
@attr.s(auto_attribs=True)
|
20
|
+
class CustomsType:
|
21
|
+
ReasonForExport: typing.Optional[str] = None
|
22
|
+
Incoterms: typing.Optional[str] = None
|
23
|
+
PreRegistrationNumber: typing.Optional[str] = None
|
24
|
+
PreRegistrationType: typing.Optional[str] = None
|
25
|
+
ShippingCharges: typing.Optional[float] = None
|
26
|
+
OtherCharges: typing.Optional[int] = None
|
27
|
+
QuotedLandedCost: typing.Optional[float] = None
|
28
|
+
InvoiceNumber: typing.Optional[str] = None
|
29
|
+
InvoiceDate: typing.Optional[str] = None
|
30
|
+
ExportLicenceRequired: typing.Optional[bool] = None
|
31
|
+
Airn: typing.Optional[str] = None
|
32
|
+
|
33
|
+
|
34
|
+
@attr.s(auto_attribs=True)
|
35
|
+
class AddressType:
|
36
|
+
ContactName: typing.Optional[str] = None
|
37
|
+
CompanyName: typing.Optional[str] = None
|
38
|
+
ContactEmail: typing.Optional[str] = None
|
39
|
+
ContactPhone: typing.Optional[str] = None
|
40
|
+
Line1: typing.Optional[str] = None
|
41
|
+
Line2: typing.Optional[str] = None
|
42
|
+
Line3: typing.Optional[str] = None
|
43
|
+
Town: typing.Optional[str] = None
|
44
|
+
Postcode: typing.Optional[str] = None
|
45
|
+
County: typing.Optional[str] = None
|
46
|
+
CountryCode: typing.Optional[str] = None
|
47
|
+
|
48
|
+
|
49
|
+
@attr.s(auto_attribs=True)
|
50
|
+
class DestinationType:
|
51
|
+
Address: typing.Optional[AddressType] = jstruct.JStruct[AddressType]
|
52
|
+
EoriNumber: typing.Optional[str] = None
|
53
|
+
VatNumber: typing.Optional[str] = None
|
54
|
+
|
55
|
+
|
56
|
+
@attr.s(auto_attribs=True)
|
57
|
+
class ItemType:
|
58
|
+
SkuCode: typing.Optional[str] = None
|
59
|
+
PackageOccurrence: typing.Optional[int] = None
|
60
|
+
Quantity: typing.Optional[int] = None
|
61
|
+
Description: typing.Optional[str] = None
|
62
|
+
Value: typing.Optional[float] = None
|
63
|
+
Weight: typing.Optional[float] = None
|
64
|
+
HSCode: typing.Optional[str] = None
|
65
|
+
CountryOfOrigin: typing.Optional[str] = None
|
66
|
+
|
67
|
+
|
68
|
+
@attr.s(auto_attribs=True)
|
69
|
+
class DimensionsType:
|
70
|
+
Length: typing.Optional[int] = None
|
71
|
+
Width: typing.Optional[int] = None
|
72
|
+
Height: typing.Optional[int] = None
|
73
|
+
|
74
|
+
|
75
|
+
@attr.s(auto_attribs=True)
|
76
|
+
class PackageType:
|
77
|
+
PackageType: typing.Optional[str] = None
|
78
|
+
PackageOccurrence: typing.Optional[int] = None
|
79
|
+
DeclaredWeight: typing.Optional[float] = None
|
80
|
+
Dimensions: typing.Optional[DimensionsType] = jstruct.JStruct[DimensionsType]
|
81
|
+
DeclaredValue: typing.Optional[float] = None
|
82
|
+
|
83
|
+
|
84
|
+
@attr.s(auto_attribs=True)
|
85
|
+
class ReturnToSenderType:
|
86
|
+
Address: typing.Optional[AddressType] = jstruct.JStruct[AddressType]
|
87
|
+
|
88
|
+
|
89
|
+
@attr.s(auto_attribs=True)
|
90
|
+
class ShipmentInformationType:
|
91
|
+
ContentType: typing.Optional[str] = None
|
92
|
+
Action: typing.Optional[str] = None
|
93
|
+
LabelFormat: typing.Optional[str] = None
|
94
|
+
ServiceCode: typing.Optional[str] = None
|
95
|
+
DescriptionOfGoods: typing.Optional[str] = None
|
96
|
+
ShipmentDate: typing.Optional[str] = None
|
97
|
+
CurrencyCode: typing.Optional[str] = None
|
98
|
+
WeightUnitOfMeasure: typing.Optional[str] = None
|
99
|
+
DimensionsUnitOfMeasure: typing.Optional[str] = None
|
100
|
+
ContainerId: typing.Optional[str] = None
|
101
|
+
DeclaredWeight: typing.Optional[float] = None
|
102
|
+
BusinessTransactionType: typing.Optional[str] = None
|
103
|
+
|
104
|
+
|
105
|
+
@attr.s(auto_attribs=True)
|
106
|
+
class ShipperType:
|
107
|
+
Address: typing.Optional[AddressType] = jstruct.JStruct[AddressType]
|
108
|
+
ShippingAccountId: typing.Optional[str] = None
|
109
|
+
ShippingLocationId: typing.Optional[str] = None
|
110
|
+
Reference1: typing.Optional[str] = None
|
111
|
+
DepartmentNumber: typing.Optional[str] = None
|
112
|
+
EoriNumber: typing.Optional[str] = None
|
113
|
+
VatNumber: typing.Optional[str] = None
|
114
|
+
|
115
|
+
|
116
|
+
@attr.s(auto_attribs=True)
|
117
|
+
class ShipmentRequestType:
|
118
|
+
ShipmentInformation: typing.Optional[ShipmentInformationType] = jstruct.JStruct[ShipmentInformationType]
|
119
|
+
Shipper: typing.Optional[ShipperType] = jstruct.JStruct[ShipperType]
|
120
|
+
Destination: typing.Optional[DestinationType] = jstruct.JStruct[DestinationType]
|
121
|
+
CarrierSpecifics: typing.Optional[CarrierSpecificsType] = jstruct.JStruct[CarrierSpecificsType]
|
122
|
+
ReturnToSender: typing.Optional[ReturnToSenderType] = jstruct.JStruct[ReturnToSenderType]
|
123
|
+
Packages: typing.Optional[typing.List[PackageType]] = jstruct.JList[PackageType]
|
124
|
+
Items: typing.Optional[typing.List[ItemType]] = jstruct.JList[ItemType]
|
125
|
+
Customs: typing.Optional[CustomsType] = jstruct.JStruct[CustomsType]
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import attr
|
2
|
+
import jstruct
|
3
|
+
import typing
|
4
|
+
|
5
|
+
|
6
|
+
@attr.s(auto_attribs=True)
|
7
|
+
class CarrierDetailsType:
|
8
|
+
UniqueId: typing.Optional[str] = None
|
9
|
+
|
10
|
+
|
11
|
+
@attr.s(auto_attribs=True)
|
12
|
+
class PackageType:
|
13
|
+
CarrierDetails: typing.Optional[CarrierDetailsType] = jstruct.JStruct[CarrierDetailsType]
|
14
|
+
ShipmentId: typing.Optional[str] = None
|
15
|
+
PackageOccurrence: typing.Optional[int] = None
|
16
|
+
TrackingNumber: typing.Optional[str] = None
|
17
|
+
CarrierTrackingUrl: typing.Optional[str] = None
|
18
|
+
|
19
|
+
|
20
|
+
@attr.s(auto_attribs=True)
|
21
|
+
class ShipmentResponseType:
|
22
|
+
Labels: typing.Optional[str] = None
|
23
|
+
LabelFormat: typing.Optional[str] = None
|
24
|
+
Packages: typing.Optional[typing.List[PackageType]] = jstruct.JList[PackageType]
|
@@ -0,0 +1,45 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: karrio_sapient
|
3
|
+
Version: 2025.5rc1
|
4
|
+
Summary: Karrio - SAPIENT Shipping Extension
|
5
|
+
Author-email: karrio <hello@karrio.io>
|
6
|
+
License-Expression: Apache-2.0
|
7
|
+
Project-URL: Homepage, https://github.com/karrioapi/karrio
|
8
|
+
Classifier: Intended Audience :: Developers
|
9
|
+
Classifier: Operating System :: OS Independent
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Requires-Python: >=3.7
|
12
|
+
Description-Content-Type: text/markdown
|
13
|
+
Requires-Dist: karrio
|
14
|
+
|
15
|
+
|
16
|
+
# karrio.sapient
|
17
|
+
|
18
|
+
This package is a SAPIENT extension of the [karrio](https://pypi.org/project/karrio) multi carrier shipping SDK.
|
19
|
+
|
20
|
+
## Requirements
|
21
|
+
|
22
|
+
`Python 3.7+`
|
23
|
+
|
24
|
+
## Installation
|
25
|
+
|
26
|
+
```bash
|
27
|
+
pip install karrio.sapient
|
28
|
+
```
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
```python
|
33
|
+
import karrio.sdk as karrio
|
34
|
+
from karrio.mappers.sapient.settings import Settings
|
35
|
+
|
36
|
+
|
37
|
+
# Initialize a carrier gateway
|
38
|
+
sapient = karrio.gateway["sapient"].create(
|
39
|
+
Settings(
|
40
|
+
...
|
41
|
+
)
|
42
|
+
)
|
43
|
+
```
|
44
|
+
|
45
|
+
Check the [Karrio Mutli-carrier SDK docs](https://docs.karrio.io) for Shipping API requests
|
@@ -0,0 +1,27 @@
|
|
1
|
+
karrio/mappers/sapient/__init__.py,sha256=I4fWpxOlRnfyBNjL0LxvQlpwOk2sjrZhgtCO2XvSJVk,149
|
2
|
+
karrio/mappers/sapient/mapper.py,sha256=BDU1L2QW1W-0e8eYcgSf-eLkj5o7SS4kje-WObnZSUg,2866
|
3
|
+
karrio/mappers/sapient/proxy.py,sha256=vzfErbI6i5_uVA5UUIjCyKpxNeZu6eupDpSkvQT-P9E,3537
|
4
|
+
karrio/mappers/sapient/settings.py,sha256=a5xE-umxBbP1sHaN6SKkbt-ftsmx9pnWkMEUIeAhVLY,1095
|
5
|
+
karrio/plugins/sapient/__init__.py,sha256=_4xkqTPpUoq8KixCdNqNxcMVH52s5xH6L-s9c5fongE,537
|
6
|
+
karrio/providers/sapient/__init__.py,sha256=Zu7FIaPsCzXgIsrmEtluHQJSj3UnFDpz-oiB93P-xK0,476
|
7
|
+
karrio/providers/sapient/error.py,sha256=hkpy86gWOjKLNhtmBoe7NkHq330kRqrtgDxsspLaxGQ,870
|
8
|
+
karrio/providers/sapient/units.py,sha256=1rCsLoMjVwg7xNhtCD6uTeuI310Bc-RXLTCYUb-rfqQ,34560
|
9
|
+
karrio/providers/sapient/utils.py,sha256=31dgSqot8k3gj_kpUfitKMrUQtuNtVzkzEW_cAKRkUQ,3763
|
10
|
+
karrio/providers/sapient/pickup/__init__.py,sha256=qT64wGr8J6pkLNa9Y0Zou5mYshoI7W-R0Pp7nWEhQ08,296
|
11
|
+
karrio/providers/sapient/pickup/cancel.py,sha256=0laDCfdhjOYHpOEN7l6u1o79nexaxT8GXMM5pJU3Xow,1763
|
12
|
+
karrio/providers/sapient/pickup/create.py,sha256=5S-h9qQuDFqxvqrmd2l0-SGQMi86O38sfjfqRVPGgo8,2776
|
13
|
+
karrio/providers/sapient/pickup/update.py,sha256=rG4du0Ey_w3AaA2db46nKLMPKTCYokZlfofRQwZ83P0,2796
|
14
|
+
karrio/providers/sapient/shipment/__init__.py,sha256=DMPIljx1L1FvlatOITGSsfSGovv-NLW7ydT7SDlDIEE,231
|
15
|
+
karrio/providers/sapient/shipment/cancel.py,sha256=4p1KyMmyasy5wKCfVGGW2I0AWClbX5FbjhhkYD0zymc,1698
|
16
|
+
karrio/providers/sapient/shipment/create.py,sha256=ZmKW4m0LqErrQbFQoQuv3eA6hIDpgfZ14gIrQlHHsQc,9794
|
17
|
+
karrio/schemas/sapient/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
|
+
karrio/schemas/sapient/error_response.py,sha256=43gmiRdi5y2I1qn1OfuR9f30JTld6I5lgtzpjIOEUpQ,384
|
19
|
+
karrio/schemas/sapient/pickup_request.py,sha256=gzZZ3Bxr_pYWxw28vDRr-aDgtzJIQptUkaTEzXhX96g,235
|
20
|
+
karrio/schemas/sapient/pickup_response.py,sha256=xcl3ofB2L2RNksMSRccvFbBdBcK1hVDDPVB-oAu4I54,195
|
21
|
+
karrio/schemas/sapient/shipment_requests.py,sha256=roKQ4iFkm6BKzI_b8TL39ZcsKWU61LVN7oRh_lyb75k,4574
|
22
|
+
karrio/schemas/sapient/shipment_response.py,sha256=457ULt-exxAJ0Drqn2Pnj9pl_4JWMZ-cesuJW9NWBAs,702
|
23
|
+
karrio_sapient-2025.5rc1.dist-info/METADATA,sha256=Wuin4NcW9GInx033Z4yfpaEWuH8kcLC9cTqoDl7cRbk,992
|
24
|
+
karrio_sapient-2025.5rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
25
|
+
karrio_sapient-2025.5rc1.dist-info/entry_points.txt,sha256=rPF-WjVKk-3suhX4-zi9hTkwr0Qxb4ZW-Tjoyzi_Cy8,59
|
26
|
+
karrio_sapient-2025.5rc1.dist-info/top_level.txt,sha256=FZCY8Nwft8oEGHdl--xku8P3TrnOxu5dETEU_fWpRSM,20
|
27
|
+
karrio_sapient-2025.5rc1.dist-info/RECORD,,
|