Skip to content

Commit a9e7057

Browse files
committed
Merge pull request #3 from maxmind/greg/clean-request
Copy request dict and remove keys with None values
2 parents 576480f + b433a4a commit a9e7057

File tree

3 files changed

+45
-9
lines changed

3 files changed

+45
-9
lines changed

minfraud/validation.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,9 @@ def _uri(s):
214214
'last_4_digits': _credit_card_last_4,
215215
},
216216
Required('device'): {
217-
'accept_language': _unicode_or_printable_ascii, Required('ip_address'):
218-
_ip_address, 'user_agent': _unicode_or_printable_ascii
217+
'accept_language': _unicode_or_printable_ascii,
218+
Required('ip_address'): _ip_address,
219+
'user_agent': _unicode_or_printable_ascii
219220
},
220221
'email': {'address': _email_or_md5,
221222
'domain': _hostname, },

minfraud/webservice.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,16 +93,17 @@ def score(self, transaction, validate=True):
9393

9494
def _response_for(self, path, model_class, request, validate):
9595
"""Send request and create response object"""
96+
cleaned_request = self._copy_and_clean(request)
9697
if validate:
9798
try:
98-
validate_transaction(request)
99+
validate_transaction(cleaned_request)
99100
except MultipleInvalid as ex:
100101
raise InvalidRequestError(
101102
"Invalid transaction data: {0}".format(ex))
102103
uri = '/'.join([self._base_uri, path])
103104
response = requests.post(
104105
uri,
105-
json=request,
106+
json=cleaned_request,
106107
auth=(self._user_id, self._license_key),
107108
headers=
108109
{'Accept': 'application/json',
@@ -113,6 +114,16 @@ def _response_for(self, path, model_class, request, validate):
113114
else:
114115
self._handle_error(response, uri)
115116

117+
def _copy_and_clean(self, data):
118+
"""This returns a copy of the data structure with Nones removed"""
119+
if isinstance(data, dict):
120+
return dict((k, self._copy_and_clean(v)) for (k, v) in data.items()
121+
if v is not None)
122+
elif isinstance(data, (list, set, tuple)):
123+
return [self._copy_and_clean(x) for x in data if x is not None]
124+
else:
125+
return data
126+
116127
def _user_agent(self):
117128
"""Create User-Agent header"""
118129
return 'minFraud-API/%s %s' % (__version__, default_user_agent())
@@ -154,9 +165,9 @@ def _handle_4xx_status(self, response, status, uri):
154165
except ValueError:
155166
raise HTTPError(
156167
'Received a {status:d} error but it did not include'
157-
' the expected JSON body: {content}'
158-
.format(status=status,
159-
content=response.content), status, uri)
168+
' the expected JSON body: {content}'.format(
169+
status=status,
170+
content=response.content), status, uri)
160171
else:
161172
if 'code' in body and 'error' in body:
162173
self._handle_web_service_error(body.get('error'),

tests/test_webservice.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ def test_200(self):
4141
if self.type == 'insights':
4242
self.assertEqual('United Kingdom', model.ip_address.country.name)
4343

44+
def test_200_on_request_with_nones(self):
45+
model = self.create_success(
46+
request={
47+
'device': {
48+
'ip_address': '81.2.69.160',
49+
'accept_language': None
50+
},
51+
'event': {
52+
'shop_id': None
53+
},
54+
'shopping_cart': [{
55+
'category': None,
56+
'quantity': 2,
57+
}, None],
58+
})
59+
response = self.response
60+
self.assertEqual(0.01, model.risk_score)
61+
4462
def test_200_with_locales(self):
4563
locales = ('fr', )
4664
client = Client(42, 'abcdef123456', locales=locales)
@@ -142,7 +160,11 @@ def create_error(self, mock, status_code=400, text='', headers=None):
142160
return getattr(self.client, self.type)(self.full_request)
143161

144162
@requests_mock.mock()
145-
def create_success(self, mock, text=None, headers=None, client=None):
163+
def create_success(self, mock,
164+
text=None,
165+
headers=None,
166+
client=None,
167+
request=None):
146168
if headers is None:
147169
headers = {
148170
'Content-Type':
@@ -158,7 +180,9 @@ def create_success(self, mock, text=None, headers=None, client=None):
158180
headers=headers)
159181
if client is None:
160182
client = self.client
161-
return getattr(client, self.type)(self.full_request)
183+
if request is None:
184+
request = self.full_request
185+
return getattr(client, self.type)(request)
162186

163187

164188
class TestInsights(BaseTest, unittest.TestCase):

0 commit comments

Comments
 (0)