karrio-easyship 2025.5.5__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.
@@ -8,14 +8,23 @@ import karrio.mappers.easyship.settings as provider_settings
8
8
  class Proxy(proxy.Proxy):
9
9
  settings: provider_settings.Settings
10
10
 
11
+ def authenticate(self, _=None) -> lib.Deserializable[str]:
12
+ """Return the access_token for API authentication.
13
+
14
+ Easyship uses a direct access token (no OAuth flow required).
15
+ This method provides a consistent interface with other carriers.
16
+ """
17
+ return lib.Deserializable(self.settings.access_token)
18
+
11
19
  def get_rates(self, request: lib.Serializable) -> lib.Deserializable[str]:
20
+ access_token = self.authenticate().deserialize()
12
21
  response = lib.request(
13
22
  url=f"{self.settings.server_url}/2023-01/rates",
14
23
  data=lib.to_json(request.serialize()),
15
24
  trace=self.trace_as("json"),
16
25
  method="POST",
17
26
  headers={
18
- "Authorization": f"Bearer {self.settings.access_token}",
27
+ "Authorization": f"Bearer {access_token}",
19
28
  "Content-Type": "application/json",
20
29
  "user-agent": "app/1.0",
21
30
  },
@@ -24,29 +33,32 @@ class Proxy(proxy.Proxy):
24
33
  return lib.Deserializable(response, lib.to_dict)
25
34
 
26
35
  def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
27
- # create shipment
36
+ access_token = self.authenticate().deserialize()
37
+ # create shipment with retry for transient failures (e.g., Cloudflare 522 timeouts)
28
38
  response = lib.request(
29
39
  url=f"{self.settings.server_url}/2023-01/shipments",
30
40
  data=lib.to_json(request.serialize()),
31
41
  trace=self.trace_as("json"),
32
42
  method="POST",
33
43
  headers={
34
- "Authorization": f"Bearer {self.settings.access_token}",
44
+ "Authorization": f"Bearer {access_token}",
35
45
  "Content-Type": "application/json",
36
46
  "user-agent": "app/1.0",
37
47
  },
48
+ max_retries=2,
38
49
  )
39
50
 
40
- return lib.Deserializable(response, lib.to_dict, request.ctx)
51
+ return lib.Deserializable(response, lib.to_dict_safe, request.ctx)
41
52
 
42
53
  def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
54
+ access_token = self.authenticate().deserialize()
43
55
  easyship_shipment_id = request.serialize().get("easyship_shipment_id")
44
56
  response = lib.request(
45
57
  url=f"{self.settings.server_url}/2023-01/shipments/{easyship_shipment_id}/cancel",
46
58
  trace=self.trace_as("json"),
47
59
  method="POST",
48
60
  headers={
49
- "Authorization": f"Bearer {self.settings.access_token}",
61
+ "Authorization": f"Bearer {access_token}",
50
62
  "Content-Type": "application/json",
51
63
  "user-agent": "app/1.0",
52
64
  },
@@ -55,6 +67,7 @@ class Proxy(proxy.Proxy):
55
67
  return lib.Deserializable(response, lib.to_dict)
56
68
 
57
69
  def get_tracking(self, request: lib.Serializable) -> lib.Deserializable[str]:
70
+ access_token = self.authenticate().deserialize()
58
71
  responses = lib.run_asynchronously(
59
72
  lambda data: (
60
73
  data["shipment_id"],
@@ -63,7 +76,7 @@ class Proxy(proxy.Proxy):
63
76
  trace=self.trace_as("json"),
64
77
  method="GET",
65
78
  headers={
66
- "Authorization": f"Bearer {self.settings.access_token}",
79
+ "Authorization": f"Bearer {access_token}",
67
80
  "Content-Type": "application/json",
68
81
  "user-agent": "app/1.0",
69
82
  },
@@ -78,13 +91,14 @@ class Proxy(proxy.Proxy):
78
91
  )
79
92
 
80
93
  def schedule_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
94
+ access_token = self.authenticate().deserialize()
81
95
  response = lib.request(
82
96
  url=f"{self.settings.server_url}/2023-01/pickups",
83
97
  data=lib.to_json(request.serialize()),
84
98
  trace=self.trace_as("json"),
85
99
  method="POST",
86
100
  headers={
87
- "Authorization": f"Bearer {self.settings.access_token}",
101
+ "Authorization": f"Bearer {access_token}",
88
102
  "Content-Type": "application/json",
89
103
  "user-agent": "app/1.0",
90
104
  },
@@ -101,13 +115,14 @@ class Proxy(proxy.Proxy):
101
115
  return lib.Deserializable(response, lib.to_dict, request.ctx)
102
116
 
103
117
  def cancel_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
118
+ access_token = self.authenticate().deserialize()
104
119
  easyship_pickup_id = request.serialize().get("easyship_pickup_id")
105
120
  response = lib.request(
106
121
  url=f"{self.settings.server_url}/2023-01/pickups/{easyship_pickup_id}/cancel",
107
122
  trace=self.trace_as("json"),
108
123
  method="POST",
109
124
  headers={
110
- "Authorization": f"Bearer {self.settings.access_token}",
125
+ "Authorization": f"Bearer {access_token}",
111
126
  "Content-Type": "application/json",
112
127
  "user-agent": "app/1.0",
113
128
  },
@@ -116,6 +131,7 @@ class Proxy(proxy.Proxy):
116
131
  return lib.Deserializable(response, lib.to_dict)
117
132
 
118
133
  def create_manifest(self, request: lib.Serializable) -> lib.Deserializable[str]:
134
+ access_token = self.authenticate().deserialize()
119
135
  # create manifest
120
136
  response = lib.to_dict(
121
137
  lib.request(
@@ -124,7 +140,7 @@ class Proxy(proxy.Proxy):
124
140
  trace=self.trace_as("json"),
125
141
  method="POST",
126
142
  headers={
127
- "Authorization": f"Bearer {self.settings.access_token}",
143
+ "Authorization": f"Bearer {access_token}",
128
144
  "Content-Type": "application/json",
129
145
  "user-agent": "app/1.0",
130
146
  },
@@ -143,7 +159,7 @@ class Proxy(proxy.Proxy):
143
159
  url=manifest_url,
144
160
  method="GET",
145
161
  headers={
146
- "Authorization": f"Bearer {self.settings.access_token}",
162
+ "Authorization": f"Bearer {access_token}",
147
163
  "origin": "http://localhost:5002",
148
164
  "user-agent": "app/1.0",
149
165
  },
@@ -62,6 +62,23 @@ def _extract_details(
62
62
  date=lib.ftime(details.updated_at, "%Y-%m-%dT%H:%M:%SZ"),
63
63
  time=lib.ftime(details.updated_at, "%Y-%m-%dT%H:%M:%SZ"),
64
64
  description="",
65
+ timestamp=lib.fiso_timestamp(details.updated_at, current_format="%Y-%m-%dT%H:%M:%SZ"),
66
+ status=next(
67
+ (
68
+ s.name
69
+ for s in list(provider_units.TrackingStatus)
70
+ if getattr(master, "tracking_state", None) in s.value
71
+ ),
72
+ None,
73
+ ),
74
+ reason=next(
75
+ (
76
+ r.name
77
+ for r in list(provider_units.TrackingIncidentReason)
78
+ if str(master.leg_number) in r.value
79
+ ),
80
+ None,
81
+ ),
65
82
  )
66
83
  ],
67
84
  )
@@ -106,6 +106,14 @@ class TrackingStatus(lib.Enum):
106
106
  ready_for_pickup = ["ready_for_pickup"]
107
107
 
108
108
 
109
+ class TrackingIncidentReason(lib.Enum):
110
+ """Maps Easyship exception codes to normalized TrackingIncidentReason."""
111
+ carrier_damaged_parcel = []
112
+ consignee_refused = []
113
+ consignee_not_home = []
114
+ unknown = []
115
+
116
+
109
117
  def to_service_code(service: typing.Dict[str, str]) -> str:
110
118
  return lib.to_slug(
111
119
  f'easyship_{to_carrier_code(service)}_{lib.to_snake_case(service["service_name"])}'
@@ -1,14 +1,10 @@
1
- import base64
2
- import datetime
3
1
  import karrio.lib as lib
4
2
  import karrio.core as core
5
- import karrio.core.errors as errors
6
3
 
7
4
 
8
5
  class Settings(core.Settings):
9
6
  """Easyship connection settings."""
10
7
 
11
- # Add carrier specific api connection properties here
12
8
  access_token: str
13
9
 
14
10
  @property
@@ -19,17 +15,6 @@ class Settings(core.Settings):
19
15
  def server_url(self):
20
16
  return "https://api.easyship.com"
21
17
 
22
- # """uncomment the following code block to expose a carrier tracking url."""
23
- # @property
24
- # def tracking_url(self):
25
- # return "https://www.carrier.com/tracking?tracking-id={}"
26
-
27
- # """uncomment the following code block to implement the Basic auth."""
28
- # @property
29
- # def authorization(self):
30
- # pair = "%s:%s" % (self.username, self.password)
31
- # return base64.b64encode(pair.encode("utf-8")).decode("ascii")
32
-
33
18
  @property
34
19
  def connection_config(self) -> lib.units.Options:
35
20
  return lib.to_connection_config(
@@ -38,56 +23,6 @@ class Settings(core.Settings):
38
23
  )
39
24
 
40
25
 
41
- # """uncomment the following code block to implement the oauth login."""
42
- # @property
43
- # def access_token(self):
44
- # """Retrieve the access_token using the client_id|client_secret pair
45
- # or collect it from the cache if an unexpired access_token exist.
46
- # """
47
- # cache_key = f"{self.carrier_name}|{self.client_id}|{self.client_secret}"
48
- # now = datetime.datetime.now() + datetime.timedelta(minutes=30)
49
-
50
- # auth = self.connection_cache.get(cache_key) or {}
51
- # token = auth.get("access_token")
52
- # expiry = lib.to_date(auth.get("expiry"), current_format="%Y-%m-%d %H:%M:%S")
53
-
54
- # if token is not None and expiry is not None and expiry > now:
55
- # return token
56
-
57
- # self.connection_cache.set(cache_key, lambda: login(self))
58
- # new_auth = self.connection_cache.get(cache_key)
59
-
60
- # return new_auth["access_token"]
61
-
62
- # """uncomment the following code block to implement the oauth login."""
63
- # def login(settings: Settings):
64
- # import karrio.providers.easyship.error as error
65
-
66
- # result = lib.request(
67
- # url=f"{settings.server_url}/oauth/token",
68
- # method="POST",
69
- # headers={"content-Type": "application/x-www-form-urlencoded"},
70
- # data=lib.to_query_string(
71
- # dict(
72
- # grant_type="client_credentials",
73
- # client_id=settings.client_id,
74
- # client_secret=settings.client_secret,
75
- # )
76
- # ),
77
- # )
78
-
79
- # response = lib.to_dict(result)
80
- # messages = error.parse_error_response(response, settings)
81
-
82
- # if any(messages):
83
- # raise errors.ParsedMessagesError(messages)
84
-
85
- # expiry = datetime.datetime.now() + datetime.timedelta(
86
- # seconds=float(response.get("expires_in", 0))
87
- # )
88
- # return {**response, "expiry": lib.fdatetime(expiry)}
89
-
90
-
91
26
  class ConnectionConfig(lib.Enum):
92
27
  """Carrier specific connection configs"""
93
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karrio_easyship
3
- Version: 2025.5.5
3
+ Version: 2025.5.7
4
4
  Summary: Karrio - Easyship Shipping Extension
5
5
  Author-email: karrio <hello@karrio.io>
6
6
  License-Expression: LGPL-3.0
@@ -1,6 +1,6 @@
1
1
  karrio/mappers/easyship/__init__.py,sha256=aLA1ENTKZ7CGHtXQ0t5BQYucVdOnKMDPKGrdRXWZyGw,151
2
2
  karrio/mappers/easyship/mapper.py,sha256=fLIBDvC8BkNAi2viZuuBy_zj1sjLSF7cEEbfxtXCz5E,3703
3
- karrio/mappers/easyship/proxy.py,sha256=xoaDjuuEW9kWsBVxBKr42g_jNv2K9l4zB2bOAE2DN_Y,5811
3
+ karrio/mappers/easyship/proxy.py,sha256=UkI9TuvFQ5bN4XDVLd5fm8xkTPf8lRnNqDaMZw53fVE,6537
4
4
  karrio/mappers/easyship/settings.py,sha256=ghyujn4gy5XtjCggAI59tzEAHNX_GxA0RC2-vxxv_mo,489
5
5
  karrio/plugins/easyship/__init__.py,sha256=ENXQAYszRmxCvdVoY8EJtRAJBL0u31AlUhEmKD3Kpx0,543
6
6
  karrio/providers/easyship/__init__.py,sha256=dWvRdjJLzka87iVfszU3cWVDJJBQn2YKKrT7-rZjAAU,759
@@ -8,9 +8,9 @@ karrio/providers/easyship/error.py,sha256=sqp_d9TMhjB1dg6UxJ-cIi8QW1UMV2pI_Xp623
8
8
  karrio/providers/easyship/manifest.py,sha256=BoFk5beASPeIbdToB5yi_naVzUSTLcIB11rLBmgJ7kY,2243
9
9
  karrio/providers/easyship/metadata.json,sha256=EuukQpPGZST6ANmXCz5QqTYYAhFUxHvt3QAnxlrqyvk,229126
10
10
  karrio/providers/easyship/rate.py,sha256=PSKu8wDJH0i3xeUpk6OLmIe7Pyio6_i4tiXmP8ekqpk,9005
11
- karrio/providers/easyship/tracking.py,sha256=WdWLFmYYyQz9UYm3QDzSDsiw6aS_JVtS6uNV2oRmoBM,3532
12
- karrio/providers/easyship/units.py,sha256=52urzcBU0tEUiOFcfBPQnwnbNkRyeQNE3VtvKFWtKKo,4284
13
- karrio/providers/easyship/utils.py,sha256=v-H-rdbCnjV6qrxYtOZR3GT91dkGFFsQl7phgclakm4,3340
11
+ karrio/providers/easyship/tracking.py,sha256=pQy4hNbOWfP-V4JXhwenMMofHRYq2totoM10O9Va4fs,4220
12
+ karrio/providers/easyship/units.py,sha256=_O45dD2La7FYJdA6NA-o-h6svrt-RRxUASfgDlMGrTw,4508
13
+ karrio/providers/easyship/utils.py,sha256=Sbthof9MMLjZoex3BYcztQ5RRlSuq6NAC18ro0x2KKI,906
14
14
  karrio/providers/easyship/pickup/__init__.py,sha256=vObZR6snWBhZb09L-klqUgNbb3Ib9g0NTSEQpF_T4mM,299
15
15
  karrio/providers/easyship/pickup/cancel.py,sha256=Kq6T0AvWjV1QSs43tbvRxiuJx0nP5v7cVa6Ls-0cQ1Y,1251
16
16
  karrio/providers/easyship/pickup/create.py,sha256=YX4pFPdZIgvIH5tB5SpcxiBo-4Rxp3c4msZKy-Ib_4E,3118
@@ -32,8 +32,8 @@ karrio/schemas/easyship/shipment_request.py,sha256=nowz5CWtxdZg4zb81a_QsrmEI0dEt
32
32
  karrio/schemas/easyship/shipment_response.py,sha256=9uJJs4xR0bLLz7vmZMCSaIyz59dBl4kvHjMKwVvLs7g,11002
33
33
  karrio/schemas/easyship/tracking_request.py,sha256=XQ735p0jyG46fzaNlSqb2Wu8Ral6UgID8HFM6fBQsPw,1620
34
34
  karrio/schemas/easyship/tracking_response.py,sha256=QwuvI_8-YQXo-2q6uMAJA5IQrgX2gfoPtg1TGBHoZA4,1830
35
- karrio_easyship-2025.5.5.dist-info/METADATA,sha256=rSDKnFwHwOGa2Yv6pKLP6GSI9r6cZiAvVOWHucV3QNk,997
36
- karrio_easyship-2025.5.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
- karrio_easyship-2025.5.5.dist-info/entry_points.txt,sha256=fWVjIqvIorXvb1UVUMceu3i7pP0ba6gJnaZm7HLkZIY,61
38
- karrio_easyship-2025.5.5.dist-info/top_level.txt,sha256=FZCY8Nwft8oEGHdl--xku8P3TrnOxu5dETEU_fWpRSM,20
39
- karrio_easyship-2025.5.5.dist-info/RECORD,,
35
+ karrio_easyship-2025.5.7.dist-info/METADATA,sha256=_40DRtWY4i2bKQvMSyd3ER7qGPLXxahPo9U9UM64-hI,997
36
+ karrio_easyship-2025.5.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
+ karrio_easyship-2025.5.7.dist-info/entry_points.txt,sha256=fWVjIqvIorXvb1UVUMceu3i7pP0ba6gJnaZm7HLkZIY,61
38
+ karrio_easyship-2025.5.7.dist-info/top_level.txt,sha256=FZCY8Nwft8oEGHdl--xku8P3TrnOxu5dETEU_fWpRSM,20
39
+ karrio_easyship-2025.5.7.dist-info/RECORD,,