From 1a45103fcdd565398557690a17b81e6f2a4e238f Mon Sep 17 00:00:00 2001 From: Peter Lightbody Date: Wed, 16 Sep 2020 19:24:44 +0100 Subject: [PATCH 1/6] Allow deferred error handling --- flask_sieve/__init__.py | 7 ++++++- tests/test_sieve.py | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/flask_sieve/__init__.py b/flask_sieve/__init__.py index 15c2d06..12a23f2 100644 --- a/flask_sieve/__init__.py +++ b/flask_sieve/__init__.py @@ -2,6 +2,11 @@ from .validator import validate, Validator from .exceptions import ValidationException, register_error_handler + class Sieve: - def __init__(self, app): + def __init__(self, app=None): + if app is not None: + self.init_app(app) + + def init_app(self, app): register_error_handler(app) diff --git a/tests/test_sieve.py b/tests/test_sieve.py index 11779b1..1b7978c 100644 --- a/tests/test_sieve.py +++ b/tests/test_sieve.py @@ -19,3 +19,19 @@ def test_registers_error_handler(self): ) self.assertEqual(400, status) self.assertIn('Test error', str(response.get_json())) + + def test_deferring_registration_of_error_handler(self): + app = Flask(__name__) + s = Sieve() + + s.init_app(app) + + self.assertIn(ValidationException, app.error_handler_spec[None][None]) + errors = {'field': 'Test error'} + + with app.app_context(): + response, status = app.error_handler_spec[None][None][ValidationException]( + ValidationException(errors) + ) + self.assertEqual(400, status) + self.assertIn('Test error', str(response.get_json())) From e34c911f5af12289ef9b1da57c80328b3f673b49 Mon Sep 17 00:00:00 2001 From: Peter Lightbody Date: Thu, 17 Sep 2020 13:00:17 +0100 Subject: [PATCH 2/6] Adds configurable status code --- flask_sieve/exceptions.py | 2 +- tests/test_exceptions.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/flask_sieve/exceptions.py b/flask_sieve/exceptions.py index 99f4f28..1553f2f 100644 --- a/flask_sieve/exceptions.py +++ b/flask_sieve/exceptions.py @@ -11,7 +11,7 @@ def validations_error_handler(ex): 'success': False, 'message': 'Validation error', 'errors': ex.errors - }), 400 + }), app.config.get('SIEVE_INVALID_STATUS_CODE', 400) app.register_error_handler( ValidationException, validations_error_handler diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 1929ecf..3a07660 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -18,3 +18,17 @@ def test_error_handler(self): ) self.assertEqual(400, status) self.assertIn('Test error', str(response.get_json())) + + def test_configurable_status_code(self): + app = Flask(__name__) + app.config['SIEVE_INVALID_STATUS_CODE'] = 422; + register_error_handler(app) + self.assertIn(ValidationException, app.error_handler_spec[None][None]) + errors = {'field': 'Test error'} + + with app.app_context(): + response, status = app.error_handler_spec[None][None][ValidationException]( + ValidationException(errors) + ) + self.assertEqual(422, status) + self.assertIn('Test error', str(response.get_json())) From 2c01e97b3e1fba1d25349746782f548dc85c98f6 Mon Sep 17 00:00:00 2001 From: Peter Lightbody Date: Thu, 17 Sep 2020 14:00:00 +0100 Subject: [PATCH 3/6] convert method to function --- flask_sieve/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask_sieve/__init__.py b/flask_sieve/__init__.py index 12a23f2..4836e1f 100644 --- a/flask_sieve/__init__.py +++ b/flask_sieve/__init__.py @@ -8,5 +8,6 @@ def __init__(self, app=None): if app is not None: self.init_app(app) - def init_app(self, app): + @staticmethod + def init_app(app): register_error_handler(app) From 3f085246eedce56a9eda851b5d0998af2d9c150e Mon Sep 17 00:00:00 2001 From: Peter Lightbody Date: Thu, 17 Sep 2020 14:00:23 +0100 Subject: [PATCH 4/6] Adds and tests default configuration options --- flask_sieve/exceptions.py | 17 ++++++--- tests/test_exceptions.py | 78 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/flask_sieve/exceptions.py b/flask_sieve/exceptions.py index 1553f2f..0e0a830 100644 --- a/flask_sieve/exceptions.py +++ b/flask_sieve/exceptions.py @@ -1,5 +1,6 @@ from flask import jsonify + class ValidationException(Exception): def __init__(self, errors): self.errors = errors @@ -7,13 +8,19 @@ def __init__(self, errors): def register_error_handler(app): def validations_error_handler(ex): - return jsonify({ - 'success': False, - 'message': 'Validation error', + response = { + 'message': app.config.get('SIEVE_DEFAULT_RESPONSE_MESSAGE', 'Validation error'), 'errors': ex.errors - }), app.config.get('SIEVE_INVALID_STATUS_CODE', 400) + } + + if app.config.get('SIEVE_INCLUDE_SUCCESS_KEY'): + response['success'] = False + + if app.config.get('SIEVE_RESPONSE_WRAPPER'): + response = {app.config.get('SIEVE_RESPONSE_WRAPPER'): response} + + return jsonify(response), app.config.get('SIEVE_INVALID_STATUS_CODE', 400) app.register_error_handler( ValidationException, validations_error_handler ) - diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 3a07660..5e99c3b 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -21,7 +21,7 @@ def test_error_handler(self): def test_configurable_status_code(self): app = Flask(__name__) - app.config['SIEVE_INVALID_STATUS_CODE'] = 422; + app.config['SIEVE_INVALID_STATUS_CODE'] = 422 register_error_handler(app) self.assertIn(ValidationException, app.error_handler_spec[None][None]) errors = {'field': 'Test error'} @@ -32,3 +32,79 @@ def test_configurable_status_code(self): ) self.assertEqual(422, status) self.assertIn('Test error', str(response.get_json())) + + def test_configuring_response_message(self): + app = Flask(__name__) + msg = "Only Chuck Norris can submit invalid data!" + app.config['SIEVE_DEFAULT_RESPONSE_MESSAGE'] = msg + register_error_handler(app) + self.assertIn(ValidationException, app.error_handler_spec[None][None]) + errors = {'field': 'Test error'} + + with app.app_context(): + response, status = app.error_handler_spec[None][None][ValidationException]( + ValidationException(errors) + ) + self.assertEqual(400, status) + self.assertIn(msg, str(response.get_json())) + + def test_keeping_success_message(self): + app = Flask(__name__) + app.config['SIEVE_INCLUDE_SUCCESS_KEY'] = True + + register_error_handler(app) + self.assertIn(ValidationException, app.error_handler_spec[None][None]) + errors = {'field': 'Test error'} + + with app.app_context(): + response, status = app.error_handler_spec[None][None][ValidationException]( + ValidationException(errors) + ) + self.assertEqual(400, status) + self.assertTrue('success' in response.get_json()) + + def test_keeping_removing_message(self): + app = Flask(__name__) + app.config['SIEVE_INCLUDE_SUCCESS_KEY'] = False + + register_error_handler(app) + self.assertIn(ValidationException, app.error_handler_spec[None][None]) + errors = {'field': 'Test error'} + + with app.app_context(): + response, status = app.error_handler_spec[None][None][ValidationException]( + ValidationException(errors) + ) + self.assertEqual(400, status) + self.assertFalse('success' in response.get_json()) + + def test_wrapping_response_with_data(self): + app = Flask(__name__) + app.config['SIEVE_RESPONSE_WRAPPER'] = 'data' + + register_error_handler(app) + self.assertIn(ValidationException, app.error_handler_spec[None][None]) + errors = {'field': 'Test error'} + + with app.app_context(): + response, status = app.error_handler_spec[None][None][ValidationException]( + ValidationException(errors) + ) + self.assertEqual(400, status) + self.assertIn('Test error', str(response.get_json())) + self.assertTrue('data' in response.get_json()) + + def test_wrapping_response_with_data(self): + app = Flask(__name__) + register_error_handler(app) + self.assertIn(ValidationException, app.error_handler_spec[None][None]) + errors = {'field': 'Test error'} + + with app.app_context(): + response, status = app.error_handler_spec[None][None][ValidationException]( + ValidationException(errors) + ) + self.assertEqual(400, status) + self.assertIn('Test error', str(response.get_json())) + self.assertFalse('data' in response.get_json()) + self.assertTrue('errors' in response.get_json()) From d48fa18faa0cd6540da86fa35a1b3d435924e103 Mon Sep 17 00:00:00 2001 From: Peter Lightbody Date: Thu, 17 Sep 2020 14:19:37 +0100 Subject: [PATCH 5/6] Add config options to docs --- docs/source/index.md | 19 +++++++++++++++++-- flask_sieve/exceptions.py | 3 ++- tests/test_exceptions.py | 4 ++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/source/index.md b/docs/source/index.md index 701a07b..3d34837 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -165,7 +165,7 @@ class PostRequest(JsonRequest): ### Error Messages Format -In case validation fails to pass, the following is the format of the generated response: +In case validation fails to pass, the following is the default format of the generated response: ```js { success: False, @@ -181,7 +181,22 @@ In case validation fails to pass, the following is the format of the generated r } } ``` -All validation error messages will have a HTTP error status code 400. + +This format can be configured using the following flask configurations + + Flask Config Key | Description + :---------------: | :---------- + SIEVE_RESPONSE_MESSAGE | Set this to modify your default error message e.g "Invalid Input" + SIEVE_INCLUDE_SUCCESS_KEY | Set this to False to remove the success key from the response + SIEVE_RESPONSE_WRAPPER | Set this to wrap your response e.g. `data` + + +### Response Status Code + +By default, all validation error messages will have a HTTP error status code 400. This can be configured by setting the flask config `SIEVE_INVALID_STATUS_CODE`. +```python +app.config['SIEVE_INVALID_STATUS_CODE'] = 422 +``` ### Stopping on First Validation Failure diff --git a/flask_sieve/exceptions.py b/flask_sieve/exceptions.py index 0e0a830..d5c6dc7 100644 --- a/flask_sieve/exceptions.py +++ b/flask_sieve/exceptions.py @@ -9,7 +9,7 @@ def __init__(self, errors): def register_error_handler(app): def validations_error_handler(ex): response = { - 'message': app.config.get('SIEVE_DEFAULT_RESPONSE_MESSAGE', 'Validation error'), + 'message': app.config.get('SIEVE_RESPONSE_MESSAGE', 'Validation error'), 'errors': ex.errors } @@ -20,6 +20,7 @@ def validations_error_handler(ex): response = {app.config.get('SIEVE_RESPONSE_WRAPPER'): response} return jsonify(response), app.config.get('SIEVE_INVALID_STATUS_CODE', 400) + app.register_error_handler( ValidationException, validations_error_handler diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 5e99c3b..a5abf83 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -36,7 +36,7 @@ def test_configurable_status_code(self): def test_configuring_response_message(self): app = Flask(__name__) msg = "Only Chuck Norris can submit invalid data!" - app.config['SIEVE_DEFAULT_RESPONSE_MESSAGE'] = msg + app.config['SIEVE_RESPONSE_MESSAGE'] = msg register_error_handler(app) self.assertIn(ValidationException, app.error_handler_spec[None][None]) errors = {'field': 'Test error'} @@ -94,7 +94,7 @@ def test_wrapping_response_with_data(self): self.assertIn('Test error', str(response.get_json())) self.assertTrue('data' in response.get_json()) - def test_wrapping_response_with_data(self): + def test_wrapping_response_without_data(self): app = Flask(__name__) register_error_handler(app) self.assertIn(ValidationException, app.error_handler_spec[None][None]) From 3cb210f0176bdeaaf96bb649295be9b03bcfa530 Mon Sep 17 00:00:00 2001 From: Peter Lightbody Date: Thu, 17 Sep 2020 14:26:52 +0100 Subject: [PATCH 6/6] fix md table in docs --- docs/source/index.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/source/index.md b/docs/source/index.md index 3d34837..fedf4c7 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -182,13 +182,10 @@ In case validation fails to pass, the following is the default format of the gen } ``` -This format can be configured using the following flask configurations - - Flask Config Key | Description - :---------------: | :---------- - SIEVE_RESPONSE_MESSAGE | Set this to modify your default error message e.g "Invalid Input" - SIEVE_INCLUDE_SUCCESS_KEY | Set this to False to remove the success key from the response - SIEVE_RESPONSE_WRAPPER | Set this to wrap your response e.g. `data` +This format can be configured using the following flask configurations: +- `SIEVE_RESPONSE_MESSAGE` - Set this to modify your default error message e.g "Invalid Input". +- `SIEVE_INCLUDE_SUCCESS_KEY` - Set this to False to remove the success key from the response. +- `SIEVE_RESPONSE_WRAPPER` - Set this to wrap your response e.g. `data`. ### Response Status Code