Skip to content

Commit 21a6cbf

Browse files
authored
Merge pull request #160 from EasyPost/modernize
chore: bump minimum Python version from 2.7 to 3.6
2 parents 38e1db9 + 1a5d421 commit 21a6cbf

File tree

10 files changed

+128
-105
lines changed

10 files changed

+128
-105
lines changed

.github/workflows/ci.yml

+9-17
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,22 @@ jobs:
1414
uses: actions/setup-python@v2
1515
with:
1616
python-version: 3.9
17-
- name: install dependencies
18-
run: python -m pip install -r requirements-dev.txt -e .
19-
- name: lint with flake8
20-
run: flake8 --statistics easypost/ tests/ examples/
21-
- name: Check formatting with Black
22-
run: black easypost/ tests/ examples/ --check
23-
- name: Check formatting with iSort
24-
run: isort easypost/ tests/ examples/ --check-only
17+
- name: Install Dependencies
18+
run: make install
19+
- name: Check format
20+
run: make format-check
2521
run-tests:
2622
runs-on: ubuntu-latest
2723
strategy:
2824
matrix:
29-
pythonversion:
30-
["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "pypy-2.7", "pypy-3.6"]
25+
pythonversion: ["pypy-3.6", "3.6", "3.7", "3.8", "3.9", "3.10"]
3126
steps:
3227
- uses: actions/checkout@v2
3328
- name: set up python
3429
uses: actions/setup-python@v2
3530
with:
3631
python-version: ${{ matrix.pythonversion }}
37-
- name: install dependencies
38-
run: "python -m pip install -r requirements-tests.txt -e ."
39-
- name: test with pytest
40-
run: pytest --cov=easypost --cov-report=term-missing --vcr-record=none --cov-fail-under=60 tests/
41-
env:
42-
TEST_API_KEY: ${{ secrets.TEST_API_KEY }}
43-
PROD_API_KEY: ${{ secrets.PROD_API_KEY }}
32+
- name: Install Dependencies
33+
run: make install
34+
- name: Run Tests
35+
run: EASYPOST_TEST_API_KEY=123 EASYPOST_PROD_API_KEY=123 make test

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
### NEXT RELEASE
44

5+
* Bumps minimum Python version from 2.7 to 3.6
6+
* Bumps all dependencies
57
* Add the `update_brand()` method to the User object
68
* Removes `_max_timeout` and instead uses a flat 60 second timeout for requests
79
* Adds Python version to user-agent header on requests

Makefile

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
PYTHON_BINARY := python3
2+
VIRTUAL_ENV := venv
3+
VIRTUAL_BIN := $(VIRTUAL_ENV)/bin
4+
PROJECT_NAME := easypost
5+
TEST_DIR := tests
6+
7+
## help - Display help about make targets for this Makefile
8+
help:
9+
@cat Makefile | grep '^## ' --color=never | cut -c4- | sed -e "`printf 's/ - /\t- /;'`" | column -s "`printf '\t'`" -t
10+
11+
## build - Builds the project in preparation for release
12+
build:
13+
$(PYTHON_BINARY) setup.py sdist bdist_wheel
14+
15+
## coverage - Test the project and generate an HTML coverage report
16+
coverage:
17+
$(VIRTUAL_BIN)/pytest --cov=$(PROJECT_NAME) --cov-branch --cov-report=html --cov-report=term-missing
18+
19+
## clean - Remove the virtual environment and clear out .pyc files
20+
clean:
21+
rm -rf $(VIRTUAL_ENV)
22+
find . -name '*.pyc' -delete
23+
rm -rf dist
24+
rm -rf build
25+
rm -rf *.egg-info
26+
27+
## black - Runs the Black Python formatter against the project
28+
black:
29+
$(VIRTUAL_BIN)/black $(PROJECT_NAME)/ $(TEST_DIR)/
30+
31+
## black-check - Checks if the project is formatted correctly against the Black rules
32+
black-check:
33+
$(VIRTUAL_BIN)/black $(PROJECT_NAME)/ $(TEST_DIR)/ --check
34+
35+
## format - Runs all formatting tools against the project
36+
format: black isort lint
37+
38+
## format-check - Checks if the project is formatted correctly against all formatting rules
39+
format-check: black-check isort-check lint
40+
41+
## install - Install the project locally
42+
install:
43+
$(PYTHON_BINARY) -m venv $(VIRTUAL_ENV)
44+
$(VIRTUAL_BIN)/pip install -e ."[dev]"
45+
46+
## isort - Sorts imports throughout the project
47+
isort:
48+
$(VIRTUAL_BIN)/isort $(PROJECT_NAME)/ $(TEST_DIR)/
49+
50+
## isort-check - Checks that imports throughout the project are sorted correctly
51+
isort-check:
52+
$(VIRTUAL_BIN)/isort $(PROJECT_NAME)/ $(TEST_DIR)/ --check-only
53+
54+
## lint - Lint the project
55+
lint:
56+
$(VIRTUAL_BIN)/flake8 $(PROJECT_NAME)/ $(TEST_DIR)/
57+
58+
## test - Test the project
59+
test:
60+
$(VIRTUAL_BIN)/pytest
61+
62+
.PHONY: help build coverage clean black black-check format format-check install isort isort-check lint test

README.md

+9-16
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,6 @@
55

66
EasyPost is the simple shipping API. You can sign up for an account at <https://easypost.com>.
77

8-
Requirements
9-
------------
10-
11-
* [Python](https://www.python.org) 2.7 or 3.3+ (or corresponding [PyPy](https://pypy.org) versions). Note that we only test on Python 2.7 and 3.5+; we strongly recommend against using 3.3.x or 3.4.x as they are no longer supported by many libraries.
12-
* [requests](http://docs.python-requests.org/en/latest/) >= v2.4.3 (if not on Google App Engine) (will be installed automatically)
13-
* [six](https://pythonhosted.org/six/) (will be installed automatically)
14-
158

169
Looking for a client library for another language? Check out <https://www.easypost.com/docs/libraries>.
1710

@@ -31,10 +24,10 @@ Alternatively, you can clone the EasyPost python client repository:
3124
git clone https://github.com/EasyPost/easypost-python
3225
```
3326

34-
Install:
27+
Install Locally:
3528

3629
```bash
37-
python setup.py install
30+
make install
3831
```
3932

4033
Import the EasyPost client:
@@ -153,12 +146,12 @@ Client Library Development
153146
1. Build sdist and wheel: `rm -rf build/ dist/ ./*.egg-info; python3 setup.py sdist bdist_wheel`
154147
1. Push to PyPI with `twine upload dist/*`
155148

156-
### Running Tests
149+
## Development
157150

158-
To run tests:
151+
```bash
152+
# Run tests
153+
EASYPOST_TEST_API_KEY=123... EASYPOST_PROD_API_KEY=123... make test
159154

160-
- Create a virtualenv for your version of Python (e.g., `python2.7 -m virtualenv venv`)
161-
- Install dependencies in that virtualenv (`./venv/bin/pip install requests six`)
162-
- Install test dependencies (`./venv/bin/pip install -r requirements-tests.txt`)
163-
- Export `$TEST_API_KEY` and `$PROD_API_KEY` appropriately (these are set automatically for CI)
164-
- Run the tests with `py.test` (`./venv/bin/py.test -vs tests`)
155+
# Run test coverage
156+
EASYPOST_TEST_API_KEY=123... EASYPOST_PROD_API_KEY=123... make coverage
157+
```

easypost/__init__.py

+17-25
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,15 @@
44
import re
55
import ssl
66
import time
7-
8-
import six
9-
from six.moves.urllib.parse import urlencode
7+
from urllib.parse import urlencode
108

119
from .version import VERSION, VERSION_INFO
1210

1311
__author__ = "EasyPost <[email protected]>"
1412
__version__ = VERSION
1513
version_info = VERSION_INFO
1614
SUPPORT_EMAIL = "[email protected]"
17-
USER_AGENT = "EasyPost/v2 PythonClient/{0} Python/{1}".format(VERSION, platform.python_version())
15+
USER_AGENT = f"EasyPost/v2 PythonClient/{VERSION} Python/{platform.python_version()}"
1816
TIMEOUT = 60
1917

2018
# config
@@ -42,7 +40,7 @@
4240
raise ImportError(
4341
"EasyPost requires an up to date requests library. "
4442
'Update requests via "pip install -U requests" or '
45-
"contact us at {}.".format(SUPPORT_EMAIL)
43+
f"contact us at {SUPPORT_EMAIL}."
4644
)
4745

4846
try:
@@ -52,14 +50,14 @@
5250
raise ImportError(
5351
"EasyPost requires an up to date requests library. "
5452
'Update requests via "pip install -U requests" or contact '
55-
"us at {}.".format(SUPPORT_EMAIL)
53+
f"us at {SUPPORT_EMAIL}."
5654
)
5755
else:
5856
if major < 1:
5957
raise ImportError(
6058
"EasyPost requires an up to date requests library. Update "
6159
'requests via "pip install -U requests" or contact us '
62-
"at {}.".format(SUPPORT_EMAIL)
60+
f"at {SUPPORT_EMAIL}."
6361
)
6462

6563

@@ -146,7 +144,7 @@ def convert_to_easypost_object(response, api_key, parent=None, name=None):
146144
response = response.copy()
147145
cls_name = response.get("object", EasyPostObject)
148146
cls_id = response.get("id", None)
149-
if isinstance(cls_name, six.string_types):
147+
if isinstance(cls_name, str):
150148
cls = types.get(cls_name, EasyPostObject)
151149
elif cls_id is not None:
152150
cls = prefixes.get(cls_id[0 : cls_id.find("_")], EasyPostObject)
@@ -159,14 +157,14 @@ def convert_to_easypost_object(response, api_key, parent=None, name=None):
159157

160158
def _utf8(value):
161159
# Python3's str(bytestring) returns "b'bytestring'" so we use .decode instead
162-
if isinstance(value, six.binary_type):
160+
if isinstance(value, bytes):
163161
return value.decode("utf-8")
164162
return value
165163

166164

167165
def encode_url_params(params):
168166
converted_params = []
169-
for key, value in sorted(six.iteritems(params)):
167+
for key, value in sorted(params.items()):
170168
if value is None:
171169
continue # don't add Nones to the query
172170
elif isinstance(value, datetime.datetime):
@@ -191,7 +189,7 @@ def _objects_to_ids(cls, param):
191189
if isinstance(param, Resource):
192190
return {"id": param.id}
193191
elif isinstance(param, dict):
194-
return {k: cls._objects_to_ids(v) for k, v in six.iteritems(param)}
192+
return {k: cls._objects_to_ids(v) for k, v in param.items()}
195193
elif isinstance(param, list):
196194
return [cls._objects_to_ids(v) for v in param]
197195
else:
@@ -213,7 +211,7 @@ def request_raw(self, method, url, params=None, apiKeyRequired=True):
213211
raise Error(
214212
"No API key provided. Set an API key via \"easypost.api_key = 'APIKEY'. "
215213
"Your API keys can be found in your EasyPost dashboard, or you can email us "
216-
"at {} for assistance.".format(SUPPORT_EMAIL)
214+
f"at {SUPPORT_EMAIL} for assistance."
217215
)
218216

219217
abs_url = "%s%s" % (api_base, url or "")
@@ -251,9 +249,7 @@ def request_raw(self, method, url, params=None, apiKeyRequired=True):
251249
elif request_lib == "requests":
252250
http_body, http_status = self.requests_request(method, abs_url, headers, params)
253251
else:
254-
raise Error(
255-
"Bug discovered: invalid request_lib: {}. " "Please report to {}.".format(request_lib, SUPPORT_EMAIL)
256-
)
252+
raise Error(f"Bug discovered: invalid request_lib: {request_lib}. Please report to {SUPPORT_EMAIL}.")
257253

258254
return http_body, http_status, my_api_key
259255

@@ -274,9 +270,7 @@ def requests_request(self, method, abs_url, headers, params):
274270
elif method == "post" or method == "put":
275271
data = json.dumps(params, default=_utf8)
276272
else:
277-
raise Error(
278-
"Bug discovered: invalid request method: {}. " "Please report to {}.".format(method, SUPPORT_EMAIL)
279-
)
273+
raise Error(f"Bug discovered: invalid request method: {method}. Please report to {SUPPORT_EMAIL}.")
280274

281275
try:
282276
result = requests_session.request(
@@ -292,7 +286,7 @@ def requests_request(self, method, abs_url, headers, params):
292286
except Exception as e:
293287
raise Error(
294288
"Unexpected error communicating with EasyPost. If this "
295-
"problem persists please let us know at {}.".format(SUPPORT_EMAIL),
289+
f"problem persists please let us know at {SUPPORT_EMAIL}.",
296290
original_exception=e,
297291
)
298292
return http_body, http_status
@@ -305,16 +299,14 @@ def urlfetch_request(self, method, abs_url, headers, params):
305299
elif method == "get" or method == "delete":
306300
fetch_args["url"] = add_params_to_url(abs_url, params)
307301
else:
308-
raise Error(
309-
"Bug discovered: invalid request method: {}. Please report " "to {}.".format(method, SUPPORT_EMAIL)
310-
)
302+
raise Error(f"Bug discovered: invalid request method: {method}. Please report to {SUPPORT_EMAIL}.")
311303

312304
try:
313305
result = urlfetch.fetch(**fetch_args)
314306
except Exception as e:
315307
raise Error(
316308
"Unexpected error communicating with EasyPost. "
317-
"If this problem persists, let us know at {}.".format(SUPPORT_EMAIL),
309+
f"If this problem persists, let us know at {SUPPORT_EMAIL}.",
318310
original_exception=e,
319311
)
320312

@@ -404,7 +396,7 @@ def construct_from(cls, values, api_key=None, parent=None, name=None):
404396
def refresh_from(self, values, api_key):
405397
self._api_key = api_key
406398

407-
for k, v in sorted(six.iteritems(values)):
399+
for k, v in sorted(values.items()):
408400
if k == "id" and self.id != v:
409401
self.id = v
410402
if k in self._immutable_values:
@@ -427,7 +419,7 @@ def flatten_unsaved(self):
427419
def __repr__(self):
428420
type_string = ""
429421

430-
if isinstance(self.get("object"), six.string_types):
422+
if isinstance(self.get("object"), str):
431423
type_string = " %s" % self.get("object").encode("utf8")
432424

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

requirements-dev.txt

-3
This file was deleted.

requirements-tests.txt

-16
This file was deleted.

0 commit comments

Comments
 (0)