karrio-eshipper 2025.5.6__py3-none-any.whl → 2025.5.7__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.
@@ -1,14 +1,74 @@
1
1
  """Karrio eShipper client proxy."""
2
2
 
3
+ import datetime
3
4
  import karrio.lib as lib
4
5
  import karrio.api.proxy as proxy
6
+ import karrio.core.errors as errors
7
+ import karrio.core.models as models
8
+ import karrio.providers.eshipper.error as provider_error
5
9
  import karrio.mappers.eshipper.settings as provider_settings
6
10
 
7
11
 
8
12
  class Proxy(proxy.Proxy):
9
13
  settings: provider_settings.Settings
10
14
 
15
+ def authenticate(self, _=None) -> lib.Deserializable[str]:
16
+ """Retrieve the token using the principal|credential pair
17
+ or collect it from the cache if an unexpired token exist.
18
+ """
19
+ cache_key = f"{self.settings.carrier_name}|{self.settings.principal}|{self.settings.credential}"
20
+
21
+ def get_token():
22
+ result = lib.request(
23
+ url=f"{self.settings.server_url}/api/v2/authenticate",
24
+ trace=self.settings.trace_as("json"),
25
+ method="POST",
26
+ headers={"content-Type": "application/json"},
27
+ data=lib.to_json(
28
+ {
29
+ "principal": self.settings.principal,
30
+ "credential": self.settings.credential,
31
+ }
32
+ ),
33
+ max_retries=2,
34
+ )
35
+
36
+ response = lib.to_dict(result)
37
+ messages = provider_error.parse_error_response(response, self.settings)
38
+
39
+ if any(messages):
40
+ raise errors.ParsedMessagesError(messages=messages)
41
+
42
+ # Validate that token is present in the response
43
+ if "token" not in response:
44
+ raise errors.ParsedMessagesError(
45
+ messages=[
46
+ models.Message(
47
+ carrier_name=self.settings.carrier_name,
48
+ carrier_id=self.settings.carrier_id,
49
+ message="Authentication failed: No token received",
50
+ code="AUTH_ERROR",
51
+ )
52
+ ]
53
+ )
54
+
55
+ expiry = datetime.datetime.now() + datetime.timedelta(
56
+ seconds=float(response.get("expires_in", 0))
57
+ )
58
+
59
+ return {**response, "expiry": lib.fdatetime(expiry)}
60
+
61
+ token = self.settings.connection_cache.thread_safe(
62
+ refresh_func=get_token,
63
+ cache_key=cache_key,
64
+ buffer_minutes=30,
65
+ token_field="token",
66
+ )
67
+
68
+ return lib.Deserializable(token.get_state())
69
+
11
70
  def get_rates(self, request: lib.Serializable) -> lib.Deserializable[str]:
71
+ access_token = self.authenticate().deserialize()
12
72
  response = lib.request(
13
73
  url=f"{self.settings.server_url}/api/v2/quote",
14
74
  data=lib.to_json(request.serialize()),
@@ -16,13 +76,14 @@ class Proxy(proxy.Proxy):
16
76
  method="POST",
17
77
  headers={
18
78
  "content-Type": "application/json",
19
- "Authorization": f"Bearer {self.settings.access_token}",
79
+ "Authorization": f"Bearer {access_token}",
20
80
  },
21
81
  )
22
82
 
23
83
  return lib.Deserializable(response, lib.to_dict)
24
84
 
25
85
  def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
86
+ access_token = self.authenticate().deserialize()
26
87
  response = lib.request(
27
88
  url=f"{self.settings.server_url}/api/v2/ship",
28
89
  data=lib.to_json(request.serialize()),
@@ -30,13 +91,14 @@ class Proxy(proxy.Proxy):
30
91
  method="PUT",
31
92
  headers={
32
93
  "content-Type": "application/json",
33
- "Authorization": f"Bearer {self.settings.access_token}",
94
+ "Authorization": f"Bearer {access_token}",
34
95
  },
35
96
  )
