karrio-zoom2u 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.zoom2u.mapper import Mapper
2
+ from karrio.mappers.zoom2u.proxy import Proxy
3
+ from karrio.mappers.zoom2u.settings import Settings
@@ -0,0 +1,55 @@
1
+
2
+ """Karrio Zoom2u client mapper."""
3
+
4
+ import typing
5
+ import karrio.lib as lib
6
+ import karrio.api.mapper as mapper
7
+ import karrio.core.models as models
8
+ import karrio.providers.zoom2u as provider
9
+ import karrio.mappers.zoom2u.settings as provider_settings
10
+
11
+
12
+ class Mapper(mapper.Mapper):
13
+ settings: provider_settings.Settings
14
+
15
+ def create_rate_request(
16
+ self, payload: models.RateRequest
17
+ ) -> lib.Serializable:
18
+ return provider.rate_request(payload, self.settings)
19
+
20
+ def create_tracking_request(
21
+ self, payload: models.TrackingRequest
22
+ ) -> lib.Serializable:
23
+ return provider.tracking_request(payload, self.settings)
24
+
25
+ def create_shipment_request(
26
+ self, payload: models.ShipmentRequest
27
+ ) -> lib.Serializable:
28
+ return provider.shipment_request(payload, self.settings)
29
+
30
+ def create_cancel_shipment_request(
31
+ self, payload: models.ShipmentCancelRequest
32
+ ) -> lib.Serializable[str]:
33
+ return provider.shipment_cancel_request(payload, self.settings)
34
+
35
+
36
+ def parse_cancel_shipment_response(
37
+ self, response: lib.Deserializable[str]
38
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
39
+ return provider.parse_shipment_cancel_response(response, self.settings)
40
+
41
+ def parse_rate_response(
42
+ self, response: lib.Deserializable[str]
43
+ ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
44
+ return provider.parse_rate_response(response, self.settings)
45
+
46
+ def parse_shipment_response(
47
+ self, response: lib.Deserializable[str]
48
+ ) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
49
+ return provider.parse_shipment_response(response, self.settings)
50
+
51
+ def parse_tracking_response(
52
+ self, response: lib.Deserializable[str]
53
+ ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
54
+ return provider.parse_tracking_response(response, self.settings)
55
+
@@ -0,0 +1,87 @@
1
+ """Karrio Zoom2u client proxy."""
2
+
3
+ import typing
4
+ import karrio.lib as lib
5
+ import karrio.api.proxy as proxy
6
+ import karrio.providers.zoom2u.error as provider_error
7
+ import karrio.providers.zoom2u.utils as provider_utils
8
+ import karrio.mappers.zoom2u.settings as provider_settings
9
+
10
+
11
+ class Proxy(proxy.Proxy):
12
+ settings: provider_settings.Settings
13
+
14
+ def get_rates(self, request: lib.Serializable) -> lib.Deserializable[str]:
15
+ response = lib.request(
16
+ url=f"{self.settings.server_url}/api/v1/delivery/quote",
17
+ data=lib.to_json(request.serialize()),
18
+ trace=self.trace_as("json"),
19
+ method="POST",
20
+ headers={
21
+ "Content-Type": "application/json",
22
+ "Authorization": f"Bearer {self.settings.api_key}",
23
+ },
24
+ on_error=provider_error.parse_http_response,
25
+ )
26
+
27
+ return lib.Deserializable(response, lib.to_dict)
28
+
29
+ def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
30
+ response = lib.request(
31
+ url=f"{self.settings.server_url}/api/v1/delivery/create",
32
+ data=lib.to_json(request.serialize()),
33
+ trace=self.trace_as("json"),
34
+ method="POST",
35
+ headers={
36
+ "Content-Type": "application/json",
37
+ "Authorization": f"Bearer {self.settings.api_key}",
38
+ },
39
+ on_error=provider_error.parse_http_response,
40
+ )
41
+
42
+ return lib.Deserializable(
43
+ response,
44
+ lambda _: lib.to_dict(provider_utils.clean_response(_)),
45
+ )
46
+
47
+ def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
48
+ payload = request.serialize()
49
+ response = lib.request(
50
+ url=f"{self.settings.server_url}/api/v1/delivery/cancel/{payload['reference']}",
51
+ trace=self.trace_as("json"),
52
+ method="POST",
53
+ headers={
54
+ "Content-Type": "application/json",
55
+ "Authorization": f"Bearer {self.settings.api_key}",
56
+ },
57
+ on_error=provider_error.parse_http_response,
58
+ decoder=lambda _: dict(ok=True),
59
+ )
60
+
61
+ return lib.Deserializable(response, lib.to_dict)
62
+
63
+ def get_tracking(self, request: lib.Serializable) -> lib.Deserializable[str]:
64
+ def _get_tracking(reference: str):
65
+ return reference, lib.request(
66
+ url=f"{self.settings.server_url}/api/v1/delivery/status/{reference}",
67
+ trace=self.trace_as("json"),
68
+ method="GET",
69
+ headers={
70
+ "Content-Type": "application/json",
71
+ "Authorization": f"Bearer {self.settings.api_key}",
72
+ },
73
+ on_error=provider_error.parse_http_response,
74
+ )
75
+
76
+ responses: typing.List[typing.Tuple[str, str]] = lib.run_concurently(
77
+ _get_tracking, request.serialize()
78
+ )
79
+
80
+ return lib.Deserializable(
81
+ responses,
82
+ lambda res: [
83
+ (num, lib.to_dict(provider_utils.clean_response(track)))
84
+ for num, track in res
85
+ if any(track.strip())
86
+ ],
87
+ )
@@ -0,0 +1,20 @@
1
+ """Karrio Zoom2u client settings."""
2
+
3
+ import attr
4
+ import karrio.providers.zoom2u.utils as provider_utils
5
+
6
+
7
+ @attr.s(auto_attribs=True)
8
+ class Settings(provider_utils.Settings):
9
+ """Zoom2u connection settings."""
10
+
11
+ # required carrier specific properties
12
+ api_key: str
13
+
14
+ # generic properties
15
+ id: str = None
16
+ test_mode: bool = False
17
+ carrier_id: str = "zoom2u"
18
+ account_country_code: str = "AU"
19
+ metadata: dict = {}
20
+ config: dict = {}
@@ -0,0 +1,19 @@
1
+ import karrio.core.metadata as metadata
2
+ import karrio.mappers.zoom2u as mappers
3
+ import karrio.providers.zoom2u.units as units
4
+
5
+
6
+ METADATA = metadata.PluginMetadata(
7
+ status="beta",
8
+ id="zoom2u",
9
+ label="Zoom2u",
10
+ # Integrations
11
+ Mapper=mappers.Mapper,
12
+ Proxy=mappers.Proxy,
13
+ Settings=mappers.Settings,
14
+ # Data Units
15
+ is_hub=False,
16
+ services=units.ShippingService,
17
+ options=units.ShippingOption,
18
+ )
19
+
@@ -0,0 +1,13 @@
1
+
2
+ from karrio.providers.zoom2u.utils import Settings
3
+ from karrio.providers.zoom2u.rate import parse_rate_response, rate_request
4
+ from karrio.providers.zoom2u.shipment import (
5
+ parse_shipment_cancel_response,
6
+ parse_shipment_response,
7
+ shipment_cancel_request,
8
+ shipment_request,
9
+ )
10
+ from karrio.providers.zoom2u.tracking import (
11
+ parse_tracking_response,
12
+ tracking_request,
13
+ )
@@ -0,0 +1,46 @@
1
+ import typing
2
+ import urllib.error
3
+ import karrio.lib as lib
4
+ import karrio.core.models as models
5
+ import karrio.providers.zoom2u.utils as provider_utils
6
+
7
+
8
+ def parse_error_response(
9
+ responses: typing.Union[typing.List[dict], dict],
10
+ settings: provider_utils.Settings,
11
+ **kwargs,
12
+ ) -> typing.List[models.Message]:
13
+ results = responses if isinstance(responses, list) else [responses]
14
+ errors: typing.List[dict] = [
15
+ error for error in results if error.get("message") is not None
16
+ ]
17
+
18
+ return [
19
+ models.Message(
20
+ carrier_id=settings.carrier_id,
21
+ carrier_name=settings.carrier_name,
22
+ code=error.get("error-code"),
23
+ message=error.get("message"),
24
+ details=lib.to_dict(
25
+ {
26
+ **kwargs,
27
+ "modelState": error.get("modelState"),
28
+ }
29
+ ),
30
+ )
31
+ for error in errors
32
+ ]
33
+
34
+
35
+ def parse_http_response(response: urllib.error.HTTPError) -> dict:
36
+ try:
37
+ return lib.decode(response.read())
38
+ except Exception:
39
+ pass
40
+
41
+ return lib.to_json(
42
+ {
43
+ "error-code": response.code,
44
+ "message": response.reason,
45
+ }
46
+ )
@@ -0,0 +1,109 @@
1
+ import karrio.schemas.zoom2u.rate_request as zoom2u
2
+ import karrio.schemas.zoom2u.rate_response as rating
3
+ import typing
4
+ import karrio.lib as lib
5
+ import karrio.core.units as units
6
+ import karrio.core.models as models
7
+ import karrio.providers.zoom2u.error as error
8
+ import karrio.providers.zoom2u.utils as provider_utils
9
+ import karrio.providers.zoom2u.units as provider_units
10
+
11
+
12
+ def parse_rate_response(
13
+ _response: lib.Deserializable[dict],
14
+ settings: provider_utils.Settings,
15
+ ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
16
+ _responses = _response.deserialize()
17
+ responses = _responses if isinstance(_responses, list) else [_responses]
18
+
19
+ messages = error.parse_error_response(responses, settings)
20
+ rates = [
21
+ _extract_details(rate, settings)
22
+ for rate in responses
23
+ if rate.get("message") is None
24
+ ]
25
+
26
+ return rates, messages
27
+
28
+
29
+ def _extract_details(
30
+ data: dict,
31
+ settings: provider_utils.Settings,
32
+ ) -> models.RateDetails:
33
+ rate = lib.to_object(rating.RateResponseElementType, data)
34
+ service = provider_units.ShippingService.map(rate.deliverySpeed)
35
+
36
+ return models.RateDetails(
37
+ carrier_id=settings.carrier_id,
38
+ carrier_name=settings.carrier_name,
39
+ service=service.name_or_key,
40
+ total_charge=lib.to_money(rate.price),
41
+ currency=settings.connection_config.currency.state or "AUD",
42
+ transit_days=1,
43
+ estimated_delivery=lib.fdate(rate.deliveredBy, "%Y-%m-%dT%H:%M:%S%z"),
44
+ meta=dict(
45
+ service_name=service.value_or_key,
46
+ earliestPickupEta=rate.earliestPickupEta,
47
+ earliestDropEta=rate.earliestDropEta,
48
+ ),
49
+ )
50
+
51
+
52
+ def rate_request(
53
+ payload: models.RateRequest,
54
+ settings: provider_utils.Settings,
55
+ ) -> lib.Serializable:
56
+ shipper = lib.to_address(payload.shipper)
57
+ recipient = lib.to_address(payload.recipient)
58
+ package = lib.to_packages(payload.parcels).single
59
+ service = lib.to_services(payload.services, provider_units.ShippingService).first
60
+ options = lib.to_shipping_options(
61
+ payload.options,
62
+ package_options=package.options,
63
+ option_type=provider_units.ShippingOption,
64
+ )
65
+
66
+ request = zoom2u.RateRequestType(
67
+ PurchaseOrderNumber=options.purchase_order_number.state or payload.reference,
68
+ PackageDescription=package.description,
69
+ DeliverySpeed=getattr(service, "value", None),
70
+ ReadyDateTime=lib.fdatetime(
71
+ options.ready_datetime.state,
72
+ current_format="%Y-%m-%d %H:%M:%S",
73
+ output_format="%Y-%m-%dT%H:%M:%S.%fZ",
74
+ ),
75
+ VehicleType=provider_units.VehiculeType.map(
76
+ options.vehicle_type.state or "Car"
77
+ ).value,
78
+ PackageType=provider_units.PackagingType.map(
79
+ package.packaging_type or "Box"
80
+ ).value,
81
+ Pickup=zoom2u.DropoffType(
82
+ ContactName=shipper.contact,
83
+ Email=shipper.email,
84
+ Phone=shipper.phone_number,
85
+ UnitNumber=None,
86
+ StreetNumber=shipper.street_number,
87
+ Street=shipper.street_name,
88
+ Suburb=shipper.city,
89
+ State=shipper.state_code,
90
+ Postcode=shipper.postal_code,
91
+ Country=shipper.country_name,
92
+ Notes=options.pickup_notes.state,
93
+ ),
94
+ Dropoff=zoom2u.DropoffType(
95
+ ContactName=recipient.contact,
96
+ Email=recipient.email,
97
+ Phone=recipient.phone_number,
98
+ UnitNumber=None,
99
+ StreetNumber=recipient.street_number,
100
+ Street=recipient.street_name,
101
+ Suburb=recipient.city,
102
+ State=recipient.state_code,
103
+ Postcode=recipient.postal_code,
104
+ Country=recipient.country_name,
105
+ Notes=options.dropoff_notes.state,
106
+ ),
107
+ )
108
+
109
+ return lib.Serializable(request, lib.to_dict)
@@ -0,0 +1,9 @@
1
+
2
+ from karrio.providers.zoom2u.shipment.create import (
3
+ parse_shipment_response,
4
+ shipment_request,
5
+ )
6
+ from karrio.providers.zoom2u.shipment.cancel import (
7
+ parse_shipment_cancel_response,
8
+ shipment_cancel_request,
9
+ )
@@ -0,0 +1,37 @@
1
+ import typing
2
+ import karrio.lib as lib
3
+ import karrio.core.models as models
4
+ import karrio.providers.zoom2u.error as error
5
+ import karrio.providers.zoom2u.utils as provider_utils
6
+ import karrio.providers.zoom2u.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_messages: list = [] # extract carrier response errors and messages
14
+ messages = error.parse_error_response(response_messages, settings)
15
+ success = len(messages) == 0
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
+ request = dict(reference=payload.shipment_identifier)
36
+
37
+ return lib.Serializable(request)
@@ -0,0 +1,100 @@
1
+ import karrio.schemas.zoom2u.shipping_request as zoom2u
2
+ import karrio.schemas.zoom2u.shipping_response as shipping
3
+ import typing
4
+ import karrio.lib as lib
5
+ import karrio.core.units as units
6
+ import karrio.core.models as models
7
+ import karrio.providers.zoom2u.error as error
8
+ import karrio.providers.zoom2u.utils as provider_utils
9
+ import karrio.providers.zoom2u.units as provider_units
10
+
11
+
12
+ def parse_shipment_response(
13
+ _response: lib.Deserializable[dict],
14
+ settings: provider_utils.Settings,
15
+ ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
16
+ response = _response.deserialize()
17
+ messages = error.parse_error_response(response, settings)
18
+ shipment = _extract_details(response, settings) if len(messages) == 0 else None
19
+
20
+ return shipment, messages
21
+
22
+
23
+ def _extract_details(
24
+ data: dict,
25
+ settings: provider_utils.Settings,
26
+ ) -> models.ShipmentDetails:
27
+ shipment = lib.to_object(shipping.ShippingResponseType, data)
28
+
29
+ return models.ShipmentDetails(
30
+ carrier_id=settings.carrier_id,
31
+ carrier_name=settings.carrier_name,
32
+ tracking_number=shipment.reference,
33
+ shipment_identifier=shipment.reference,
34
+ label_type="PDF",
35
+ docs=models.Documents(label="No label..."),
36
+ meta=dict(
37
+ trackingCode=shipment.trackingCode,
38
+ carrier_tracking_link=shipment.trackinglink,
39
+ ),
40
+ )
41
+
42
+
43
+ def shipment_request(
44
+ payload: models.ShipmentRequest,
45
+ settings: provider_utils.Settings,
46
+ ) -> lib.Serializable:
47
+ shipper = lib.to_address(payload.shipper)
48
+ recipient = lib.to_address(payload.recipient)
49
+ package = lib.to_packages(payload.parcels).single
50
+ service = provider_units.ShippingService.map(payload.service).value_or_key
51
+ options = lib.to_shipping_options(
52
+ payload.options,
53
+ package_options=package.options,
54
+ option_type=provider_units.ShippingOption,
55
+ )
56
+
57
+ request = zoom2u.ShippingRequestType(
58
+ PurchaseOrderNumber=options.purchase_order_number.state or payload.reference,
59
+ PackageDescription=package.description,
60
+ DeliverySpeed=service,
61
+ ReadyDateTime=lib.fdatetime(
62
+ options.ready_datetime.state,
63
+ current_format="%Y-%m-%d %H:%M:%S",
64
+ output_format="%Y-%m-%dT%H:%M:%S.%fZ",
65
+ ),
66
+ VehicleType=provider_units.VehiculeType.map(
67
+ options.vehicle_type.state or "Car"
68
+ ).value,
69
+ PackageType=provider_units.PackagingType.map(
70
+ package.packaging_type or "Box"
71
+ ).value,
72
+ Pickup=zoom2u.DropoffType(
73
+ ContactName=shipper.contact,
74
+ Email=shipper.email,
75
+ Phone=shipper.phone_number,
76
+ UnitNumber=None,
77
+ StreetNumber=shipper.street_number,
78
+ Street=shipper.street_name,
79
+ Suburb=shipper.city,
80
+ State=shipper.state_code,
81
+ Postcode=shipper.postal_code,
82
+ Country=shipper.country_name,
83
+ Notes=options.pickup_notes.state,
84
+ ),
85
+ Dropoff=zoom2u.DropoffType(
86
+ ContactName=recipient.contact,
87
+ Email=recipient.email,
88
+ Phone=recipient.phone_number,
89
+ UnitNumber=None,
90
+ StreetNumber=recipient.street_number,
91
+ Street=recipient.street_name,
92
+ Suburb=recipient.city,
93
+ State=recipient.state_code,
94
+ Postcode=recipient.postal_code,
95
+ Country=recipient.country_name,
96
+ Notes=options.dropoff_notes.state,
97
+ ),
98
+ )
99
+
100
+ return lib.Serializable(request, lib.to_dict)
@@ -0,0 +1,79 @@
1
+ import karrio.schemas.zoom2u.tracking_response as locate2u
2
+ import typing
3
+ import karrio.lib as lib
4
+ import karrio.core.units as units
5
+ import karrio.core.models as models
6
+ import karrio.providers.zoom2u.error as error
7
+ import karrio.providers.zoom2u.utils as provider_utils
8
+ import karrio.providers.zoom2u.units as provider_units
9
+
10
+
11
+ def parse_tracking_response(
12
+ _responses: lib.Deserializable[typing.List[typing.Tuple[str, dict]]],
13
+ settings: provider_utils.Settings,
14
+ ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
15
+ responses = _responses.deserialize()
16
+ messages: typing.List[models.Message] = sum(
17
+ [
18
+ error.parse_error_response(response, settings, tracking_number=_)
19
+ for _, response in responses
20
+ if response.get("message") is not None
21
+ ],
22
+ [],
23
+ )
24
+
25
+ tracking_details = [
26
+ _extract_details(response, settings)
27
+ for _, response in responses
28
+ if response.get("message") is None
29
+ ]
30
+
31
+ return tracking_details, messages
32
+
33
+
34
+ def _extract_details(
35
+ data: dict,
36
+ settings: provider_utils.Settings,
37
+ ) -> models.TrackingDetails:
38
+ tracking = lib.to_object(locate2u.TrackingResponseType, data)
39
+ status = next(
40
+ (
41
+ status.name
42
+ for status in list(provider_units.TrackingStatus)
43
+ if tracking.status in status.value
44
+ ),
45
+ provider_units.TrackingStatus.in_transit.name,
46
+ )
47
+
48
+ return models.TrackingDetails(
49
+ carrier_id=settings.carrier_id,
50
+ carrier_name=settings.carrier_name,
51
+ tracking_number=tracking.reference,
52
+ events=[
53
+ models.TrackingEvent(
54
+ date=lib.fdate(tracking.statusChangeDateTime, "%Y-%m-%dT%H:%M:%S.%fZ"),
55
+ description=tracking.status,
56
+ code=tracking.status,
57
+ time=lib.flocaltime(
58
+ tracking.statusChangeDateTime, "%Y-%m-%dT%H:%M:%S.%fZ"
59
+ ),
60
+ )
61
+ ],
62
+ delivered=(status == "delivered"),
63
+ info=models.TrackingInfo(
64
+ carrier_tracking_link=tracking.trackinglink,
65
+ ),
66
+ meta=dict(
67
+ proofOfDeliveryPhotoUrl=data.get("proofOfDeliveryPhotoUrl"),
68
+ signatureUrl=data.get("signatureUrl"),
69
+ ),
70
+ )
71
+
72
+
73
+ def tracking_request(
74
+ payload: models.TrackingRequest,
75
+ settings: provider_utils.Settings,
76
+ ) -> lib.Serializable:
77
+ request = payload.tracking_numbers
78
+
79
+ return lib.Serializable(request)
@@ -0,0 +1,81 @@
1
+ import enum
2
+ import karrio.lib as lib
3
+ import karrio.core.units as units
4
+
5
+
6
+ class PackagingType(lib.StrEnum):
7
+ """Carrier specific packaging type"""
8
+
9
+ zoom2u_documents = "Documents"
10
+ zoom2u_bag = "Bag"
11
+ zoom2u_box = "Box"
12
+ zoom2u_flowers = "Flowers"
13
+ zoom2u_custom = "Custom"
14
+
15
+ """ Unified Packaging type mapping """
16
+ envelope = zoom2u_documents
17
+ pak = zoom2u_bag
18
+ tube = zoom2u_custom
19
+ pallet = zoom2u_custom
20
+ small_box = zoom2u_box
21
+ medium_box = zoom2u_box
22
+ your_packaging = zoom2u_custom
23
+
24
+
25
+ class ConnectionConfig(lib.Enum):
26
+ currency = lib.OptionEnum(
27
+ "currency", lib.units.create_enum("Currency", ["AUD", "USD"])
28
+ )
29
+ shipping_options = lib.OptionEnum("shipping_options", list)
30
+ shipping_services = lib.OptionEnum("shipping_services", list)
31
+
32
+
33
+ class VehiculeType(lib.StrEnum):
34
+ """Zoom2u vehicule type"""
35
+
36
+ zoom2u_bike = "Bike"
37
+ zoom2u_car = "Car"
38
+ zoom2u_van = "Van"
39
+
40
+
41
+ class ShippingService(lib.StrEnum):
42
+ """Carrier specific services"""
43
+
44
+ zoom2u_VIP = "VIP"
45
+ zoom2u_3_hour = "3 hour"
46
+ zoom2u_same_day = "Same day"
47
+
48
+
49
+ class ShippingOption(lib.Enum):
50
+ """Carrier specific options"""
51
+
52
+ purchase_order_number = lib.OptionEnum("purchase_order_number")
53
+ ready_datetime = lib.OptionEnum("ready_datetime")
54
+ vehicle_type = lib.OptionEnum("vehicle_type")
55
+ pickup_notes = lib.OptionEnum("pickup_notes")
56
+ dropoff_notes = lib.OptionEnum("dropoff_notes")
57
+
58
+
59
+ def shipping_options_initializer(
60
+ options: dict,
61
+ package_options: units.ShippingOptions = None,
62
+ ) -> units.ShippingOptions:
63
+ """
64
+ Apply default values to the given options.
65
+ """
66
+
67
+ if package_options is not None:
68
+ options.update(package_options.content)
69
+
70
+ def items_filter(key: str) -> bool:
71
+ return key in ShippingOption # type: ignore
72
+
73
+ return units.ShippingOptions(options, ShippingOption, items_filter=items_filter)
74
+
75
+
76
+ class TrackingStatus(lib.Enum):
77
+ on_hold = ["Unassigned", "Accepted", "On Hold - With Courier"]
78
+ delivered = ["Dropped Off"]
79
+ in_transit = ["On Route to Pickup", "Picked up"]
80
+ delivery_failed = ["Cancelled", "Returning", "Returned"]
81
+ out_for_delivery = ["On Route to Dropoff", "Tried to deliver"]
@@ -0,0 +1,33 @@
1
+ import karrio.lib as lib
2
+ import karrio.core as core
3
+
4
+
5
+ class Settings(core.Settings):
6
+ """Zoom2u connection settings."""
7
+
8
+ api_key: str
9
+
10
+ account_country_code: str = "AU"
11
+ metadata: dict = {}
12
+ config: dict = {}
13
+
14
+ @property
15
+ def carrier_name(self):
16
+ return "zoom2u"
17
+
18
+ @property
19
+ def server_url(self):
20
+ return "https://api.zoom2u.com"
21
+
22
+ @property
23
+ def connection_config(self) -> lib.units.Options:
24
+ from karrio.providers.zoom2u.units import ConnectionConfig
25
+
26
+ return lib.to_connection_config(
27
+ self.config or {},
28
+ option_type=ConnectionConfig,
29
+ )
30
+
31
+
32
+ def clean_response(response: str):
33
+ return response.replace("tracking-link", "trackinglink")
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 ModelStateType:
8
+ getQuoteRequestPickupSuburb: typing.Optional[typing.List[str]] = None
9
+ getQuoteRequestPickupPostcode: typing.Optional[typing.List[str]] = None
10
+
11
+
12
+ @attr.s(auto_attribs=True)
13
+ class ErrorResponseType:
14
+ errorcode: typing.Optional[str] = None
15
+ message: typing.Optional[str] = None
16
+ modelState: typing.Optional[ModelStateType] = jstruct.JStruct[ModelStateType]
@@ -0,0 +1,30 @@
1
+ import attr
2
+ import jstruct
3
+ import typing
4
+
5
+
6
+ @attr.s(auto_attribs=True)
7
+ class DropoffType:
8
+ ContactName: typing.Optional[str] = None
9
+ Email: typing.Optional[str] = None
10
+ Phone: typing.Optional[str] = None
11
+ UnitNumber: typing.Optional[str] = None
12
+ StreetNumber: typing.Optional[int] = None
13
+ Street: typing.Optional[str] = None
14
+ Suburb: typing.Optional[str] = None
15
+ State: typing.Optional[str] = None
16
+ Postcode: typing.Optional[int] = None
17
+ Country: typing.Optional[str] = None
18
+ Notes: typing.Optional[str] = None
19
+
20
+
21
+ @attr.s(auto_attribs=True)
22
+ class RateRequestType:
23
+ PurchaseOrderNumber: typing.Optional[str] = None
24
+ PackageDescription: typing.Optional[str] = None
25
+ DeliverySpeed: typing.Optional[str] = None
26
+ ReadyDateTime: typing.Optional[str] = None
27
+ VehicleType: typing.Optional[str] = None
28
+ PackageType: typing.Optional[str] = None
29
+ Pickup: typing.Optional[DropoffType] = jstruct.JStruct[DropoffType]
30
+ Dropoff: typing.Optional[DropoffType] = jstruct.JStruct[DropoffType]
@@ -0,0 +1,12 @@
1
+ import attr
2
+ import jstruct
3
+ import typing
4
+
5
+
6
+ @attr.s(auto_attribs=True)
7
+ class RateResponseElementType:
8
+ deliverySpeed: typing.Optional[str] = None
9
+ price: typing.Optional[int] = None
10
+ deliveredBy: typing.Optional[str] = None
11
+ earliestPickupEta: typing.Optional[str] = None
12
+ earliestDropEta: typing.Optional[str] = None
@@ -0,0 +1,30 @@
1
+ import attr
2
+ import jstruct
3
+ import typing
4
+
5
+
6
+ @attr.s(auto_attribs=True)
7
+ class DropoffType:
8
+ ContactName: typing.Optional[str] = None
9
+ Email: typing.Optional[str] = None
10
+ Phone: typing.Optional[str] = None
11
+ UnitNumber: typing.Optional[str] = None
12
+ StreetNumber: typing.Optional[int] = None
13
+ Street: typing.Optional[str] = None
14
+ Suburb: typing.Optional[str] = None
15
+ State: typing.Optional[str] = None
16
+ Postcode: typing.Optional[int] = None
17
+ Country: typing.Optional[str] = None
18
+ Notes: typing.Optional[str] = None
19
+
20
+
21
+ @attr.s(auto_attribs=True)
22
+ class ShippingRequestType:
23
+ PurchaseOrderNumber: typing.Optional[str] = None
24
+ PackageDescription: typing.Optional[str] = None
25
+ DeliverySpeed: typing.Optional[str] = None
26
+ ReadyDateTime: typing.Optional[str] = None
27
+ VehicleType: typing.Optional[str] = None
28
+ PackageType: typing.Optional[str] = None
29
+ Pickup: typing.Optional[DropoffType] = jstruct.JStruct[DropoffType]
30
+ Dropoff: typing.Optional[DropoffType] = jstruct.JStruct[DropoffType]
@@ -0,0 +1,11 @@
1
+ import attr
2
+ import jstruct
3
+ import typing
4
+
5
+
6
+ @attr.s(auto_attribs=True)
7
+ class ShippingResponseType:
8
+ reference: typing.Optional[str] = None
9
+ price: typing.Optional[float] = None
10
+ trackinglink: typing.Optional[str] = None
11
+ trackingCode: typing.Optional[str] = None
@@ -0,0 +1,22 @@
1
+ import attr
2
+ import jstruct
3
+ import typing
4
+
5
+
6
+ @attr.s(auto_attribs=True)
7
+ class CourierType:
8
+ id: typing.Optional[int] = None
9
+ name: typing.Optional[str] = None
10
+ phone: typing.Optional[str] = None
11
+
12
+
13
+ @attr.s(auto_attribs=True)
14
+ class TrackingResponseType:
15
+ reference: typing.Optional[str] = None
16
+ status: typing.Optional[str] = None
17
+ statusChangeDateTime: typing.Optional[str] = None
18
+ purchaseOrderNumber: typing.Optional[str] = None
19
+ trackinglink: typing.Optional[str] = None
20
+ proofOfDeliveryPhotoUrl: typing.Any = None
21
+ signatureUrl: typing.Any = None
22
+ courier: typing.Optional[CourierType] = jstruct.JStruct[CourierType]
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.4
2
+ Name: karrio_zoom2u
3
+ Version: 2025.5rc1
4
+ Summary: Karrio - Zoom2u 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.zoom2u
17
+
18
+ This package is a Zoom2u 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.zoom2u
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```python
33
+ import karrio.sdk as karrio
34
+ from karrio.mappers.zoom2u.settings import Settings
35
+
36
+
37
+ # Initialize a carrier gateway
38
+ zoom2u = karrio.gateway["zoom2u"].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,26 @@
1
+ karrio/mappers/zoom2u/__init__.py,sha256=Mt_JDxjJtR4DbgqSjnBl4n3HARPHL90TJK8d92p8VGI,146
2
+ karrio/mappers/zoom2u/mapper.py,sha256=sqj3ngo2Lx3v871RE-yJ8njY3U-ErktqRY5jU1ljwyw,2016
3
+ karrio/mappers/zoom2u/proxy.py,sha256=vP7SwlJD4wLecI0HD_fH7McsTedyWcmb1DBLCj5eFew,3212
4
+ karrio/mappers/zoom2u/settings.py,sha256=MlLgw7qTJO-hSyoTRKhON4MXP-pYPKkJyEy2xNSbUNo,461
5
+ karrio/plugins/zoom2u/__init__.py,sha256=HB-FWZu1weeKkYgij0P9Sv-08v2BC1ul0MW_CaieLfU,430
6
+ karrio/providers/zoom2u/__init__.py,sha256=PeRbCr6K--JSfSqXocP_ARXftILCkaz-L8xAWpvs03M,392
7
+ karrio/providers/zoom2u/error.py,sha256=_V3OReCkDXSDTYyTXFik6p5c2u4Rd4MU772KDBQwJxo,1221
8
+ karrio/providers/zoom2u/rate.py,sha256=Ot3Fs4ESS2LYltT99a7l2CejwhTbmivIAZoR0jV78-E,3889
9
+ karrio/providers/zoom2u/tracking.py,sha256=G_Y69pNK647fgJf7mb6e1DmNH8ZXMCOhtEWmrcZsALk,2503
10
+ karrio/providers/zoom2u/units.py,sha256=dHAvrXTXsxLgXjDjzmoMWpcruyGs3cfNVsRTVI4rZfw,2221
11
+ karrio/providers/zoom2u/utils.py,sha256=8r3E5PjGlx7uSd3KYxafrQQHOKAu4rGmfzA2AM8Gd10,732
12
+ karrio/providers/zoom2u/shipment/__init__.py,sha256=M6fY2Koib_HDcY3fMKH3OU6MTSMEXQOWnOwsiQa-i2c,229
13
+ karrio/providers/zoom2u/shipment/cancel.py,sha256=KCWHM63qkDLHGIXoPalItH1poWZy87yqus600b8qEBk,1149
14
+ karrio/providers/zoom2u/shipment/create.py,sha256=C2fTpV0ti2QojWsbLXzsJkOkZZNu0GVzr0gWldiDSZ4,3570
15
+ karrio/schemas/zoom2u/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ karrio/schemas/zoom2u/error_response.py,sha256=MQZfMjQzmcXzfddTe6jbN5aB6WJXPoXhEq9ZAnHrLTU,462
17
+ karrio/schemas/zoom2u/rate_request.py,sha256=cNzlD1s2Fo0rgVQywdJrIpLw8iAlpymK5NKjSfdRLUY,1029
18
+ karrio/schemas/zoom2u/rate_response.py,sha256=A7tUlD6yAhsLT2LImD2nXWxiNhepyNIRPIroRpmwNsA,332
19
+ karrio/schemas/zoom2u/shipping_request.py,sha256=qB0FzpYBVJiyeo61DqhvcQkHFN5lCQ7grSx5t4fBHp0,1033
20
+ karrio/schemas/zoom2u/shipping_response.py,sha256=tUoz5nX-UXpet9bh8pUeq3_42SryYKli9ujE4KFql3Y,274
21
+ karrio/schemas/zoom2u/tracking_response.py,sha256=23aEGIMlDRu7bjZxsh2UgLd85Vwhs-nIuq0GA9UUAms,651
22
+ karrio_zoom2u-2025.5rc1.dist-info/METADATA,sha256=F6eRy7P-23XR0CdE34gS0_TcxsLSpM58R2Bia6hIzCM,984
23
+ karrio_zoom2u-2025.5rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
+ karrio_zoom2u-2025.5rc1.dist-info/entry_points.txt,sha256=ItGT5y_xC6B_LSYJuSOMDMPeQ8t8LSavustGfFXbGIY,57
25
+ karrio_zoom2u-2025.5rc1.dist-info/top_level.txt,sha256=FZCY8Nwft8oEGHdl--xku8P3TrnOxu5dETEU_fWpRSM,20
26
+ karrio_zoom2u-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
+ zoom2u = karrio.plugins.zoom2u:METADATA
@@ -0,0 +1,3 @@
1
+ dist
2
+ karrio
3
+ schemas