Skip to content

Commit fefd184

Browse files
trizzstyxit
authored andcommitted
Extend ValidationException (#17)
1 parent 937a9c4 commit fefd184

File tree

4 files changed

+58
-26
lines changed

4 files changed

+58
-26
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip
77
## [Unreleased]
88
[Compare 2.0.0 - Unreleased](https://github.com/exonet/exonet-api-python/compare/2.0.0...master)
99

10+
## [2.1.0](https://github.com/exonet/exonet-api-python/releases/tag/2.1.0) - 2019-11-19
11+
[Compare 2.0.0 - 2.1.0](https://github.com/exonet/exonet-api-python/compare/2.0.0...2.1.0)
12+
### Changed
13+
- Extend the `ValidationException` to contain all returned validation errors. See the [docs](./docs/error_handling.md) for more information.
14+
1015
## [2.0.0](https://github.com/exonet/exonet-api-python/releases/tag/2.0.0) - 2019-09-19
1116
[Compare 1.0.0 - 2.0.0](https://github.com/exonet/exonet-api-python/compare/1.0.0...2.0.0)
1217
### Breaking

docs/error_handling.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,28 @@ This will output the request body where the details of the error can be found:
5050
requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: https://api.exonet.nl/certificates/invalidID
5151
```
5252

53+
## Handling validation errors
54+
When there are validation errors, an extended version of the HTTPError as describe above is returned. It contains the
55+
validation errors grouped per field, accessible with the `get_failed_validations` method:
56+
57+
```python
58+
# Assuming 'record' is an already constructed variable.
59+
try:
60+
created_item = record.post()
61+
except ValidationException as err:
62+
# Print validation errors.
63+
print('{}'.format(err))
64+
validation_errors = err.get_failed_validations()
65+
for field in validation_errors:
66+
for error in validation_errors[field]:
67+
print('{}: {}'.format(field, error))
68+
except HTTPError as err:
69+
# Print all other errors.
70+
print('HTTP Error: {}'.format(err))
71+
```
72+
73+
Validation errors that are not related to a field are keyed with `generic`.
74+
5375
---
5476

5577
[Back to the index](index.md)
Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
11
from requests.exceptions import HTTPError
2+
23
"""
34
Http validation exception.
45
"""
6+
7+
58
class ValidationException(HTTPError):
69
def __init__(self, response):
710
# Collect validation errors as list of strings.
8-
validationErrors = []
11+
self.validation_errors = {}
912

1013
# Loop errors.
1114
for error in response.json()['errors']:
1215
# Handle only validation errors.
1316
if error['status'] == 422:
14-
# Use the error details as error message.
15-
errorMessage = error['detail']
16-
17-
# If the error has variables available, use those.
18-
if len(error['variables']) >= 3:
19-
errorMessage = "Field: %s, failed rule: %s(%s)." % (
20-
error['variables']['field'],
21-
error['variables']['rule'],
22-
error['variables']['rule_requirement']
23-
)
24-
# Add this error message to the list of errors.
25-
validationErrors.append(errorMessage)
26-
27-
HTTPError.__init__(self, ' '.join(validationErrors), response=response)
17+
field = 'generic'
18+
if 'field' in error['variables']:
19+
field = error['variables']['field'] or error['detail']
20+
21+
if field not in self.validation_errors:
22+
self.validation_errors[field] = []
23+
24+
self.validation_errors[field].append(error['detail'])
25+
26+
if self.validation_errors.__len__() == 1:
27+
validation_error = 'There is {} validation error.'
28+
else:
29+
validation_error = 'There are {} validation errors.'
30+
31+
HTTPError.__init__(self, validation_error.format(self.validation_errors.__len__()),
32+
response=response)
33+
34+
def get_failed_validations(self):
35+
return self.validation_errors

tests/exceptions/testValidationException.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ def test_no_errors(self):
2121
v = ValidationException(response)
2222
response.json.assert_called_once()
2323

24-
# Assert no validation exception message is set.
25-
self.assertEqual(v.args[0], '')
24+
self.assertEqual(v.args[0], 'There are 0 validation errors.')
2625

2726
def test_one_error(self):
2827
# Construct the request response.
@@ -49,10 +48,8 @@ def test_one_error(self):
4948

5049
response.json.assert_called_once()
5150
# Make sure the right message is set.
52-
self.assertEqual(
53-
v.args[0],
54-
'Field: start_date, failed rule: iso8601-date(Date must be in iso8601 format).'
55-
)
51+
self.assertEqual(v.args[0], 'There is 1 validation error.')
52+
self.assertEqual(v.get_failed_validations()['start_date'][0], 'Detailed error message')
5653

5754
def test_twoErrors(self):
5855
# Construct the request response.
@@ -88,11 +85,11 @@ def test_twoErrors(self):
8885
v = ValidationException(response)
8986

9087
response.json.assert_called_once()
88+
failed = v.get_failed_validations()
9189
# Make sure the right message is set.
92-
self.assertEqual(
93-
v.args[0],
94-
'Field: data.end_date, failed rule: Required(). The provided data is invalid.'
95-
)
90+
self.assertEqual(v.args[0], 'There are 2 validation errors.')
91+
self.assertEqual(failed['data.end_date'][0], 'The data.end_date field is required.')
92+
self.assertEqual(failed['generic'][0], 'The provided data is invalid.')
9693

9794
def test_otherErrors(self):
9895
# Construct the request response.
@@ -113,7 +110,7 @@ def test_otherErrors(self):
113110

114111
response.json.assert_called_once()
115112
# Make sure there is no validation exception message.
116-
self.assertEqual(v.args[0], '')
113+
self.assertEqual(v.args[0], 'There are 0 validation errors.')
117114

118115

119116
if __name__ == '__main__':

0 commit comments

Comments
 (0)