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,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,10 @@
1
+ import attr
2
+ import jstruct
3
+ import typing
4
+
5
+
6
+ @attr.s(auto_attribs=True)
7
+ class PickupRequestType:
8
+ SlotReservationId: typing.Optional[str] = None
9
+ SlotDate: typing.Optional[str] = None
10
+ BringMyLabel: typing.Optional[bool] = None
@@ -0,0 +1,9 @@
1
+ import attr
2
+ import jstruct
3
+ import typing
4
+
5
+
6
+ @attr.s(auto_attribs=True)
7
+ class PickupResponseType:
8
+ CollectionOrderId: typing.Optional[str] = None
9
+ CollectionDate: typing.Optional[str] = None
@@ -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,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [karrio.plugins]
2
+ sapient = karrio.plugins.sapient:METADATA
@@ -0,0 +1,3 @@
1
+ dist
2
+ karrio
3
+ schemas