Skip to content

Commit cf03dc7

Browse files
authored
Merge pull request #127 from EasyPost/true_verifications
clean up address verify property and some miscellaneous request logic
2 parents 1614b0d + 5daad01 commit cf03dc7

File tree

3 files changed

+143
-27
lines changed

3 files changed

+143
-27
lines changed

easypost/__init__.py

+39-27
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,31 @@ def convert_to_easypost_object(response, api_key, parent=None, name=None):
153153
return response
154154

155155

156+
def _utf8(value):
157+
if six.PY2:
158+
# Python2's urlencode wants bytestrings, not unicode
159+
if isinstance(value, six.text_type):
160+
return value.encode('utf-8')
161+
return value
162+
elif isinstance(value, six.binary_type):
163+
# Python3's six.text_type(bytestring) returns "b'bytestring'"
164+
# So, have to decode it to unicode
165+
return value.decode('utf-8')
166+
else:
167+
# Python3's urlencode can handle unicode
168+
return value
169+
170+
171+
def _urlencode_list(params):
172+
encoded = []
173+
for key, values in sorted(params.items()):
174+
for value in values:
175+
if isinstance(value, bool):
176+
value = str(value).lower()
177+
encoded.append('{0}[]={1}'.format(key, quote_plus(_utf8(value))))
178+
return '&'.join(encoded)
179+
180+
156181
class Requestor(object):
157182
def __init__(self, local_api_key=None):
158183
self._api_key = local_api_key
@@ -162,35 +187,20 @@ def api_url(cls, url=None):
162187
url = url or ''
163188
return '%s%s' % (api_base, url)
164189

165-
@classmethod
166-
def _utf8(cls, value):
167-
if six.PY2:
168-
# Python2's urlencode wants bytestrings, not unicode
169-
if isinstance(value, six.text_type):
170-
return value.encode('utf-8')
171-
return value
172-
elif isinstance(value, six.binary_type):
173-
# Python3's six.text_type(bytestring) returns "b'bytestring'"
174-
# So, have to decode it to unicode
175-
return value.decode('utf-8')
176-
else:
177-
# Python3's urlencode can handle unicode
178-
return value
179-
180190
@classmethod
181191
def encode_dict(cls, out, key, dict_value):
182192
n = {}
183193
for k, v in sorted(six.iteritems(dict_value)):
184-
k = cls._utf8(k)
185-
v = cls._utf8(v)
194+
k = _utf8(k)
195+
v = _utf8(v)
186196
n["%s[%s]" % (key, k)] = v
187197
out.extend(cls._encode_inner(n))
188198

189199
@classmethod
190200
def encode_list(cls, out, key, list_value):
191201
n = {}
192202
for k, v in enumerate(list_value):
193-
v = cls._utf8(v)
203+
v = _utf8(v)
194204
n["%s[%s]" % (key, k)] = v
195205
out.extend(cls._encode_inner(n))
196206

@@ -218,7 +228,7 @@ def _encode_inner(cls, params):
218228

219229
out = []
220230
for key, value in sorted(six.iteritems(params)):
221-
key = cls._utf8(key)
231+
key = _utf8(key)
222232
try:
223233
encoder = ENCODERS[value.__class__]
224234
encoder(out, key, value)
@@ -575,7 +585,7 @@ def instance_url(self):
575585
easypost_id = self.get('id')
576586
if not easypost_id:
577587
raise Error('%s instance has invalid ID: %r' % (type(self).__name__, easypost_id))
578-
easypost_id = Requestor._utf8(easypost_id)
588+
easypost_id = _utf8(easypost_id)
579589
base = self.class_url()
580590
param = quote_plus(easypost_id)
581591
return "{base}/{param}".format(base=base, param=param)
@@ -635,13 +645,15 @@ def create(cls, api_key=None, verify=None, verify_strict=None, **params):
635645
requestor = Requestor(api_key)
636646
url = cls.class_url()
637647

638-
if verify or verify_strict:
639-
verify = verify or []
640-
verify_strict = verify_strict or []
641-
url += '?' + '&'.join(
642-
['verify[]={0}'.format(opt) for opt in verify] +
643-
['verify_strict[]={0}'.format(opt) for opt in verify_strict]
644-
)
648+
verify_params = {}
649+
for key, value in (('verify', verify), ('verify_strict', verify_strict)):
650+
if not value:
651+
continue
652+
elif isinstance(value, (bool, str)):
653+
value = [value]
654+
verify_params[key] = value
655+
if verify_params:
656+
url += '?' + _urlencode_list(verify_params)
645657

