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,3 @@
1
+ from karrio.mappers.sapient.mapper import Mapper
2
+ from karrio.mappers.sapient.proxy import Proxy
3
+ from karrio.mappers.sapient.settings import Settings
@@ -0,0 +1,69 @@
1
+ """Karrio SAPIENT client mapper."""
2
+
3
+ import typing
4
+ import karrio.lib as lib
5
+ import karrio.api.mapper as mapper
6
+ import karrio.core.models as models
7
+ import karrio.providers.sapient as provider
8
+ import karrio.mappers.sapient.settings as provider_settings
9
+ import karrio.universal.providers.rating as universal_provider
10
+
11
+
12
+ class Mapper(mapper.Mapper):
13
+ settings: provider_settings.Settings
14
+
15
+ def create_rate_request(self, payload: models.RateRequest) -> lib.Serializable:
16
+ return universal_provider.rate_request(payload, self.settings)
17
+
18
+ def create_shipment_request(
19
+ self, payload: models.ShipmentRequest
20
+ ) -> lib.Serializable:
21
+ return provider.shipment_request(payload, self.settings)
22
+
23
+ def create_pickup_request(self, payload: models.PickupRequest) -> lib.Serializable:
24
+ return provider.pickup_request(payload, self.settings)
25
+
26
+ def create_pickup_update_request(
27
+ self, payload: models.PickupUpdateRequest
28
+ ) -> lib.Serializable:
29
+ return provider.pickup_update_request(payload, self.settings)
30
+
31
+ def create_cancel_pickup_request(
32
+ self, payload: models.PickupCancelRequest
33
+ ) -> lib.Serializable:
34
+ return provider.pickup_cancel_request(payload, self.settings)
35
+
36
+ def create_cancel_shipment_request(
37
+ self, payload: models.ShipmentCancelRequest
38
+ ) -> lib.Serializable[str]:
39
+ return provider.shipment_cancel_request(payload, self.settings)
40
+
41
+ def parse_cancel_pickup_response(
42
+ self, response: lib.Deserializable[str]
43
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
44
+ return provider.parse_pickup_cancel_response(response, self.settings)
45
+
46
+ def parse_cancel_shipment_response(
47
+ self, response: lib.Deserializable[str]
48
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
49
+ return provider.parse_shipment_cancel_response(response, self.settings)
50
+
51
+ def parse_pickup_response(
52
+ self, response: lib.Deserializable[str]
53
+ ) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
54
+ return provider.parse_pickup_response(response, self.settings)
55
+
56
+ def parse_pickup_update_response(
57
+ self, response: lib.Deserializable[str]
58
+ ) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
59
+ return provider.parse_pickup_update_response(response, self.settings)
60
+
61
+ def parse_rate_response(
62
+ self, response: lib.Deserializable[str]
63
+ ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
64
+ return universal_provider.parse_rate_response(response, self.settings)
65
+
66
+ def parse_shipment_response(
67
+ self, response: lib.Deserializable[str]
68
+ ) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
69
+ return provider.parse_shipment_response(response, self.settings)
@@ -0,0 +1,87 @@
1
+ """Karrio SAPIENT client proxy."""
2
+
3
+ import karrio.lib as lib
4
+ import karrio.api.proxy as proxy
5
+ import karrio.providers.sapient.utils as provider_utils
6
+ import karrio.mappers.sapient.settings as provider_settings
7
+ import karrio.universal.mappers.rating_proxy as rating_proxy
8
+
9
+
10
+ class Proxy(rating_proxy.RatingMixinProxy, proxy.Proxy):
11
+ settings: provider_settings.Settings
12
+
13
+ def get_rates(self, request: lib.Serializable) -> lib.Deserializable[str]:
14
+ return super().get_rates(request)
15
+
16
+ def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
17
+ response = lib.request(
18
+ url=f"{self.settings.server_url}/v4/shipments/{request.ctx['carrier_code']}",
19
+ data=lib.to_json(request.serialize()),
20
+ trace=self.trace_as("json"),
21
+ method="POST",
22
+ headers={
23
+ "Content-Type": "application/json",
24
+ "Authorization": f"Bearer {self.settings.access_token}",
25
+ "user-agent": "Karrio/1.0",
26
+ },
27
+ on_error=provider_utils.parse_error_response,
28
+ )
29
+
30
+ return lib.Deserializable(response, lib.to_dict, request.ctx)
31
+
32
+ def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
33
+ response = lib.request(
34
+ url=f"{self.settings.server_url}/v4/shipments/status",
35
+ data=lib.to_json(request.serialize()),
36
+ trace=self.trace_as("json"),
37
+ method="PUT",
38
+ headers={
39
+ "Content-Type": "application/json",
40
+ "Authorization": f"Bearer {self.settings.access_token}",
41
+ "user-agent": "Karrio/1.0",
42
+ },
43
+ on_ok=lambda _: '{"ok": true}',
44
+ on_error=provider_utils.parse_error_response,
45
+ )
46
+
47
+ return lib.Deserializable(response, lib.to_dict)
48
+
49
+ def schedule_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
50
+ response = lib.request(
51
+ url=f"{self.settings.server_url}/v4/collections/{request.ctx['carrier_code']}/{request.ctx['shipmentId']}",
52
+ data=lib.to_json(request.serialize()),
53
+ trace=self.trace_as("json"),
54
+ method="POST",
55
+ headers={
56
+ "Content-Type": "application/json",
57
+ "Authorization": f"Bearer {self.settings.access_token}",
58
+ "user-agent": "Karrio/1.0",
59
+ },
60
+ on_error=provider_utils.parse_error_response,
61
+ )
62
+
63
+ return lib.Deserializable(response, lib.to_dict, request.ctx)
64
+
65
+ def modify_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
66
+ response = self.cancel_pickup(lib.Serializable(request.ctx))
67
+
68
+ if response.deserialize()["ok"]:
69
+ response = self.schedule_pickup(request)
70
+
71
+ return lib.Deserializable(response, lib.to_dict, request.ctx)
72
+
73
+ def cancel_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
74
+ payload = request.serialize()
75
+ response = lib.request(
76
+ url=f"{self.settings.server_url}/v4/collections/{payload['carrier_code']}/{payload['shipmentId']}/cancel",
77
+ trace=self.trace_as("json"),
78
+ method="PUT",
79
+ headers={
80
+ "Content-Type": "application/json",
81
+ "Authorization": f"Bearer {self.settings.access_token}",
82
+ "user-agent": "Karrio/1.0",
83
+ },
84
+ on_error=provider_utils.parse_error_response,
85
+ )
86
+
87
+ return lib.Deserializable(response, lib.to_dict)
@@ -0,0 +1,36 @@
1
+ """Karrio SAPIENT client settings."""
2
+
3
+ import attr
4
+ import typing
5
+ import jstruct
6
+ import karrio.core.models as models
7
+ import karrio.providers.sapient.utils as provider_utils
8
+ import karrio.providers.sapient.units as provider_units
9
+
10
+
11
+ @attr.s(auto_attribs=True)
12
+ class Settings(provider_utils.Settings):
13
+ """SAPIENT connection settings."""
14
+
15
+ # Add carrier specific API connection properties here
16
+ client_id: str
17
+ client_secret: str
18
+ shipping_account_id: str
19
+ sapient_carrier_code: provider_utils.SapientCarrierCode = "RM" # type: ignore
20
+
21
+ # generic properties
22
+ id: str = None
23
+ test_mode: bool = False
24
+ carrier_id: str = "sapient"
25
+ account_country_code: str = "GB"
26
+ metadata: dict = {}
27
+ config: dict = {}
28
+
29
+ services: typing.List[models.ServiceLevel] = jstruct.JList[models.ServiceLevel, False, dict(default=provider_units.DEFAULT_SERVICES)] # type: ignore
30
+
31
+ @property
32
+ def shipping_services(self) -> typing.List[models.ServiceLevel]:
33
+ if any(self.services or []):
34
+ return self.services
35
+
36
+ return provider_units.DEFAULT_SERVICES
@@ -0,0 +1,20 @@
1
+ import karrio.core.metadata as metadata
2
+ import karrio.mappers.sapient as mappers
3
+ import karrio.providers.sapient.units as units
4
+ import karrio.providers.sapient.utils as utils
5
+
6
+
7
+ METADATA = metadata.PluginMetadata(
8
+ status="production-ready",
9
+ id="sapient",
10
+ label="SAPIENT",
11
+ # Integrations
12
+ Mapper=mappers.Mapper,
13
+ Proxy=mappers.Proxy,
14
+ Settings=mappers.Settings,
15
+ # Data Units
16
+ is_hub=True,
17
+ options=units.ShippingOption,
18
+ services=units.ShippingService,
19
+ connection_configs=utils.ConnectionConfig,
20
+ )
@@ -0,0 +1,18 @@
1
+ """Karrio SAPIENT provider imports."""
2
+
3
+ from karrio.providers.sapient.utils import Settings
4
+
5
+ from karrio.providers.sapient.shipment import (
6
+ parse_shipment_cancel_response,
7
+ parse_shipment_response,
8
+ shipment_cancel_request,
9
+ shipment_request,
10
+ )
11
+ from karrio.providers.sapient.pickup import (
12
+ parse_pickup_cancel_response,
13
+ parse_pickup_update_response,
14
+ parse_pickup_response,
15
+ pickup_update_request,
16
+ pickup_cancel_request,
17
+ pickup_request,
18
+ )
@@ -0,0 +1,28 @@
1
+ """Karrio SAPIENT error parser."""
2
+
3
+ import typing
4
+ import karrio.lib as lib
5
+ import karrio.core.models as models
6
+ import karrio.providers.sapient.utils as provider_utils
7
+
8
+
9
+ def parse_error_response(
10
+ response: typing.Union[dict, typing.List[dict]],
11
+ settings: provider_utils.Settings,
12
+ **kwargs,
13
+ ) -> typing.List[models.Message]:
14
+ responses = response if isinstance(response, list) else [response]
15
+ errors: typing.List[dict] = sum(
16
+ [_["Errors"] for _ in responses if "Errors" in _], []
17
+ )
18
+
19
+ return [
20
+ models.Message(
21
+ carrier_id=settings.carrier_id,
22
+ carrier_name=settings.carrier_name,
23
+ code=error.get("ErrorCode", "UNKNOWN"),
24
+ message=error.get("Message", "Unknown error"),
25
+ details=lib.to_dict({**kwargs, "Cause": error.get("Cause")}),
26
+ )
27
+ for error in errors
28
+ ]
@@ -0,0 +1,4 @@
1
+
2
+ from karrio.providers.sapient.pickup.create import parse_pickup_response, pickup_request
3
+ from karrio.providers.sapient.pickup.update import parse_pickup_update_response, pickup_update_request
4
+ from karrio.providers.sapient.pickup.cancel import parse_pickup_cancel_response, pickup_cancel_request
@@ -0,0 +1,57 @@
1
+ import typing
2
+ import karrio.lib as lib
3
+ import karrio.core.units as units
4
+ import karrio.core.models as models
5
+ import karrio.providers.sapient.error as error
6
+ import karrio.providers.sapient.utils as provider_utils
7
+ import karrio.providers.sapient.units as provider_units
8
+
9
+
10
+ def parse_pickup_cancel_response(
11
+ _response: lib.Deserializable[dict],
12
+ settings: provider_utils.Settings,
13
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
14
+ response = _response.deserialize()
15
+ messages = error.parse_error_response(response, settings)
16
+ success = True # compute address validation success state
17
+
18
+ confirmation = (
19
+ models.ConfirmationDetails(
20
+ carrier_id=settings.carrier_id,
21
+ carrier_name=settings.carrier_name,
22
+ operation="Cancel Pickup",
23
+ success=success,
24
+ )
25
+ if success
26
+ else None
27
+ )
28
+
29
+ return confirmation, messages
30
+
31
+
32
+ def pickup_cancel_request(
33
+ payload: models.PickupCancelRequest,
34
+ settings: provider_utils.Settings,
35
+ ) -> lib.Serializable:
36
+ options = lib.units.Options(
37
+ payload.options,
38
+ option_type=lib.units.create_enum(
39
+ "PickupOptions",
40
+ # fmt: off
41
+ {
42
+ "sapient_carrier_code": lib.OptionEnum("sapient_carrier_code"),
43
+ "sapient_shipment_id": lib.OptionEnum("sapient_shipment_id"),
44
+ },
45
+ # fmt: on
46
+ ),
47
+ )
48
+
49
+ # map data to convert karrio model to sapient specific type
50
+ request = dict(
51
+ shipmentId=options.sapient_shipment_id.state,
52
+ carrier_code=lib.identity(
53
+ options.sapient_carrier_code.state or settings.sapient_carrier_code
54
+ ),
55
+ )
56
+
57
+ return lib.Serializable(request, lib.to_dict)
@@ -0,0 +1,87 @@
1
+ import karrio.schemas.sapient.pickup_request as sapient
2
+ import karrio.schemas.sapient.pickup_response as pickup
3
+
4
+ import typing
5
+ import karrio.lib as lib
6
+ import karrio.core.units as units
7
+ import karrio.core.models as models
8
+ import karrio.providers.sapient.error as error
9
+ import karrio.providers.sapient.utils as provider_utils
10
+ import karrio.providers.sapient.units as provider_units
11
+
12
+
13
+ def parse_pickup_response(
14
+ _response: lib.Deserializable[dict],
15
+ settings: provider_utils.Settings,
16
+ ) -> typing.Tuple[typing.List[models.PickupDetails], typing.List[models.Message]]:
17
+ response = _response.deserialize()
18
+
19
+ messages = error.parse_error_response(response, settings)
20
+ pickup = lib.identity(
21
+ _extract_details(response, settings, _response.ctx)
22
+ if "CollectionOrderId" in response
23
+ else None
24
+ )
25
+
26
+ return pickup, messages
27
+
28
+
29
+ def _extract_details(
30
+ data: dict,
31
+ settings: provider_utils.Settings,
32
+ ctx: dict = None,
33
+ ) -> models.PickupDetails:
34
+ details = lib.to_object(pickup.PickupResponseType, data)
35
+
36
+ return models.PickupDetails(
37
+ carrier_id=settings.carrier_id,
38
+ carrier_name=settings.carrier_name,
39
+ confirmation_number=details.CollectionOrderId,
40
+ pickup_date=lib.fdate(details.CollectionDate),
41
+ meta=dict(
42
+ sapient_shipment_id=ctx.get("shipmentId"),
43
+ sapient_carrier_code=ctx.get("carrier_code"),
44
+ ),
45
+ )
46
+
47
+
48
+ def pickup_request(
49
+ payload: models.PickupRequest,
50
+ settings: provider_utils.Settings,
51
+ ) -> lib.Serializable:
52
+ options = lib.units.Options(
53
+ payload.options,
54
+ option_type=lib.units.create_enum(
55
+ "PickupOptions",
56
+ # fmt: off
57
+ {
58
+ "sapient_shipment_id": lib.OptionEnum("sapient_shipment_id"),
59
+ "sapient_carrier_code": lib.OptionEnum("sapient_carrier_code"),
60
+ "sapient_bring_my_label": lib.OptionEnum("BringMyLabel"),
61
+ "sapient_slot_reservation_id": lib.OptionEnum("SlotReservationId"),
62
+ },
63
+ # fmt: on
64
+ ),
65
+ )
66
+
67
+ # map data to convert karrio model to sapient specific type
68
+ request = sapient.PickupRequestType(
69
+ SlotDate=payload.pickup_date,
70
+ SlotReservationId=options.sapient_slot_reservation_id.state,
71
+ BringMyLabel=lib.identity(
72
+ options.sapient_bring_my_label.state
73
+ if options.sapient_bring_my_label.state is not None
74
+ else False
75
+ ),
76
+ )
77
+
78
+ return lib.Serializable(
79
+ request,
80
+ lib.to_dict,
81
+ dict(
82
+ shipmentId=options.sapient_shipment_id.state,
83
+ carrier_code=lib.identity(
84
+ options.sapient_carrier_code.state or settings.sapient_carrier_code
85
+ ),
86
+ ),
87
+ )
@@ -0,0 +1,87 @@
1
+ import karrio.schemas.sapient.pickup_request as sapient
2
+ import karrio.schemas.sapient.pickup_response as pickup
3
+
4
+ import typing
5
+ import karrio.lib as lib
6
+ import karrio.core.units as units
7
+ import karrio.core.models as models
8
+ import karrio.providers.sapient.error as error
9
+ import karrio.providers.sapient.utils as provider_utils
10
+ import karrio.providers.sapient.units as provider_units
11
+
12
+
13
+ def parse_pickup_update_response(
14
+ _response: lib.Deserializable[dict],
15
+ settings: provider_utils.Settings,
16
+ ) -> typing.Tuple[typing.List[models.PickupDetails], typing.List[models.Message]]:
17
+ response = _response.deserialize()
18
+
19
+ messages = error.parse_error_response(response, settings, _response.ctx)
20
+ pickup = lib.identity(
21
+ _extract_details(response, settings)
22
+ if "confirmation_number" in response
23
+ else None
24
+ )
25
+
26
+ return pickup, messages
27
+
28
+
29
+ def _extract_details(
30
+ data: dict,
31
+ settings: provider_utils.Settings,
32
+ ctx: dict = None,
33
+ ) -> models.PickupDetails:
34
+ details = lib.to_object(pickup.PickupResponseType, data)
35
+
36
+ return models.PickupDetails(
37
+ carrier_id=settings.carrier_id,
38
+ carrier_name=settings.carrier_name,
39
+ confirmation_number=details.CollectionOrderId,
40
+ pickup_date=lib.fdate(details.CollectionDate),
41
+ meta=dict(
42
+ sapient_shipment_id=ctx.get("shipmentId"),
43
+ sapient_carrier_code=ctx.get("carrier_code"),
44
+ ),
45
+ )
46
+
47
+
48
+ def pickup_update_request(
49
+ payload: models.PickupUpdateRequest,
50
+ settings: provider_utils.Settings,
51
+ ) -> lib.Serializable:
52
+ options = lib.units.Options(
53
+ payload.options,
54
+ option_type=lib.units.create_enum(
55
+ "PickupOptions",
56
+ # fmt: off
57
+ {
58
+ "sapient_shipment_id": lib.OptionEnum("shipment_id"),
59
+ "sapient_carrier_code": lib.OptionEnum("sapient_carrier_code"),
60
+ "sapient_slot_reservation_id": lib.OptionEnum("SlotReservationId"),
61
+ "sapient_bring_my_label": lib.OptionEnum("BringMyLabel", bool),
62
+ },
63
+ # fmt: on
64
+ ),
65
+ )
66
+
67
+ # map data to convert karrio model to sapient specific type
68
+ request = sapient.PickupRequestType(
69
+ SlotDate=payload.pickup_date,
70
+ SlotReservationId=options.sapient_slot_reservation_id.state,
71
+ BringMyLabel=lib.identity(
72
+ options.sapient_bring_my_label.state
73
+ if options.sapient_bring_my_label.state is not None
74
+ else False
75
+ ),
76
+ )
77
+
78
+ return lib.Serializable(
79
+ request,
80
+ lib.to_dict,
81
+ dict(
82
+ shipmentId=options.sapient_shipment_id.state,
83
+ carrier_code=lib.identity(
84
+ options.sapient_carrier_code.state or settings.sapient_carrier_code
85
+ ),
86
+ ),
87
+ )
@@ -0,0 +1,9 @@
1
+
2
+ from karrio.providers.sapient.shipment.create import (
3
+ parse_shipment_response,
4
+ shipment_request,
5
+ )
6
+ from karrio.providers.sapient.shipment.cancel import (
7
+ parse_shipment_cancel_response,
8
+ shipment_cancel_request,
9
+ )
@@ -0,0 +1,57 @@
1
+ import typing
2
+ import karrio.lib as lib
3
+ import karrio.core.models as models
4
+ import karrio.providers.sapient.error as error
5
+ import karrio.providers.sapient.utils as provider_utils
6
+ import karrio.providers.sapient.units as provider_units
7
+
8
+
9
+ def parse_shipment_cancel_response(
10
+ _response: lib.Deserializable[dict],
11
+ settings: provider_utils.Settings,
12
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
13
+ response = _response.deserialize()
14
+ messages = error.parse_error_response(response, settings)
15
+ success = response.get("ok") is True
16
+
17
+ confirmation = (
18
+ models.ConfirmationDetails(
19
+ carrier_id=settings.carrier_id,
20
+ carrier_name=settings.carrier_name,
21
+ operation="Cancel Shipment",
22
+ success=success,
23
+ )
24
+ if success
25
+ else None
26
+ )
27
+
28
+ return confirmation, messages
29
+
30
+
31
+ def shipment_cancel_request(
32
+ payload: models.ShipmentCancelRequest,
33
+ settings: provider_utils.Settings,
34
+ ) -> lib.Serializable:
35
+ options = lib.units.Options(
36
+ payload.options,
37
+ option_type=lib.units.create_enum(
38
+ "PickupOptions",
39
+ # fmt: off
40
+ {
41
+ "reason": lib.OptionEnum("Reason"),
42
+ "shipment_ids": lib.OptionEnum("ShipmentIds", list),
43
+ },
44
+ # fmt: on
45
+ ),
46
+ )
47
+
48
+ # map data to convert karrio model to sapient specific type
49
+ request = dict(
50
+ Status="Cancel",
51
+ Reason=options.reason.state or "Order Cancelled",
52
+ ShipmentIds=lib.identity(
53
+ options.shipment_ids.state or [payload.shipment_identifier]
54
+ ),
55
+ )
56
+
57
+ return lib.Serializable(request, lib.to_dict)