Skip to content

Commit 63b8625

Browse files
authored
Merge pull request #79 from maxmind/greg/wrong-body-fix
Test #78 and prepare for release
2 parents 4609192 + 1d1927f commit 63b8625

File tree

3 files changed

+64
-32
lines changed

3 files changed

+64
-32
lines changed

HISTORY.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
History
44
-------
55

6+
2.3.1 (2021-02-12)
7+
++++++++++++++++++
8+
9+
* In 2.2.0 and 2.3.0, a ``KeyError`` would be thrown if the response from the
10+
web service did not have the ``ip_address`` key but did contain the text
11+
"ip_address" in the JSON body. Reported and fixed by Justas-iDenfy. GitHub
12+
#78.
13+
614
2.3.0 (2021-02-02)
715
++++++++++++++++++
816

minfraud/webservice.py

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -65,25 +65,25 @@ def __init__(
6565

6666
def _handle_success(
6767
self,
68-
body: str,
68+
raw_body: str,
6969
uri: str,
7070
model_class: Union[Type[Factors], Type[Score], Type[Insights]],
7171
) -> Union[Score, Factors, Insights]:
7272
"""Handle successful response."""
7373
try:
74-
decoded_body = json.loads(body)
74+
decoded_body = json.loads(raw_body)
7575
except ValueError as ex:
7676
raise MinFraudError(
77-
f"Received a 200 response but could not decode the response as JSON: {body}",
77+
f"Received a 200 response but could not decode the response as JSON: {raw_body}",
7878
200,
7979
uri,
8080
) from ex
81-
if "ip_address" in body:
81+
if "ip_address" in decoded_body:
8282
decoded_body["ip_address"]["_locales"] = self._locales
8383
return model_class(decoded_body) # type: ignore
8484

8585
def _exception_for_error(
86-
self, status: int, content_type: str, body: str, uri: str
86+
self, status: int, content_type: str, raw_body: str, uri: str
8787
) -> Union[
8888
AuthenticationError,
8989
InsufficientFundsError,
@@ -94,13 +94,13 @@ def _exception_for_error(
9494
"""Returns the exception for the error responses."""
9595

9696
if 400 <= status < 500:
97-
return self._exception_for_4xx_status(status, content_type, body, uri)
97+
return self._exception_for_4xx_status(status, content_type, raw_body, uri)
9898
if 500 <= status < 600:
99-
return self._exception_for_5xx_status(status, body, uri)
100-
return self._exception_for_unexpected_status(status, body, uri)
99+
return self._exception_for_5xx_status(status, raw_body, uri)
100+
return self._exception_for_unexpected_status(status, raw_body, uri)
101101

102102
def _exception_for_4xx_status(
103-
self, status: int, content_type: str, body: str, uri: str
103+
self, status: int, content_type: str, raw_body: str, uri: str
104104
) -> Union[
105105
AuthenticationError,
106106
InsufficientFundsError,
@@ -109,36 +109,38 @@ def _exception_for_4xx_status(
109109
PermissionRequiredError,
110110
]:
111111
"""Returns exception for error responses with 4xx status codes."""
112-
if not body:
112+
if not raw_body:
113113
return HTTPError(
114-
f"Received a {status} error with no body", status, uri, body
114+
f"Received a {status} error with no body", status, uri, raw_body
115115
)
116116
if content_type.find("json") == -1:
117117
return HTTPError(
118-
f"Received a {status} with the following body: {body}",
118+
f"Received a {status} with the following body: {raw_body}",
119119
status,
120120
uri,
121-
body,
121+
raw_body,
122122
)
123123
try:
124-
decoded_body = json.loads(body)
124+
decoded_body = json.loads(raw_body)
125125
except ValueError:
126126
return HTTPError(
127-
f"Received a {status} error but it did not include the expected JSON body: {body}",
127+
f"Received a {status} error but it did not "
128+
+ f"include the expected JSON body: {raw_body}",
128129
status,
129130
uri,
130-
body,
131+
raw_body,
131132
)
132133
else:
133134
if "code" in decoded_body and "error" in decoded_body:
134135
return self._exception_for_web_service_error(
135136
decoded_body.get("error"), decoded_body.get("code"), status, uri
136137
)
137138
return HTTPError(
138-
f"Error response contains JSON but it does not specify code or error keys: {body}",
139+
"Error response contains JSON but it does not "
140+
+ f"specify code or error keys: {raw_body}",
139141
status,
140142
uri,
141-
body,
143+
raw_body,
142144
)
143145

144146
@staticmethod
@@ -168,29 +170,29 @@ def _exception_for_web_service_error(
168170
@staticmethod
169171
def _exception_for_5xx_status(
170172
status: int,
171-
body: Optional[str],
173+
raw_body: Optional[str],
172174
uri: str,
173175
) -> HTTPError:
174176
"""Returns exception for error response with 5xx status codes."""
175177
return HTTPError(
176178
f"Received a server error ({status}) for {uri}",
177179
status,
178180
uri,
179-
body,
181+
raw_body,
180182
)
181183

182184
@staticmethod
183185
def _exception_for_unexpected_status(
184186
status: int,
185-
body: Optional[str],
187+
raw_body: Optional[str],
186188
uri: str,
187189
) -> HTTPError:
188190
"""Returns exception for responses with unexpected status codes."""
189191
return HTTPError(
190192
f"Received an unexpected HTTP status ({status}) for {uri}",
191193
status,
192194
uri,
193-
body,
195+
raw_body,
194196
)
195197

196198

@@ -374,10 +376,10 @@ async def report(
374376
async with await self._do_request(uri, prepared_request) as response:
375377
status = response.status
376378
content_type = response.content_type
377-
body = await response.text()
379+
raw_body = await response.text()
378380

379381
if status != 204:
380-
raise self._exception_for_error(status, content_type, body, uri)
382+
raise self._exception_for_error(status, content_type, raw_body, uri)
381383

382384
async def _response_for(
383385
self,
@@ -392,11 +394,11 @@ async def _response_for(
392394
async with await self._do_request(uri, prepared_request) as response:
393395
status = response.status
394396
content_type = response.content_type
395-
body = await response.text()
397+
raw_body = await response.text()
396398

397399
if status != 200:
398-
raise self._exception_for_error(status, content_type, body, uri)
399-
return self._handle_success(body, uri, model_class)
400+
raise self._exception_for_error(status, content_type, raw_body, uri)
401+
return self._handle_success(raw_body, uri, model_class)
400402

401403
async def _do_request(
402404
self, uri: str, data: Dict[str, Any]
@@ -617,9 +619,9 @@ def report(self, report: Dict[str, Optional[str]], validate: bool = True) -> Non
617619
response = self._do_request(uri, prepared_request)
618620
status = response.status_code
619621
content_type = response.headers["Content-Type"]
620-
body = response.text
622+
raw_body = response.text
621623
if status != 204:
622-
raise self._exception_for_error(status, content_type, body, uri)
624+
raise self._exception_for_error(status, content_type, raw_body, uri)
623625

624626
def _response_for(
625627
self,
@@ -635,10 +637,10 @@ def _response_for(
635637
response = self._do_request(uri, prepared_request)
636638
status = response.status_code
637639
content_type = response.headers["Content-Type"]
638-
body = response.text
640+
raw_body = response.text
639641
if status != 200:
640-
raise self._exception_for_error(status, content_type, body, uri)
641-
return self._handle_success(body, uri, model_class)
642+
raise self._exception_for_error(status, content_type, raw_body, uri)
643+
return self._handle_success(raw_body, uri, model_class)
642644

643645
def _do_request(self, uri: str, data: Dict[str, Any]) -> Response:
644646
return self._session.post(

tests/test_webservice.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ def test_200_with_email_hashing(self):
239239
json.loads(httpretty.last_request.body),
240240
)
241241

242+
# This was fixed in https://github.com/maxmind/minfraud-api-python/pull/78
242243
@httprettified
243244
def test_200_with_locales(self):
244245
locales = ("fr",)
@@ -252,6 +253,27 @@ def test_200_with_locales(self):
252253
self.assertEqual("Royaume-Uni", model.ip_address.country.name)
253254
self.assertEqual("Londres", model.ip_address.city.name)
254255

256+
@httprettified
257+
def test_200_with_reserved_ip_warning(self):
258+
model = self.create_success(
259+
"""
260+
{
261+
"risk_score": 12,
262+
"id": "0e52f5ac-7690-4780-a939-173cb13ecd75",
263+
"warnings": [
264+
{
265+
"code": "IP_ADDRESS_RESERVED",
266+
"warning":
267+
"The IP address supplied is in a reserved network.",
268+
"input_pointer": "/device/ip_address"
269+
}
270+
]
271+
}
272+
"""
273+
)
274+
275+
self.assertEqual(12, model.risk_score)
276+
255277
@httprettified
256278
def test_200_with_no_body(self):
257279
with self.assertRaisesRegex(

0 commit comments

Comments
 (0)