karrio-locate2u 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.locate2u.mapper import Mapper
2
+ from karrio.mappers.locate2u.proxy import Proxy
3
+ from karrio.mappers.locate2u.settings import Settings
@@ -0,0 +1,51 @@
1
+ """Karrio Locate2u 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.locate2u as provider
8
+ import karrio.mappers.locate2u.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_tracking_request(
19
+ self, payload: models.TrackingRequest
20
+ ) -> lib.Serializable:
21
+ return provider.tracking_request(payload, self.settings)
22
+
23
+ def create_shipment_request(
24
+ self, payload: models.ShipmentRequest
25
+ ) -> lib.Serializable:
26
+ return provider.shipment_request(payload, self.settings)
27
+
28
+ def create_cancel_shipment_request(
29
+ self, payload: models.ShipmentCancelRequest
30
+ ) -> lib.Serializable[str]:
31
+ return provider.shipment_cancel_request(payload, self.settings)
32
+
33
+ def parse_cancel_shipment_response(
34
+ self, response: lib.Deserializable
35
+ ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
36
+ return provider.parse_shipment_cancel_response(response, self.settings)
37
+
38
+ def parse_rate_response(
39
+ self, response: lib.Deserializable
40
+ ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
41
+ return universal_provider.parse_rate_response(response, self.settings)
42
+
43
+ def parse_shipment_response(
44
+ self, response: lib.Deserializable[str]
45
+ ) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
46
+ return provider.parse_shipment_response(response, self.settings)
47
+
48
+ def parse_tracking_response(
49
+ self, response: lib.Deserializable[str]
50
+ ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
51
+ return provider.parse_tracking_response(response, self.settings)
@@ -0,0 +1,69 @@
1
+ """Karrio Locate2u client proxy."""
2
+
3
+ import typing
4
+ import karrio.lib as lib
5
+ import karrio.api.proxy as proxy
6
+ import karrio.providers.locate2u.error as provider_error
7
+ import karrio.mappers.locate2u.settings as provider_settings
8
+ import karrio.universal.mappers.rating_proxy as rating_proxy
9
+
10
+
11
+ class Proxy(rating_proxy.RatingMixinProxy, proxy.Proxy):
12
+ settings: provider_settings.Settings
13
+
14
+ def get_rates(self, request: lib.Serializable) -> lib.Deserializable:
15
+ return super().get_rates(request)
16
+
17
+ def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
18
+ response = lib.request(
19
+ url=f"{self.settings.server_url}/api/v1/stops",
20
+ data=lib.to_json(request.serialize()),
21
+ trace=self.trace_as("json"),
22
+ method="POST",
23
+ headers={
24
+ "Content-Type": "application/json",
25
+ "Authorization": f"Bearer {self.settings.access_token}",
26
+ },
27
+ on_error=provider_error.parse_http_response,
28
+ )
29
+
30
+ return lib.Deserializable(response, lib.to_dict)
31
+
32
+ def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
33
+ payload = request.serialize()
34
+ response = lib.request(
35
+ url=f"{self.settings.server_url}/api/v1/stops/{payload['stopId']}",
36
+ trace=self.trace_as("json"),
37
+ method="DELETE",
38
+ headers={
39
+ "Content-Type": "application/json",
40
+ "Authorization": f"Bearer {self.settings.access_token}",
41
+ },
42
+ on_error=provider_error.parse_http_response,
43
+ decoder=lambda _: dict(ok=True),
44
+ )
45
+
46
+ return lib.Deserializable(response, lib.to_dict)
47
+
48
+ def get_tracking(self, request: lib.Serializable) -> lib.Deserializable[str]:
49
+ def _get_tracking(stop_id: str):
50
+ return stop_id, lib.request(
51
+ url=f"{self.settings.server_url}/api/v1/stops/{stop_id}?includeItems=false&includeLines=false",
52
+ trace=self.trace_as("json"),
53
+ method="GET",
54
+ headers={
55
+ "Content-Type": "application/json",
56
+ "Authorization": f"Bearer {self.settings.access_token}",
57
+ },
58
+ on_error=provider_error.parse_http_response,
59
+ )
60
+
61
+ responses: typing.List[typing.Tuple[str, str]] = lib.run_concurently(
62
+ _get_tracking, request.serialize()
63
+ )
64
+ return lib.Deserializable(
65
+ responses,
66
+ lambda res: [
67
+ (num, lib.to_dict(track)) for num, track in res if any(track.strip())
68
+ ],
69
+ )
@@ -0,0 +1,32 @@
1
+ """Karrio Locate2u client settings."""
2
+
3
+ import attr
4
+ import typing
5
+ import jstruct
6
+ import karrio.lib as lib
7
+ import karrio.core.models as models
8
+ import karrio.providers.locate2u.utils as provider_utils
9
+ import karrio.providers.locate2u.units as provider_units
10
+
11
+
12
+ @attr.s(auto_attribs=True)
13
+ class Settings(provider_utils.Settings):
14
+ """Locate2u connection settings."""
15
+
16
+ # required carrier specific properties
17
+ client_id: str = None
18
+ client_secret: str = None
19
+
20
+ id: str = None
21
+ test_mode: bool = False
22
+ carrier_id: str = "locate2u"
23
+ account_country_code: str = "AU"
24
+ services: typing.List[models.ServiceLevel] = jstruct.JList[models.ServiceLevel, False, dict(default=provider_units.DEFAULT_SERVICES)] # type: ignore
25
+ metadata: dict = {}
26
+
27
+ @property
28
+ def shipping_services(self) -> typing.List[models.ServiceLevel]:
29
+ if any(self.services or []):
30
+ return self.services
31
+
32
+ return provider_units.DEFAULT_SERVICES
@@ -0,0 +1,19 @@
1
+ import karrio.core.metadata as metadata
2
+ import karrio.mappers.locate2u as mappers
3
+ import karrio.providers.locate2u.units as units
4
+
5
+
6
+ METADATA = metadata.PluginMetadata(
7
+ status="beta",
8
+ id="locate2u",
9
+ label="Locate2u",
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
+ service_levels=units.DEFAULT_SERVICES,
19
+ )
@@ -0,0 +1,11 @@
1
+ from karrio.providers.locate2u.utils import Settings
2
+ from karrio.providers.locate2u.shipment import (
3
+ parse_shipment_cancel_response,
4
+ parse_shipment_response,
5
+ shipment_cancel_request,
6
+ shipment_request,
7
+ )
8
+ from karrio.providers.locate2u.tracking import (
9
+ parse_tracking_response,
10
+ tracking_request,
11
+ )
@@ -0,0 +1,31 @@
1
+ import typing
2
+ import urllib.error
3
+ import karrio.lib as lib
4
+ import karrio.core.models as models
5
+ import karrio.providers.locate2u.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("error") 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("code"),
23
+ message=error.get("error"),
24
+ details={**kwargs},
25
+ )
26
+ for error in errors
27
+ ]
28
+
29
+
30
+ def parse_http_response(response: urllib.error.HTTPError) -> dict:
31
+ return lib.to_json(dict(code=str(response.code), error=response.reason))
@@ -0,0 +1,9 @@
1
+
2
+ from karrio.providers.locate2u.shipment.create import (
3
+ parse_shipment_response,
4
+ shipment_request,
5
+ )
6
+ from karrio.providers.locate2u.shipment.cancel import (
7
+ parse_shipment_cancel_response,
8
+ shipment_cancel_request,
9
+ )
@@ -0,0 +1,39 @@
1
+ import typing
2
+ import karrio.lib as lib
3
+ import karrio.core.models as models
4
+ import karrio.providers.locate2u.error as error
5
+ import karrio.providers.locate2u.utils as provider_utils
6
+ import karrio.providers.locate2u.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("error") is None
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(
36
+ stopId=payload.shipment_identifier,
37
+ )
38
+
39
+ return lib.Serializable(request)
@@ -0,0 +1,116 @@
1
+ import karrio.schemas.locate2u.shipping_request as locate2u
2
+ import karrio.schemas.locate2u.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.locate2u.error as error
8
+ import karrio.providers.locate2u.utils as provider_utils
9
+ import karrio.providers.locate2u.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.ShippingResponse, data)
28
+
29
+ return models.ShipmentDetails(
30
+ carrier_id=settings.carrier_id,
31
+ carrier_name=settings.carrier_name,
32
+ tracking_number=str(shipment.stopId),
33
+ shipment_identifier=str(shipment.stopId),
34
+ label_type="PDF",
35
+ docs=models.Documents(label="No label..."),
36
+ meta=dict(
37
+ shipmentId=shipment.shipmentId,
38
+ durationMinutes=shipment.durationMinutes,
39
+ ),
40
+ )
41
+
42
+
43
+ def shipment_request(
44
+ payload: models.ShipmentRequest,
45
+ settings: provider_utils.Settings,
46
+ ) -> lib.Serializable:
47
+ recipient = lib.to_address(payload.recipient)
48
+ package = lib.to_packages(payload.parcels).single
49
+ options = lib.to_shipping_options(
50
+ payload.options,
51
+ package_options=package.options,
52
+ option_type=provider_units.ShippingOption,
53
+ )
54
+
55
+ request = locate2u.ShippingRequest(
56
+ contact=locate2u.Contact(
57
+ name=recipient.contact,
58
+ phone=recipient.phone_number,
59
+ email=recipient.email,
60
+ ),
61
+ name=recipient.company_name or recipient.person_name,
62
+ address=lib.text(
63
+ recipient.address_line,
64
+ lib.text(recipient.city, recipient.postal_code, recipient.state_code),
65
+ recipient.country_name,
66
+ separator=", ",
67
+ ),
68
+ location=(
69
+ locate2u.Location(
70
+ latitude=options.latitude.state,
71
+ longitude=options.longitude.state,
72
+ )
73
+ if any([options.latitude.state, options.longitude.state])
74
+ else None
75
+ ),
76
+ appointmentTime=options.appointment_time.state,
77
+ timeWindowStart=options.time_window_start.state,
78
+ timeWindowEnd=options.time_window_end.state,
79
+ brandId=options.brand_id.state,
80
+ durationMinutes=options.duration_minutes.state,
81
+ tripDate=lib.fdatetime(
82
+ options.shipment_date.state,
83
+ current_format="%Y-%m-%d",
84
+ output_format="%Y-%m-%dT%H:%M:%S.%fZ",
85
+ ),
86
+ customFields=None,
87
+ assignedTeamMemberId=options.assigned_team_member_id.state,
88
+ source=options.source.state,
89
+ sourceReference=payload.reference,
90
+ load=locate2u.Load(
91
+ quantity=package.items.quantity,
92
+ volume=package.volume.m3,
93
+ weight=package.weight.KG,
94
+ length=package.length.CM,
95
+ width=package.width.CM,
96
+ height=package.height.CM,
97
+ ),
98
+ customerId=options.customer_id.state,
99
+ runNumber=options.run_number.state,
100
+ teamRegionId=options.team_region_id.state,
101
+ driverInstructions=options.driver_instructions.state,
102
+ notes=options.notes.state,
103
+ lines=[
104
+ locate2u.Line(
105
+ barcode=item.hs_code or item.sku,
106
+ description=item.title or item.description,
107
+ currentLocation=item.metadata.get("currentLocation"),
108
+ serviceId=None,
109
+ productVariantId=None,
110
+ quantity=item.quantity,
111
+ )
112
+ for item in package.items
113
+ ],
114
+ )
115
+
116
+ return lib.Serializable(request, lib.to_dict)
@@ -0,0 +1,73 @@
1
+ import karrio.schemas.locate2u.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.locate2u.error as error
7
+ import karrio.providers.locate2u.utils as provider_utils
8
+ import karrio.providers.locate2u.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("error") is not None
21
+ ],
22
+ [],
23
+ )
24
+
25
+ tracking_details = [
26
+ _extract_details(response, settings)
27
+ for _, response in responses
28
+ if response.get("error") 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.TrackingResponse, 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=str(tracking.stopId),
52
+ events=[
53
+ models.TrackingEvent(
54
+ date=lib.fdate(tracking.lastModifiedDate, "%Y-%m-%dT%H:%M:%S.%fZ"),
55
+ description=tracking.status,
56
+ code=tracking.status,
57
+ time=lib.flocaltime(tracking.lastModifiedDate, "%Y-%m-%dT%H:%M:%S.%fZ"),
58
+ latitude=tracking.location.latitude,
59
+ longitude=tracking.location.longitude,
60
+ )
61
+ ],
62
+ estimated_delivery=lib.fdate(tracking.arrivalDate, "%Y-%m-%dT%H:%M:%S%z"),
63
+ delivered=(status == "delivered"),
64
+ )
65
+
66
+
67
+ def tracking_request(
68
+ payload: models.TrackingRequest,
69
+ settings: provider_utils.Settings,
70
+ ) -> lib.Serializable:
71
+ request = payload.tracking_numbers
72
+
73
+ return lib.Serializable(request)
@@ -0,0 +1,79 @@
1
+ import karrio.lib as lib
2
+ import karrio.core.units as units
3
+ import karrio.core.models as models
4
+
5
+
6
+ class PackagingType(lib.StrEnum):
7
+ """Carrier specific packaging type"""
8
+
9
+ PACKAGE = "PACKAGE"
10
+
11
+ """ Unified Packaging type mapping """
12
+ envelope = PACKAGE
13
+ pak = PACKAGE
14
+ tube = PACKAGE
15
+ pallet = PACKAGE
16
+ small_box = PACKAGE
17
+ medium_box = PACKAGE
18
+ your_packaging = PACKAGE
19
+
20
+
21
+ class ShippingService(lib.StrEnum):
22
+ """Carrier specific services"""
23
+
24
+ locate2u_local_delivery = "Locate2u Local Delivery"
25
+
26
+
27
+ class ShippingOption(lib.Enum):
28
+ """Carrier specific options"""
29
+
30
+ appointment_time = lib.OptionEnum("appointment_time")
31
+ time_window_start = lib.OptionEnum("time_window_start")
32
+ time_window_end = lib.OptionEnum("time_window_end")
33
+ brand_id = lib.OptionEnum("brand_id")
34
+ duration_minutes = lib.OptionEnum("duration_minutes", lib.to_int)
35
+ assigned_team_member_id = lib.OptionEnum("assigned_team_member_id")
36
+ source = lib.OptionEnum("source")
37
+ customer_id = lib.OptionEnum("customer_id")
38
+ run_number = lib.OptionEnum("run_number")
39
+ team_region_id = lib.OptionEnum("team_region_id")
40
+ driver_instructions = lib.OptionEnum("driver_instructions")
41
+ notes = lib.OptionEnum("notes")
42
+ latitude = lib.OptionEnum("latitude", float)
43
+ longitude = lib.OptionEnum("longitude", float)
44
+
45
+
46
+ def shipping_options_initializer(
47
+ options: dict,
48
+ package_options: units.ShippingOptions = None,
49
+ ) -> units.ShippingOptions:
50
+ """
51
+ Apply default values to the given options.
52
+ """
53
+
54
+ if package_options is not None:
55
+ options.update(package_options.content)
56
+
57
+ def items_filter(key: str) -> bool:
58
+ return key in ShippingOption # type: ignore
59
+
60
+ return units.ShippingOptions(options, ShippingOption, items_filter=items_filter)
61
+
62
+
63
+ class TrackingStatus(lib.Enum):
64
+ on_hold = ["On Hold"]
65
+ delivered = ["Complete"]
66
+ in_transit = ["Pending", "Enroute"]
67
+ delivery_failed = ["Failed", "Cancelled"]
68
+ delivery_delayed = ["Delayed"]
69
+ out_for_delivery = ["Arrived"]
70
+
71
+
72
+ DEFAULT_SERVICES = [
73
+ models.ServiceLevel(
74
+ service_name="Locate2u Local Delivery",
75
+ service_code="locate2u_local_delivery",
76
+ currency="AUD",
77
+ zones=[models.ServiceZone(label="Zone 1", rate=0.0)],
78
+ ),
79
+ ]
@@ -0,0 +1,88 @@
1
+ import base64
2
+ import jstruct
3
+ import datetime
4
+ import urllib.parse
5
+ import karrio.lib as lib
6
+ import karrio.core as core
7
+ import karrio.core.errors as errors
8
+
9
+
10
+ class Settings(core.Settings):
11
+ """Locate2u connection settings."""
12
+
13
+ client_id: str = None
14
+ client_secret: str = None
15
+
16
+ id: str = None
17
+ test_mode: bool = False
18
+ carrier_id: str = "locate2u"
19
+ account_country_code: str = "AU"
20
+ metadata: dict = {}
21
+
22
+ @property
23
+ def carrier_name(self):
24
+ return "locate2u"
25
+
26
+ @property
27
+ def server_url(self):
28
+ return "https://api.locate2u.com"
29
+
30
+ @property
31
+ def auth_server_url(self):
32
+ return "https://id.locate2u.com"
33
+
34
+ @property
35
+ def authorization(self):
36
+ pair = "%s:%s" % (self.client_id, self.client_secret)
37
+ return base64.b64encode(pair.encode("utf-8")).decode("ascii")
38
+
39
+ @property
40
+ def access_token(self):
41
+ """Retrieve the access_token using the client_id|client_secret pair
42
+ or collect it from the cache if an unexpired access_token exist.
43
+ """
44
+ cache_key = f"{self.carrier_name}|{self.client_id}|{self.client_secret}"
45
+ now = datetime.datetime.now() + datetime.timedelta(minutes=30)
46
+
47
+ auth = self.connection_cache.get(cache_key) or {}
48
+ token = auth.get("access_token")
49
+ expiry = lib.to_date(auth.get("expiry"), current_format="%Y-%m-%d %H:%M:%S")
50
+
51
+ if token is not None and expiry is not None and expiry > now:
52
+ return token
53
+
54
+ self.connection_cache.set(cache_key, lambda: login(self))
55
+ new_auth = self.connection_cache.get(cache_key)
56
+
57
+ return new_auth["access_token"]
58
+
59
+
60
+ def login(settings: Settings):
61
+ import karrio.providers.locate2u.error as error
62
+
63
+ result = lib.request(
64
+ url=f"{settings.auth_server_url}/connect/token",
65
+ method="POST",
66
+ headers={
67
+ "content-Type": "application/x-www-form-urlencoded",
68
+ "Authorization": f"Basic {settings.authorization}",
69
+ },
70
+ data=urllib.parse.urlencode(
71
+ dict(
72
+ scope="locate2u.api",
73
+ grant_type="client_credentials",
74
+ )
75
+ ),
76
+ )
77
+
78
+ response = lib.to_dict(result)
79
+ messages = error.parse_error_response(response, settings)
80
+
81
+ if any(messages):
82
+ raise errors.ParsedMessagesError(messages)
83
+
84
+ expiry = datetime.datetime.now() + datetime.timedelta(
85
+ seconds=float(response.get("expires_in", 0))
86
+ )
87
+
88
+ return {**response, "expiry": lib.fdatetime(expiry)}
File without changes
@@ -0,0 +1,69 @@
1
+ import attr
2
+ import jstruct
3
+ import typing
4
+
5
+
6
+ @attr.s(auto_attribs=True)
7
+ class Contact:
8
+ name: typing.Optional[str] = None
9
+ phone: typing.Optional[str] = None
10
+ email: typing.Optional[str] = None
11
+
12
+
13
+ @attr.s(auto_attribs=True)
14
+ class CustomFields:
15
+ custom1: typing.Optional[str] = None
16
+ custom2: typing.Optional[str] = None
17
+ custom3: typing.Optional[str] = None
18
+
19
+
20
+ @attr.s(auto_attribs=True)
21
+ class Line:
22
+ barcode: typing.Optional[int] = None
23
+ description: typing.Optional[str] = None
24
+ currentLocation: typing.Optional[str] = None
25
+ serviceId: typing.Optional[int] = None
26
+ productVariantId: typing.Optional[int] = None
27
+ quantity: typing.Optional[int] = None
28
+
29
+
30
+ @attr.s(auto_attribs=True)
31
+ class Load:
32
+ quantity: typing.Optional[int] = None
33
+ volume: typing.Optional[int] = None
34
+ weight: typing.Optional[int] = None
35
+ length: typing.Optional[int] = None
36
+ width: typing.Optional[int] = None
37
+ height: typing.Optional[int] = None
38
+
39
+
40
+ @attr.s(auto_attribs=True)
41
+ class Location:
42
+ latitude: typing.Optional[float] = None
43
+ longitude: typing.Optional[float] = None
44
+
45
+
46
+ @attr.s(auto_attribs=True)
47
+ class ShippingRequest:
48
+ contact: typing.Optional[Contact] = jstruct.JStruct[Contact]
49
+ name: typing.Optional[str] = None
50
+ address: typing.Optional[str] = None
51
+ location: typing.Optional[Location] = jstruct.JStruct[Location]
52
+ type: typing.Any = None
53
+ appointmentTime: typing.Optional[str] = None
54
+ timeWindowStart: typing.Any = None
55
+ timeWindowEnd: typing.Any = None
56
+ brandId: typing.Any = None
57
+ durationMinutes: typing.Optional[int] = None
58
+ notes: typing.Optional[str] = None
59
+ tripDate: typing.Optional[str] = None
60
+ customFields: typing.Optional[CustomFields] = jstruct.JStruct[CustomFields]
61
+ assignedTeamMemberId: typing.Optional[str] = None
62
+ source: typing.Any = None
63
+ sourceReference: typing.Any = None
64
+ load: typing.Optional[Load] = jstruct.JStruct[Load]
65
+ customerId: typing.Optional[int] = None
66
+ runNumber: typing.Optional[int] = None
67
+ teamRegionId: typing.Optional[int] = None
68
+ driverInstructions: typing.Any = None
69
+ lines: typing.Optional[typing.List[Line]] = jstruct.JList[Line]
@@ -0,0 +1,88 @@
1
+ import attr
2
+ import jstruct
3
+ import typing
4
+
5
+
6
+ @attr.s(auto_attribs=True)
7
+ class AssignedTo:
8
+ id: typing.Optional[str] = None
9
+ name: typing.Optional[str] = None
10
+
11
+
12
+ @attr.s(auto_attribs=True)
13
+ class Contact:
14
+ name: typing.Optional[str] = None
15
+ phone: typing.Optional[str] = None
16
+ email: typing.Optional[str] = None
17
+
18
+
19
+ @attr.s(auto_attribs=True)
20
+ class CustomFields:
21
+ custom1: typing.Optional[str] = None
22
+ custom2: typing.Optional[str] = None
23
+ custom3: typing.Optional[str] = None
24
+
25
+
26
+ @attr.s(auto_attribs=True)
27
+ class Line:
28
+ lineId: typing.Optional[int] = None
29
+ itemId: typing.Optional[int] = None
30
+ serviceId: typing.Any = None
31
+ productVariantId: typing.Any = None
32
+ barcode: typing.Optional[int] = None
33
+ description: typing.Optional[str] = None
34
+ quantity: typing.Optional[int] = None
35
+ status: typing.Optional[str] = None
36
+ itemStatus: typing.Optional[str] = None
37
+ unitPriceExTax: typing.Optional[int] = None
38
+ priceCurrency: typing.Optional[str] = None
39
+
40
+
41
+ @attr.s(auto_attribs=True)
42
+ class Load:
43
+ quantity: typing.Optional[int] = None
44
+ volume: typing.Optional[int] = None
45
+ weight: typing.Optional[int] = None
46
+ length: typing.Optional[int] = None
47
+ width: typing.Optional[int] = None
48
+ height: typing.Optional[int] = None
49
+
50
+
51
+ @attr.s(auto_attribs=True)
52
+ class Location:
53
+ latitude: typing.Optional[float] = None
54
+ longitude: typing.Optional[float] = None
55
+
56
+
57
+ @attr.s(auto_attribs=True)
58
+ class ShippingResponse:
59
+ assignedTo: typing.Optional[AssignedTo] = jstruct.JStruct[AssignedTo]
60
+ stopId: typing.Optional[int] = None
61
+ status: typing.Optional[str] = None
62
+ brandId: typing.Any = None
63
+ contact: typing.Optional[Contact] = jstruct.JStruct[Contact]
64
+ name: typing.Optional[str] = None
65
+ address: typing.Optional[str] = None
66
+ location: typing.Optional[Location] = jstruct.JStruct[Location]
67
+ tripDate: typing.Optional[str] = None
68
+ appointmentTime: typing.Optional[str] = None
69
+ timeWindowStart: typing.Any = None
70
+ timeWindowEnd: typing.Any = None
71
+ durationMinutes: typing.Optional[int] = None
72
+ notes: typing.Optional[str] = None
73
+ lastModifiedDate: typing.Optional[str] = None
74
+ customFields: typing.Optional[CustomFields] = jstruct.JStruct[CustomFields]
75
+ type: typing.Any = None
76
+ shipmentId: typing.Optional[int] = None
77
+ load: typing.Optional[Load] = jstruct.JStruct[Load]
78
+ source: typing.Any = None
79
+ sourceReference: typing.Any = None
80
+ customerId: typing.Optional[int] = None
81
+ runNumber: typing.Optional[int] = None
82
+ teamRegionId: typing.Optional[int] = None
83
+ teamMemberInvoiceId: typing.Optional[int] = None
84
+ customerInvoiceId: typing.Optional[int] = None
85
+ arrivalDate: typing.Optional[str] = None
86
+ lines: typing.Optional[typing.List[Line]] = jstruct.JList[Line]
87
+ driverInstructions: typing.Any = None
88
+ oneTimePin: typing.Optional[str] = None
@@ -0,0 +1,88 @@
1
+ import attr
2
+ import jstruct
3
+ import typing
4
+
5
+
6
+ @attr.s(auto_attribs=True)
7
+ class AssignedTo:
8
+ id: typing.Optional[str] = None
9
+ name: typing.Optional[str] = None
10
+
11
+
12
+ @attr.s(auto_attribs=True)
13
+ class Contact:
14
+ name: typing.Optional[str] = None
15
+ phone: typing.Optional[str] = None
16
+ email: typing.Optional[str] = None
17
+
18
+
19
+ @attr.s(auto_attribs=True)
20
+ class CustomFields:
21
+ custom1: typing.Optional[str] = None
22
+ custom2: typing.Optional[str] = None
23
+ custom3: typing.Optional[str] = None
24
+
25
+
26
+ @attr.s(auto_attribs=True)
27
+ class Line:
28
+ lineId: typing.Optional[int] = None
29
+ itemId: typing.Optional[int] = None
30
+ serviceId: typing.Any = None
31
+ productVariantId: typing.Any = None
32
+ barcode: typing.Optional[int] = None
33
+ description: typing.Optional[str] = None
34
+ quantity: typing.Optional[int] = None
35
+ status: typing.Optional[str] = None
36
+ itemStatus: typing.Optional[str] = None
37
+ unitPriceExTax: typing.Optional[int] = None
38
+ priceCurrency: typing.Optional[str] = None
39
+
40
+
41
+ @attr.s(auto_attribs=True)
42
+ class Load:
43
+ quantity: typing.Optional[int] = None
44
+ volume: typing.Optional[int] = None
45
+ weight: typing.Optional[int] = None
46
+ length: typing.Optional[int] = None
47
+ width: typing.Optional[int] = None
48
+ height: typing.Optional[int] = None
49
+
50
+
51
+ @attr.s(auto_attribs=True)
52
+ class Location:
53
+ latitude: typing.Optional[float] = None
54
+ longitude: typing.Optional[float] = None
55
+
56
+
57
+ @attr.s(auto_attribs=True)
58
+ class TrackingResponse:
59
+ assignedTo: typing.Optional[AssignedTo] = jstruct.JStruct[AssignedTo]
60
+ stopId: typing.Optional[int] = None
61
+ status: typing.Optional[str] = None
62
+ brandId: typing.Any = None
63
+ contact: typing.Optional[Contact] = jstruct.JStruct[Contact]
64
+ name: typing.Optional[str] = None
65
+ address: typing.Optional[str] = None
66
+ location: typing.Optional[Location] = jstruct.JStruct[Location]
67
+ tripDate: typing.Optional[str] = None
68
+ appointmentTime: typing.Optional[str] = None
69
+ timeWindowStart: typing.Any = None
70
+ timeWindowEnd: typing.Any = None
71
+ durationMinutes: typing.Optional[int] = None
72
+ notes: typing.Optional[str] = None
73
+ lastModifiedDate: typing.Optional[str] = None
74
+ customFields: typing.Optional[CustomFields] = jstruct.JStruct[CustomFields]
75
+ type: typing.Any = None
76
+ shipmentId: typing.Optional[int] = None
77
+ load: typing.Optional[Load] = jstruct.JStruct[Load]
78
+ source: typing.Any = None
79
+ sourceReference: typing.Any = None
80
+ customerId: typing.Optional[int] = None
81
+ runNumber: typing.Optional[int] = None
82
+ teamRegionId: typing.Optional[int] = None
83
+ teamMemberInvoiceId: typing.Optional[int] = None
84
+ customerInvoiceId: typing.Optional[int] = None
85
+ arrivalDate: typing.Optional[str] = None
86
+ lines: typing.Optional[typing.List[Line]] = jstruct.JList[Line]
87
+ driverInstructions: typing.Any = None
88
+ oneTimePin: typing.Optional[str] = None
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.4
2
+ Name: karrio_locate2u
3
+ Version: 2025.5rc1
4
+ Summary: Karrio - Locate2u 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.locate2u
17
+
18
+ This package is a Locate2u 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.locate2u
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```python
33
+ import karrio.sdk as karrio
34
+ from karrio.mappers.locate2u.settings import Settings
35
+
36
+
37
+ # Initialize a carrier gateway
38
+ locate2u = karrio.gateway["locate2u"].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,22 @@
1
+ karrio/mappers/locate2u/__init__.py,sha256=GIqA5pU1RiPY4Pmo5rSFxMq-ge57gt5Y70MYMOcRt0Q,152
2
+ karrio/mappers/locate2u/mapper.py,sha256=1fG0YzxaCbXiMvykeoyzkGEPiducxEF3SeFQH5Mj7EY,2042
3
+ karrio/mappers/locate2u/proxy.py,sha256=TkCB2hTN4oa8a3EP3CIWL9g6Nv6hz_Ql0qXCf6vg6rw,2663
4
+ karrio/mappers/locate2u/settings.py,sha256=YCPAfEv9DfyNcCET23qdQJ6KKxgoMxTLYcm-woN35B0,964
5
+ karrio/plugins/locate2u/__init__.py,sha256=PWzbROuRfVuUeSiA_GFY_CsZKuPh8w7j5ViTVOPdc7k,480
6
+ karrio/providers/locate2u/__init__.py,sha256=DD0GxkUX-CBVUApoRkl0Ue2j8Nx3mAottAxa-jej8bE,322
7
+ karrio/providers/locate2u/error.py,sha256=4daLpSeCZKVfZhad7MSNV8JR3u2GIH4m5D-fEIaPY_I,930
8
+ karrio/providers/locate2u/tracking.py,sha256=atAmnoeJIR9QJg3LKcyf_Quxnr9sI8q7X9TprcTXIdo,2394
9
+ karrio/providers/locate2u/units.py,sha256=GdiA_95_8HfSW-r6UoMGPgW9_WmD4vo2ESAYvizITBw,2292
10
+ karrio/providers/locate2u/utils.py,sha256=81Cl5K0wEq5CD9NbsA_uV7jMsVzWODBjrtIVYpOJGkA,2513
11
+ karrio/providers/locate2u/shipment/__init__.py,sha256=8UpJ7BqZawO7zi4ziPDjeJw3QJZBYtp_u7nhRVkdAYE,233
12
+ karrio/providers/locate2u/shipment/cancel.py,sha256=f0ZE78hSGG8cin0s4tmD5piAhrYYSmXwOwVMJszvPYQ,1128
13
+ karrio/providers/locate2u/shipment/create.py,sha256=tel33d5Ue0sdN_4P5Kg8EgK-8X0IBwSex_qJoOPdNjk,4110
14
+ karrio/schemas/locate2u/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ karrio/schemas/locate2u/shipping_request.py,sha256=WAQ5SZFnqI8rkAxZR3fIdEegk_sX2JMZvyJCs0Dyleg,2180
16
+ karrio/schemas/locate2u/shipping_response.py,sha256=5WB5Zc36rMUT72BfDva4U73zG2gCc_v5f3ut01IE9iU,2879
17
+ karrio/schemas/locate2u/tracking_response.py,sha256=WcZQWV952GnSijBw1XuK5xX_Q-cXTDo5kx2oRuFVlx8,2879
18
+ karrio_locate2u-2025.5rc1.dist-info/METADATA,sha256=Awgk0xKsLkXn6dKYXydNEMR6aLjeP9FFdCCEe9iuejI,1000
19
+ karrio_locate2u-2025.5rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ karrio_locate2u-2025.5rc1.dist-info/entry_points.txt,sha256=sxqKOEWh7elHsX05SNXUrJaJoqVFCIB0Wit6KsazyiA,61
21
+ karrio_locate2u-2025.5rc1.dist-info/top_level.txt,sha256=FZCY8Nwft8oEGHdl--xku8P3TrnOxu5dETEU_fWpRSM,20
22
+ karrio_locate2u-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
+ locate2u = karrio.plugins.locate2u:METADATA
@@ -0,0 +1,3 @@
1
+ dist
2
+ karrio
3
+ schemas