Skip to content

Commit 504c590

Browse files
authored
Merge pull request #358 from EasyPost/breaking_changes
breaking changes
2 parents 5f7eaf1 + 409b87e commit 504c590

File tree

177 files changed

+8947
-10013
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

177 files changed

+8947
-10013
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@ jobs:
1919
- name: Lint
2020
run: make lint
2121
run-tests:
22-
# TODO: Use `latest` once we drop support for Python 3.7
23-
runs-on: ubuntu-22.04
22+
runs-on: ubuntu-latest
2423
strategy:
2524
matrix:
26-
pythonversion: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
25+
pythonversion: ['3.9', '3.10', '3.11', '3.12', '3.13']
2726
steps:
2827
- uses: actions/checkout@v4
2928
- uses: actions/setup-python@v5

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
## Next Release
44

5+
- Drops support for Python 3.7 and 3.8
56
- Fixes the payload wrapping for updating a webhook
7+
- Removes deprecated `user.all_api_keys` and `user.api_keys`, use `api_key.all` and `api_key.retrieve_api_keys_for_user` respectively
8+
- Bumps all dev dependencies
69

710
## v9.5.0 (2024-10-24)
811

easypost/easypost_object.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@
33
import re
44
from typing import (
55
Any,
6-
Dict,
7-
List,
86
Optional,
97
)
108

119
from easypost.constant import NO_ATTRIBUTE_ERROR
1210

1311