36
97
 
37
98
  return lib.Deserializable(response, lib.to_dict)
38
99
 
39
100
  def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
101
+ access_token = self.authenticate().deserialize()
40
102
  response = lib.request(
41
103
  url=f"{self.settings.server_url}/api/v2/ship/cancel",
42
104
  data=lib.to_json(request.serialize()),
@@ -44,13 +106,14 @@ class Proxy(proxy.Proxy):
44
106
  method="DELETE",
45
107
  headers={
46
108
  "content-Type": "application/json",
47
- "Authorization": f"Bearer {self.settings.access_token}",
109
+ "Authorization": f"Bearer {access_token}",
48
110
  },
49
111
  )
50
112
 
51
113
  return lib.Deserializable(response, lib.to_dict)
52
114
 
53
115
  def get_tracking(self, request: lib.Serializable) -> lib.Deserializable[str]:
116
+ access_token = self.authenticate().deserialize()
54
117
  query = lib.to_query_string(request.serialize())
55
118
  response = lib.request(
56
119
  url=f"{self.settings.server_url}/api/v2/track/events?{query}",
@@ -58,7 +121,7 @@ class Proxy(proxy.Proxy):
58
121
  method="GET",
59
122
  headers={
60
123
  "content-Type": "application/json",
61
- "Authorization": f"Bearer {self.settings.access_token}",
124
+ "Authorization": f"Bearer {access_token}",
62
125
  },
63
126
  )
64
127
 
@@ -51,6 +51,23 @@ def _extract_details(
51
51
  description=event.description,
52
52
  date=lib.fdate(event.originalEvent.eventDate, "%Y-%m-%d %H:%M:%S"),
53
53
  time=lib.flocaltime(event.originalEvent.eventDate, "%Y-%m-%d %H:%M:%S"),
54
+ timestamp=lib.fiso_timestamp(event.originalEvent.eventDate, current_format="%Y-%m-%d %H:%M:%S"),
55
+ status=next(
56
+ (
57
+ s.name
58
+ for s in list(provider_units.TrackingStatus)
59
+ if event.originalEvent.name in s.value
60
+ ),
61
+ None,
62
+ ),
63
+ reason=next(
64
+ (
65
+ r.name
66
+ for r in list(provider_units.TrackingIncidentReason)
67
+ if event.originalEvent.name in r.value
68
+ ),
69
+ None,
70
+ ),
54
71
  )
55
72
  for event in details.event
56
73
  ],
@@ -102,6 +102,14 @@ class TrackingStatus(lib.Enum):
102
102
  ready_for_pickup = ["ready_for_pickup"]
103
103
 
104
104
 
105
+ class TrackingIncidentReason(lib.Enum):
106
+ """Maps eShipper exception codes to normalized TrackingIncidentReason."""
107
+ carrier_damaged_parcel = []
108
+ consignee_refused = []
109
+ consignee_not_home = []
110
+ unknown = []
111
+
112
+
105
113
  def to_carrier_code(carrierDTO: typing.Dict[str, str]) -> str:
106
114
  _code = lib.to_snake_case((carrierDTO or {}).get("name") or "eshipper")
107
115
 
@@ -1,8 +1,6 @@
1
- import datetime
2
- import karrio.lib as lib
1
+ """eShipper connection settings."""
2
+
3
3
  import karrio.core as core
4
- import karrio.core.errors as errors
5
- import karrio.core.models as models
6
4
 
7
5
 
8
6
  class Settings(core.Settings):
@@ -20,65 +18,3 @@ class Settings(core.Settings):
20
18
  return (
21
19
  "https://uu2.eshipper.com" if self.test_mode else "https://ww2.eshipper.com"
22
20
  )
