Skip to content

Commit 6262f97

Browse files
authored
Merge commit from fork
* fix: remove Google App Engine urlfetch transport * refactor: remove unused Requestor URL helpers
1 parent 3a4d2ae commit 6262f97

6 files changed

Lines changed: 24 additions & 142 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# CHANGELOG
22

3+
## Next Release
4+
5+
- Removes the legacy Google App Engine `urlfetch` transport; all requests now use `requests`
6+
37
## v10.8.0 (2026-10-27)
48

59
- Removes the deprecated, unusable `add_credit_card` function

‎README.md‎

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,3 @@ Some tests may require an EasyPost user with a particular set of enabled feature
140140
- `USPS_CARRIER_ACCOUNT_ID` (eg: one-call buying a shipment for non-EasyPost employees)
141141
- `PARTNER_USER_PROD_API_KEY` (eg: creating a referral user)
142142
- `REFERRAL_CUSTOMER_PROD_API_KEY` (eg: adding a credit card to a referral user)
143-
144-
#### Google Cloud SDK
145-
146-
To run the test suite with the Google Cloud SDK (`urlfetch` instead of the `requests` library), you'll need the following:
147-
148-
1. Install the appengine Python package to this virtual environment: `venv/bin/pip install appengine-python-standard`
149-
1. Install the Google Cloud SDK
150-
- [Direct Download](https://cloud.google.com/sdk/docs/install)
151-
- [Homebrew](https://formulae.brew.sh/cask/google-cloud-sdk)
152-
1. Point the `PYTHONPATH` environment variable to the path of the newly installed `google-cloud-sdk` directory. For Homebrew, this is `"$(brew --prefix)/share/google-cloud-sdk"`
153-
1. Run the test suite with the commands listed in this README

‎easypost/constant.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
COMMUNICATION_ERROR = "Unexpected error communicating with EasyPost. If this problem persists please let us know at {}. Original error: {}"
1515
INVALID_DELIVER_ACCURACY_ERROR = "Invalid delivery_accuracy value, must be one of: {}"
1616
INVALID_PAYMENT_METHOD_ERROR = "The chosen payment method is not valid. Please try again."
17-
INVALID_REQUEST_LIB_ERROR = "Bug discovered: invalid request_lib: {}. Please report to {}."
1817
INVALID_REQUEST_METHOD_ERROR = "Bug discovered: invalid request method: {}. Please report to {}."
1918
INVALID_REQUEST_PARAMETERS_ERROR = "Only GET and DELETE requests support parameters."
2019
INVALID_REQUESTS_VERSION_ERROR = 'EasyPost requires an up to date requests library. Update requests via "pip install -U requests" or contact us at {}.'

‎easypost/easypost_client.py‎

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -96,36 +96,26 @@ def __init__(
9696
self._request_hook = RequestHook()
9797
self._response_hook = ResponseHook()
9898

99-
# use urlfetch as request_lib on google app engine, otherwise use requests
100-
self._request_lib = None
10199
try:
102-
from google.appengine.api import urlfetch # type: ignore
103-
104-
self._request_lib = "urlfetch"
105-
# use the GAE application-wide "deadline" (or its default)
106-
timeout = urlfetch.get_default_fetch_deadline() or self.timeout
107-
except ImportError:
108-
try:
109-
import requests
110-
111-
self._request_lib = "requests"
112-
self._requests_session = requests.Session()
113-
requests_http_adapter = requests.adapters.HTTPAdapter(max_retries=3)
114-
self._requests_session.mount(
115-
prefix=self.api_base.split(f"/{API_VERSION}")[0],
116-
adapter=requests_http_adapter,
117-
)
118-
except Exception:
119-
raise ImportError(INVALID_REQUESTS_VERSION_ERROR.format(SUPPORT_EMAIL))
100+
import requests
101+
102+
self._requests_session = requests.Session()
103+
requests_http_adapter = requests.adapters.HTTPAdapter(max_retries=3)
104+
self._requests_session.mount(
105+
prefix=self.api_base.split(f"/{API_VERSION}")[0],
106+
adapter=requests_http_adapter,
107+
)
108+
except Exception:
109+
raise ImportError(INVALID_REQUESTS_VERSION_ERROR.format(SUPPORT_EMAIL))
120110

121-
try:
122-
requests_version = requests.__version__
123-
major_version, _, _ = [int(i) for i in requests_version.split(".")]
124-
except Exception:
111+
try:
112+
requests_version = requests.__version__
113+
major_version, _, _ = [int(i) for i in requests_version.split(".")]
114+
except Exception:
115+
raise ImportError(INVALID_REQUESTS_VERSION_ERROR.format(SUPPORT_EMAIL))
116+
else:
117+
if major_version < 1:
125118
raise ImportError(INVALID_REQUESTS_VERSION_ERROR.format(SUPPORT_EMAIL))
126-
else:
127-
if major_version < 1:
128-
raise ImportError(INVALID_REQUESTS_VERSION_ERROR.format(SUPPORT_EMAIL))
129119

130120
def subscribe_to_request_hook(self, function):
131121
"""Subscribe functions to run when a request occurs."""

‎easypost/requestor.py‎

Lines changed: 3 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
import datetime
22
import json
33
import platform
4-
import time
54
import uuid
65
from enum import Enum
76
from json import JSONDecodeError
87
from typing import (
98
Any,
109
Optional,
1110
Tuple,
12-
Union,
1311
)
14-
from urllib.parse import urlencode
1512

1613
import requests
1714

1815
from easypost.constant import (
1916
API_VERSION,
2017
COMMUNICATION_ERROR,
21-
INVALID_REQUEST_LIB_ERROR,
2218
INVALID_REQUEST_METHOD_ERROR,
2319
INVALID_REQUEST_PARAMETERS_ERROR,
2420
INVALID_RESPONSE_BODY_ERROR,
@@ -212,16 +208,9 @@ def request_raw(
212208
request_uuid=request_uuid,
213209
)
214210

215-
if self._client._request_lib == "urlfetch":
216-
http_body, http_status, http_headers = self.urlfetch_request(
217-
method=method, abs_url=abs_url, headers=headers, params=params
218-
)
219-
elif self._client._request_lib == "requests":
220-
http_body, http_status, http_headers = self.requests_request(
221-
method=method, abs_url=abs_url, headers=headers, params=params
222-
)
223-
else:
224-
raise EasyPostError(INVALID_REQUEST_LIB_ERROR.format(self._client._request_lib, SUPPORT_EMAIL))
211+
http_body, http_status, http_headers = self.requests_request(
212+
method=method, abs_url=abs_url, headers=headers, params=params
213+
)
225214

226215
response_timestamp = datetime.datetime.now(datetime.timezone.utc)
227216
self._client._response_hook(
@@ -293,40 +282,6 @@ def requests_request(
293282

294283
return http_body, http_status, http_headers
295284

296-
def urlfetch_request(
297-
self,
298-
method: RequestMethod,
299-
abs_url: str,
300-
headers: dict[str, Any],
301-
params: dict[str, Any],
302-
) -> Tuple[str, int, dict[str, Any]]:
303-
"""Make a request by using the `urlfetch` library."""
304-
fetch_args = {
305-
"method": method.value,
306-
"headers": headers,
307-
"validate_certificate": False,
308-
"deadline": self._client.timeout,
309-
}
310-
311-
if method in [RequestMethod.GET, RequestMethod.DELETE]:
312-
# GET/DELETE requests use query params
313-
fetch_args["url"] = self.add_params_to_url(url=abs_url, params=params, method=method)
314-
elif method in [RequestMethod.POST, RequestMethod.PUT]:
315-
fetch_args["url"] = abs_url
316-
# POST/PUT requests use body params
317-
fetch_args["payload"] = json.dumps(params, default=self._utf8)
318-
else:
319-
raise EasyPostError(INVALID_REQUEST_METHOD_ERROR.format(method, SUPPORT_EMAIL))
320-
321-
try:
322-
from google.appengine.api import urlfetch # type: ignore
323-
324-
result = urlfetch.fetch(**fetch_args) # type: ignore
325-
except Exception as e:
326-
raise HttpError(COMMUNICATION_ERROR.format(SUPPORT_EMAIL, e))
327-
328-
return result.content, result.status_code, result.headers
329-
330285
def handle_api_error(self, http_status: int, http_body: str, response: dict[str, Any]) -> None:
331286
"""Handles API errors returned from the EasyPost API."""
332287
try:
@@ -348,37 +303,3 @@ def handle_api_error(self, http_status: int, http_body: str, response: dict[str,
348303
http_status=http_status,
349304
http_body=http_body,
350305
)
351-
352-
def _utf8(self, value: Union[str, bytes]) -> str:
353-
# Python3's str(bytestring) returns "b'bytestring'" so we use .decode instead
354-
if isinstance(value, bytes):
355-
return value.decode(encoding="utf-8")
356-
return value
357-
358-
def encode_url_params(self, params: dict[str, Any], method: RequestMethod) -> Union[str, None]:
359-
"""Encode params for a URL."""
360-
if method not in [RequestMethod.GET, RequestMethod.DELETE]:
361-
raise EasyPostError(INVALID_REQUEST_PARAMETERS_ERROR)
362-
363-
converted_params = []
364-
365-
for key, value in sorted(params.items()):
366-
if value is None:
367-
continue # Don't add Nones to the query
368-
elif isinstance(value, datetime.datetime):
369-
value = int(time.mktime(value.timetuple())) # to UTC timestamp
370-
converted_params.append((key, value))
371-
372-
return urlencode(query=converted_params)
373-
374-
def add_params_to_url(self, url: str, params: dict[str, Any], method: RequestMethod) -> str:
375-
"""Add params to the URL."""
376-
if method not in [RequestMethod.GET, RequestMethod.DELETE]:
377-
raise EasyPostError(INVALID_REQUEST_PARAMETERS_ERROR)
378-
379-
encoded_params = self.encode_url_params(params=params, method=method)
380-
381-
if encoded_params:
382-
return "%s?%s" % (url, encoded_params)
383-
384-
return url

‎tests/conftest.py‎

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,6 @@
2121
SCRUBBED_DICT: dict = {}
2222

2323

24-
def pytest_sessionstart(session):
25-
"""This is for local unit testing with google appengine, otherwise you get a
26-
'No api proxy found for service "urlfetch"' response.
27-
"""
28-
try:
29-
from google.appengine.ext import testbed # type: ignore
30-
31-
session.appengine_testbed = testbed.Testbed() # type: ignore
32-
session.appengine_testbed.activate() # type: ignore
33-
session.appengine_testbed.init_urlfetch_stub() # type: ignore
34-
except ImportError:
35-
# if the import fails then we're not using the appengine sdk, so just
36-
# keep going without initializing the testbed stub
37-
pass
38-
39-
40-
def pytest_sessionfinish(session, exitstatus):
41-
if hasattr(session, "appengine_testbed"):
42-
session.appengine_testbed.deactivate() # type: ignore
43-
44-
4524
@pytest.fixture(autouse=True)
4625
def check_expired_cassettes(expiration_days: int = 365, throw_error: bool = False):
4726
"""Checks for expired cassettes and throws errors if they are too old and must be re-recorded."""

0 commit comments

Comments
 (0)