646658
wrapped_params = {cls.class_name(): params}
647659
response, api_key = requestor.request('post', url, wrapped_params)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
interactions:
2+
- request:
3+
body: address%5Bcity%5D=San+Francisco&address%5Bcompany%5D=EasyPost&address%5Bcountry%5D=US&address%5Bphone%5D=415-456-7890&address%5Bstate%5D=CA&address%5Bstreet1%5D=118+2&address%5Bstreet2%5D=FLoor+4&address%5Bzip%5D=94105
4+
headers:
5+
Accept:
6+
- '*/*'
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Connection:
10+
- keep-alive
11+
Content-Length:
12+
- '218'
13+
Content-type:
14+
- application/x-www-form-urlencoded
15+
authorization:
16+
- EZTK-NONE
17+
user-agent:
18+
- easypost/v2 pythonclient/suppressed
19+
x-client-user-agent:
20+
- suppressed
21+
method: POST
22+
uri: https://api.easypost.com/v2/addresses?verify%5B%5D=true
23+
response:
24+
body:
25+
string: !!binary |
26+
H4sIAAAAAAAAA4ySzU7jMBSFXyXylhRsN2nS7KIySCOhgkhZjEYocuwbxiPHrmwHARXvznULs5hV
27+
s3Hu8Xeuj38ORCvSEKF8v+IDH0ZVg2R1IYWoGZMg1uuCVeuqHgaSEzf8BRmRb5XyEAJK0oOIoHqR
28+
ZE45W9DVgrId503JG0YvKG0oRXDeq/NAKyYgjZ2Nwe5u2gv7ho4fbffr/q7bIRCiB4gMRcbqjG+v
29+
s26X3dxmxb85jnMpm47J2rXb7Oah3W5+dpu7I4NBUN+0WLzrPf7iJmm5WK54CiDdbKNPzscOy/0f
30+
ZxNesLIoV1W9TgxMQpvvlJNTCYgQYrIL7zX4fhRSm2OCE4UHphXYqAUaR2EC5GQEBV6YPorXPl3E
31+
iTwG/E97Aa9HLUXUzgbSHFLwIo1hljJdRRP9jB3Be+ex+v2UEwURU4ZTi49UG4193s61HYjB9eKc
32+
dresLqu6qsqcGGefv8QF4/xyiU+kyknUE/Tvp6NqJ0wrxdWtC31rn8FAIB/4fQIAAP//AwDow9Tp
33+
bgIAAA==
34+
headers:
35+
cache-control:
36+
- no-cache, no-store
37+
content-encoding:
38+
- gzip
39+
content-type:
40+
- application/json; charset=utf-8
41+
etag:
42+
- W/"b7d8f69ebe4ba9aee3182e54d399f72a"
43+
expires:
44+
- '0'
45+
location:
46+
- /api/v2/addresses/adr_62b2bfd8ec184caa811cea99417978bb
47+
pragma:
48+
- no-cache
49+
referrer-policy:
50+
- strict-origin-when-cross-origin
51+
strict-transport-security:
52+
- max-age=15768000; includeSubDomains; preload
53+
transfer-encoding:
54+
- chunked
55+
x-backend:
56+
- easypost
57+
x-content-type-options:
58+
- nosniff
59+
x-download-options:
60+
- noopen
61+
x-ep-request-uuid:
62+
- d6502afc60b6ba1afb32a2c100f6fc6a
63+
x-frame-options:
64+
- SAMEORIGIN
65+
x-node:
66+
- bigweb5nuq
67+
x-permitted-cross-domain-policies:
68+
- none
69+
x-proxied:
70+
- intlb1nuq 15c8815ace
71+
- extlb1nuq 15c8815ace
72+
x-request-id:
73+
- 1d5229af-27c8-4ffb-a2f9-b2fa88d35b9e
74+
x-runtime:
75+
- '0.056680'
76+
x-version-label:
77+
- easypost-202106012134-e838b9cad4-master
78+
x-xss-protection:
79+
- 1; mode=block
80+
status:
81+
code: 201
82+
message: Created
83+
version: 1

tests/test_address.py

+21
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@ def test_address_creation_with_verify():
4949
assert address.country == 'US'
5050

5151

52+
@pytest.mark.vcr()
53+
def test_address_creation_with_verify_bool():
54+
# Create an address with a verify parameter to test that it verifies accurately
55+
address = easypost.Address.create(
56+
verify=True,
57+
street1='118 2',
58+
street2='FLoor 4',
59+
city='San Francisco',
60+
state='CA',
61+
zip='94105',
62+
country='US',
63+
company='EasyPost',
64+
phone='415-456-7890'
65+
)
66+
67+
assert address.id is not None
68+
assert address.street1 == '118 2ND ST FL 4'
69+
assert address.street2 == ''
70+
assert address.country == 'US'
71+
72+
5273
@pytest.mark.vcr()
5374
def test_address_creation_with_verify_failure():
5475
# Create an address with a verify parameter to test that it fails elegantly

0 commit comments

Comments
 (0)