23
-
24
- @property
25
- def access_token(self):
26
- """Retrieve the "token" using the principal|credential pair
27
- or collect it from the cache if an unexpired "token" exist.
28
- """
29
- cache_key = f"{self.carrier_name}|{self.principal}|{self.credential}"
30
-
31
- return self.connection_cache.thread_safe(
32
- refresh_func=lambda: login(self),
33
- cache_key=cache_key,
34
- buffer_minutes=30,
35
- token_field="token",
36
- ).get_state()
37
-
38
-
39
- def login(settings: Settings):
40
- """Sign in response
41
- {
42
- "token": "string",
43
- "expires_in": "string",
44
- "token_type": "string",
45
- "refresh_token": "string",
46
- "refresh_expires_in": "string"
47
- }
48
- """
49
- import karrio.providers.eshipper.error as error
50
-
51
- result = lib.request(
52
- url=f"{settings.server_url}/api/v2/authenticate",
53
- data=lib.to_json(
54
- {
55
- "principal": settings.principal,
56
- "credential": settings.credential,
57
- }
58
- ),
59
- method="POST",
60
- headers={"content-Type": "application/json"},
61
- )
62
- response = lib.to_dict(result)
63
- messages = error.parse_error_response(response, settings)
64
-
65
- if any(messages):
66
- raise errors.ParsedMessagesError(messages=messages)
67
-
68
- # Validate that token is present in the response
69
- if "token" not in response:
70
- raise errors.ParsedMessagesError(
71
- messages=[
72
- models.Message(
73
- carrier_name=settings.carrier_name,
74
- carrier_id=settings.carrier_id,
75
- message="Authentication failed: No token received",
76
- code="AUTH_ERROR",
77
- )
78
- ]
79
- )
80
-
81
- expiry = datetime.datetime.now() + datetime.timedelta(
82
- seconds=float(response.get("expires_in", 0))
83
- )
84
- return {**response, "expiry": lib.fdatetime(expiry)}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karrio_eshipper
3
- Version: 2025.5.6
3
+ Version: 2025.5.7
4
4
  Summary: Karrio - eShipper Shipping Extension
5
5
  Author-email: karrio <hello@karrio.io>
6
6
  License-Expression: LGPL-3.0
@@ -1,15 +1,15 @@
1
1
  karrio/mappers/eshipper/__init__.py,sha256=klSnHWetpSpOBsj7OgncePwLbSK1etgRQN_PZSK2pUo,152
2
2
  karrio/mappers/eshipper/mapper.py,sha256=oFs6tqFI9vbm13sh8hHoZVxA2VrnqdaixOxeB9nnJeY,1969
3
- karrio/mappers/eshipper/proxy.py,sha256=ZLOXHxgGPSVkitCqHJRFeRWpqjcFY01xVEuL2yz4dd8,2350
3
+ karrio/mappers/eshipper/proxy.py,sha256=2U683Jl_Prr1Rov8fbtuUyhJHWGA-cxkJPHLVMvYIk8,4770
4
4
  karrio/mappers/eshipper/settings.py,sha256=ZN7DTMBMTC_JtOgUFQ2UNCyU5j-VUfv2us_F90fL1jw,491
5
5
  karrio/plugins/eshipper/__init__.py,sha256=v5cNriCwL5R2QR5x6Iki23CcBGPmGmH8zEL5j02u9Ko,448
6
6
  karrio/providers/eshipper/__init__.py,sha256=Y9vKPYScbmFvM17L-x52m_yLLiG6WsX9j6o4NIERBMU,400
7
7
  karrio/providers/eshipper/error.py,sha256=uroZ2dW7OHEIrhi3HKnsd8EntmXaLpZisSDyJ2HLKOY,2038
8
8
  karrio/providers/eshipper/metadata.json,sha256=KBae69ntmCIkZ0dKrAX0vjkm1CyE9UwvIstt7mLrq4M,369526
9
9
  karrio/providers/eshipper/rate.py,sha256=r0OTKeXFXmaWdi7XcNDNQ0wL5R1idQAnNhMtXTRfpPM,7184