14-
EASYPOST_OBJECT_ID_PREFIX_TO_CLASS_NAME_MAP: Dict[str, Any] = {
12+
EASYPOST_OBJECT_ID_PREFIX_TO_CLASS_NAME_MAP: dict[str, Any] = {
1513
"adr": "Address",
1614
"ak": "ApiKey",
1715
"batch": "Batch",
@@ -44,7 +42,7 @@
4442
"user": "User",
4543
}
4644

47-
OBJECT_CLASS_NAME_OVERRIDES: Dict[str, Any] = {
45+
OBJECT_CLASS_NAME_OVERRIDES: dict[str, Any] = {
4846
"CashFlowReport": "Report",
4947
"PaymentLogReport": "Report",
5048
"RefundReport": "Report",
@@ -55,7 +53,7 @@
5553

5654

5755
def convert_to_easypost_object(
58-
response: Dict[str, Any],
56+
response: dict[str, Any],
5957
parent: object = None,
6058
name: Optional[str] = None,
6159
):
@@ -147,17 +145,17 @@ def __setitem__(self, k, v) -> None:
147145
setattr(self, k, v)
148146

149147
@property
150-
def keys(self) -> List[str]:
148+
def keys(self) -> list[str]:
151149
return self._values.keys()
152150

153151
@property
154-
def values(self) -> List[Any]:
152+
def values(self) -> list[Any]:
155153
return self._values.keys()
156154

157155
@classmethod
158156
def construct_from(
159157
cls,
160-
values: Dict[str, Any],
158+
values: dict[str, Any],
161159
parent: object = None,
162160
name: Optional[str] = None,
163161
) -> object:
@@ -167,7 +165,7 @@ def construct_from(
167165

168166
return instance
169167

170-
def convert_each_value(self, values: Dict[str, Any]) -> None:
168+
def convert_each_value(self, values: dict[str, Any]) -> None:
171169
"""Convert each value of a response into an EasyPostObject."""
172170
for k, v in sorted(values.items()):
173171
if k == "id" and self.id != v:
@@ -186,7 +184,12 @@ def __repr__(self) -> str:
186184

187185
json_string = json.dumps(obj=self.to_dict(), sort_keys=True, indent=2, cls=EasyPostObjectEncoder)
188186

189-
return "<%s%s at %s> JSON: %s" % (type(self).__name__, type_string, hex(id(self)), json_string)
187+
return "<%s%s at %s> JSON: %s" % (
188+
type(self).__name__,
189+
type_string,
190+
hex(id(self)),
191+
json_string,
192+
)
190193

191194
def __str__(self) -> str:
192195
return self.to_json(indent=2)
@@ -200,7 +203,7 @@ def to_json(self, indent: Optional[int] = None) -> str:
200203
"""Convert current object to json string."""
201204
return json.dumps(obj=self.to_dict(), sort_keys=True, indent=indent, cls=EasyPostObjectEncoder)
202205

203-
def to_dict(self) -> Dict[str, Any]:
206+
def to_dict(self) -> dict[str, Any]:
204207
"""Convert current object to a dict."""
205208

206209
def _serialize(o):

easypost/errors/api/api_error.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import json
22
from typing import (
33
Any,
4-
Dict,
5-
List,
64
Optional,
75
Union,
86
)
@@ -16,15 +14,15 @@ class ApiError(EasyPostError):
1614
def __init__(
1715
self,
1816
message: Union[
19-
Dict[str, Any], list, str
17+
dict[str, Any], list, str
2018
], # message should be a string but can sometimes incorrectly come back as a list or object
21-
errors: Optional[List[str]] = None,
19+
errors: Optional[list[str]] = None,
2220
code: Optional[str] = None,
2321
http_status: Optional[int] = None,
2422
http_body: Optional[Union[str, bytes]] = None,
2523
):
2624
super().__init__(message) # type: ignore
27-
message_list: List[str] = []
25+
message_list: list[str] = []
2826
self._traverse_json_element(message, message_list)
2927
self.message = ", ".join(message_list)
3028
self.errors = errors
@@ -54,8 +52,8 @@ def __init__(
5452

5553
def _traverse_json_element(
5654
self,
57-
error_message: Optional[Union[Dict[str, Any], list, str]],
58-
messages_list: List[str],
55+
error_message: Optional[Union[dict[str, Any], list, str]],
56+
messages_list: list[str],
5957
) -> None:
6058
"""Recursively traverses a JSON object or array and extracts error messages
6159
as strings. Adds the extracted messages to the specified messages_list array.

easypost/models/order.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from typing import (
2-
List,
32
Optional,
43
)
54

@@ -9,7 +8,7 @@
98

109

1110
class Order(EasyPostObject):
12-
def lowest_rate(self, carriers: Optional[List[str]] = None, services: Optional[List[str]] = None) -> Rate:
11+
def lowest_rate(self, carriers: Optional[list[str]] = None, services: Optional[list[str]] = None) -> Rate:
1312
"""Get the lowest rate of this Order."""
1413
lowest_rate = get_lowest_object_rate(self, carriers, services)
1514

easypost/models/pickup.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from typing import (
2-
List,
32
Optional,
43
)
54

@@ -9,7 +8,7 @@
98

109

1110
class Pickup(EasyPostObject):
12-
def lowest_rate(self, carriers: Optional[List[str]] = None, services: Optional[List[str]] = None) -> Rate:
11+
def lowest_rate(self, carriers: Optional[list[str]] = None, services: Optional[list[str]] = None) -> Rate:
1312
"""Get the lowest rate of this Pickup."""
1413
lowest_rate = get_lowest_object_rate(self, carriers, services, "pickup_rates")
1514

easypost/models/shipment.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from typing import (
2-
List,
32
Optional,
43
)
54

@@ -9,7 +8,7 @@
98

109

1110
class Shipment(EasyPostObject):
12-
def lowest_rate(self, carriers: Optional[List[str]] = None, services: Optional[List[str]] = None) -> Rate:
11+
def lowest_rate(self, carriers: Optional[list[str]] = None, services: Optional[list[str]] = None) -> Rate:
1312
"""Get the lowest rate of this shipment."""
1413
lowest_rate = get_lowest_object_rate(self, carriers, services)
1514

easypost/requestor.py

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
from json import JSONDecodeError
88
from typing import (
99
Any,
10-
Dict,
11-
List,
1210
Optional,
1311
Tuple,
1412
Union,
@@ -49,7 +47,7 @@
4947
)
5048

5149

52-
STATUS_CODE_TO_ERROR_MAPPING: Dict[int, Any] = {
50+
STATUS_CODE_TO_ERROR_MAPPING: dict[int, Any] = {
5351
400: BadRequestError,
5452
401: UnauthorizedError,
5553
402: PaymentError,
@@ -78,7 +76,7 @@ def __init__(self, client):
7876
self._client = client
7977

8078
@classmethod
81-
def _objects_to_ids(cls, param: Dict[str, Any]) -> Dict[str, Any]:
79+
def _objects_to_ids(cls, param: dict[str, Any]) -> dict[str, Any]:
8280
"""If providing an object as a parameter to another object,
8381
only pass along the ID so the API will use the object reference correctly.
8482
"""
@@ -97,10 +95,10 @@ def _objects_to_ids(cls, param: Dict[str, Any]) -> Dict[str, Any]:
9795

9896
@staticmethod
9997
def form_encode_params(
100-
data: Dict[str, Any],
101-
parent_keys: Optional[List[str]] = None,
102-
parent_dict: Optional[Dict[str, Any]] = None,
103-
) -> Dict:
98+
data: dict[str, Any],
99+
parent_keys: Optional[list[str]] = None,
100+
parent_dict: Optional[dict[str, Any]] = None,
101+
) -> dict:
104102
"""Form-encode a multi-layer dictionary to a one-layer dictionary."""
105103
result = parent_dict or {}
106104
keys = parent_keys or []
@@ -116,7 +114,7 @@ def form_encode_params(
116114
return result
117115

118116
@staticmethod
119-
def _build_dict_key(keys: List[str]) -> str:
117+
def _build_dict_key(keys: list[str]) -> str:
120118
"""Build a dict key from a list of keys.
121119
Example: [code, number] -> code[number]
122120
"""
@@ -131,9 +129,9 @@ def request(
131129
self,
132130
method: RequestMethod,
133131
url: str,
134-
params: Optional[Dict[str, Any]] = None,
132+
params: Optional[dict[str, Any]] = None,
135133
beta: bool = False,
136-
) -> Dict[str, Any]:
134+
) -> dict[str, Any]:
137135
"""Make a request to the EasyPost API."""
138136
if params is None:
139137
params = {}
@@ -153,7 +151,7 @@ def request_raw(
153151
self,
154152
method: RequestMethod,
155153
url: str,
156-
params: Optional[Dict[str, Any]] = None,
154+
params: Optional[dict[str, Any]] = None,
157155
beta: bool = False,
158156
) -> Tuple[str, int]:
159157
"""Internal logic required to make a request to the EasyPost API."""
@@ -239,7 +237,7 @@ def request_raw(
239237

240238
return http_body, http_status
241239

242-
def interpret_response(self, http_body: str, http_status: int) -> Dict[str, Any]:
240+
def interpret_response(self, http_body: str, http_status: int) -> dict[str, Any]:
243241
"""Interpret the response body we receive from the API."""
244242
if http_status == 204:
245243
# HTTP 204 does not have any response body and we can just return here
@@ -259,9 +257,9 @@ def requests_request(
259257
self,
260258
method: RequestMethod,
261259
abs_url: str,
262-
headers: Dict[str, Any],
263-
params: Dict[str, Any],
264-
) -> Tuple[str, int, Dict[str, Any]]:
260+
headers: dict[str, Any],
261+
params: dict[str, Any],
262+
) -> Tuple[str, int, dict[str, Any]]:
265263
"""Make a request by using the `request` library."""
266264
if method in [RequestMethod.GET, RequestMethod.DELETE]:
267265
url_params = params
@@ -299,9 +297,9 @@ def urlfetch_request(
299297
self,
300298
method: RequestMethod,
301299
abs_url: str,
302-
headers: Dict[str, Any],
303-
params: Dict[str, Any],
304-
) -> Tuple[str, int, Dict[str, Any]]:
300+
headers: dict[str, Any],
301+
params: dict[str, Any],
302+
) -> Tuple[str, int, dict[str, Any]]:
305303
"""Make a request by using the `urlfetch` library."""
306304
fetch_args = {
307305
"method": method.value,
@@ -329,7 +327,7 @@ def urlfetch_request(
329327

330328
return result.content, result.status_code, result.headers
331329

332-
def handle_api_error(self, http_status: int, http_body: str, response: Dict[str, Any]) -> None:
330+
def handle_api_error(self, http_status: int, http_body: str, response: dict[str, Any]) -> None:
333331
"""Handles API errors returned from the EasyPost API."""
334332
try:
335333
error = response["error"]
@@ -357,7 +355,7 @@ def _utf8(self, value: Union[str, bytes]) -> str:
357355
return value.decode(encoding="utf-8")
358356
return value
359357

360-
def encode_url_params(self, params: Dict[str, Any], method: RequestMethod) -> Union[str, None]:
358+
def encode_url_params(self, params: dict[str, Any], method: RequestMethod) -> Union[str, None]:
361359
"""Encode params for a URL."""
362360
if method not in [RequestMethod.GET, RequestMethod.DELETE]:
363361
raise EasyPostError(INVALID_REQUEST_PARAMETERS_ERROR)
@@ -373,7 +371,7 @@ def encode_url_params(self, params: Dict[str, Any], method: RequestMethod) -> Un
373371

374372
return urlencode(query=converted_params)
375373

376-
def add_params_to_url(self, url: str, params: Dict[str, Any], method: RequestMethod) -> str:
374+
def add_params_to_url(self, url: str, params: dict[str, Any], method: RequestMethod) -> str:
377375
"""Add params to the URL."""
378376
if method not in [RequestMethod.GET, RequestMethod.DELETE]:
379377
raise EasyPostError(INVALID_REQUEST_PARAMETERS_ERROR)

easypost/services/address_service.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import (
22
Any,
3-
Dict,
43
Optional,
54
)
65

@@ -26,7 +25,7 @@ def create(
2625
) -> Address:
2726
"""Create an Address."""
2827
url = self._class_url(self._model_class)
29-
wrapped_params = {self._snakecase_name(self._model_class): params} # type: Dict[str, Any]
28+
wrapped_params = {self._snakecase_name(self._model_class): params} # type: dict[str, Any]
3029

3130
if verify:
3231
wrapped_params["verify"] = verify
@@ -37,7 +36,7 @@ def create(
3736

3837
return convert_to_easypost_object(response=response)
3938

40-
def all(self, **params) -> Dict[str, Any]:
39+
def all(self, **params) -> dict[str, Any]:
4140
"""Retrieve a list of Addresses."""
4241
filters = {
4342
"key": "addresses",
@@ -68,10 +67,10 @@ def verify(self, id) -> Address:
6867

6968
def get_next_page(
7069
self,
71-
addresses: Dict[str, Any],
70+
addresses: dict[str, Any],
7271
page_size: int,
73-
optional_params: Optional[Dict[str, Any]] = None,
74-
) -> Dict[str, Any]:
72+
optional_params: Optional[dict[str, Any]] = None,
73+
) -> dict[str, Any]:
7574
"""Retrieve the next page of the list Addresses response."""
7675
self._check_has_next_page(collection=addresses)
7776

easypost/services/api_key_service.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from typing import (
22
Any,
3-
Dict,
4-
List,
53
)
64

75
from easypost.constant import NO_USER_FOUND
@@ -20,15 +18,15 @@ def __init__(self, client):
2018
self._client = client
2119
self._model_class = ApiKey.__name__
2220

23-
def all(self) -> Dict[str, Any]:
21+
def all(self) -> dict[str, Any]:
2422
"""Retrieve a list of all API keys."""
2523
url = "/api_keys"
2624

2725
response = Requestor(self._client).request(method=RequestMethod.GET, url=url)
2826

2927
return convert_to_easypost_object(response=response)
3028

31-
def retrieve_api_keys_for_user(self, id: str) -> List[ApiKey]:
29+
def retrieve_api_keys_for_user(self, id: str) -> list[ApiKey]:
3230
"""Retrieve a list of API keys (works for the authenticated User or a child User)."""
3331
api_keys = self.all()
3432

0 commit comments

Comments
 (0)