10
- karrio/providers/eshipper/tracking.py,sha256=46DVFj8iMLWSsi0f5irTzFpH0uArZtya4eyYh5LkdFQ,2452
11
- karrio/providers/eshipper/units.py,sha256=CTi0NbWEpVc-GwPEKrtGVR7VEEV9Ij0odaQTUacwgyc,9658
12
- karrio/providers/eshipper/utils.py,sha256=0Ok4BNFe4KoNNtCNjp6FLUfR8iwfuaMSh_jZRsPK4KE,2381
10
+ karrio/providers/eshipper/tracking.py,sha256=wIYItHY_aQ1Cnth6c9Wqdjfr4fn9fALTBiWSB-FhjLs,3137
11
+ karrio/providers/eshipper/units.py,sha256=l9dkHvmxYI7GMmm_l5PoyRZflrKSIG4uMX1f29iig0E,9882
12
+ karrio/providers/eshipper/utils.py,sha256=LOHTEHMgoLpYIPMuCRxcyGwVp3UsMC7DZ3h36YtXwvQ,403
13
13
  karrio/providers/eshipper/shipment/__init__.py,sha256=oPCX3bykNDVeczan1tbmSpuOtFYTtd6h8J9otYVDQqQ,233
14
14
  karrio/providers/eshipper/shipment/cancel.py,sha256=vdxMmK7AmI-xOn62NlaMzCCKQ8CBql8QBVPft0VQWn0,1317
15
15
  karrio/providers/eshipper/shipment/create.py,sha256=jL4RWdtYOmpofcGVYn-7l2rstMvXK-DKoIAe_TjCBkY,11632
@@ -26,8 +26,8 @@ karrio/schemas/eshipper/shipping_request.py,sha256=OrqSutwyzTj-iau3LDPJUEGBRzzV9
26
26
  karrio/schemas/eshipper/shipping_response.py,sha256=RdMXh9goyTrH2qdU_5KMDIAWAEDzMchy_e5f45k-zxs,3892
27
27
  karrio/schemas/eshipper/tracking_request.py,sha256=EAE7PJFp2rTYCXMQJ6Kg-hMs3vL-GSTFjhHvzSmMzPg,252
28
28
  karrio/schemas/eshipper/tracking_response.py,sha256=2m9doy3y5f6zdA-gbNfsw8rp0oqs3eYrgw8Por_1fjE,2219
29
- karrio_eshipper-2025.5.6.dist-info/METADATA,sha256=rIE9q93gzGfM1WEZ9bNdXN8nr0KA8WuzC45wSqwJjYM,997
30
- karrio_eshipper-2025.5.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
- karrio_eshipper-2025.5.6.dist-info/entry_points.txt,sha256=u9RQVmPE2uC3-aL85DnlIHdsHAWYkOnwTAfJvi2--DY,61
32
- karrio_eshipper-2025.5.6.dist-info/top_level.txt,sha256=FZCY8Nwft8oEGHdl--xku8P3TrnOxu5dETEU_fWpRSM,20
33
- karrio_eshipper-2025.5.6.dist-info/RECORD,,
29
+ karrio_eshipper-2025.5.7.dist-info/METADATA,sha256=iJQMWc8Jr03jA4IcQEI-MyXfPJcaaxivQlw1gkxgH-c,997
30
+ karrio_eshipper-2025.5.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
+ karrio_eshipper-2025.5.7.dist-info/entry_points.txt,sha256=u9RQVmPE2uC3-aL85DnlIHdsHAWYkOnwTAfJvi2--DY,61
32
+ karrio_eshipper-2025.5.7.dist-info/top_level.txt,sha256=FZCY8Nwft8oEGHdl--xku8P3TrnOxu5dETEU_fWpRSM,20
33
+ karrio_eshipper-2025.5.7.dist-info/RECORD,,