├── .gitignore ├── flask_validator ├── __init__.py ├── exceptions.py ├── error_bag.py ├── validators │ ├── __init__.py │ ├── general.py │ └── date.py └── validator_engine.py ├── requirements.txt ├── .travis.yml ├── tests ├── test_rule_spliter.py ├── __init__.py ├── test_validator_engine.py ├── test_date_validation.py └── test_validators.py ├── setup.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | .vscode 3 | __pycache__/ 4 | *.pyc 5 | .idea 6 | -------------------------------------------------------------------------------- /flask_validator/__init__.py: -------------------------------------------------------------------------------- 1 | from .validator_engine import ValidatorEngine 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==6.7 2 | Flask==1.0.2 3 | itsdangerous==0.24 4 | Jinja2==2.10 5 | MarkupSafe==1.0 6 | Werkzeug==0.14.1 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | git: 3 | depth: 3 4 | 5 | python: 6 | - '3.6' 7 | - '3.5' 8 | - '3.4' 9 | 10 | install: 11 | pip install -r requirements.txt 12 | 13 | script: python -m unittest -v 14 | -------------------------------------------------------------------------------- /flask_validator/exceptions.py: -------------------------------------------------------------------------------- 1 | class ValidatorAttributeError(Exception): 2 | def __init__(self, exp, message): 3 | self.exp = exp 4 | self.message = message 5 | 6 | 7 | class ValidatorKeyError(Exception): 8 | def __init__(self, exp, message): 9 | self.exp = exp 10 | self.message = message 11 | 12 | class ValidationArgumentError(Exception): 13 | def __init__ (self, exp, message): 14 | self.exp = exp 15 | self.message = message -------------------------------------------------------------------------------- /flask_validator/error_bag.py: -------------------------------------------------------------------------------- 1 | from flask import request, jsonify, session, redirect 2 | 3 | 4 | class ErrorBag(): 5 | def __init__(self): 6 | self.errors = {} 7 | 8 | def response(self): 9 | return jsonify( 10 | status=False, 11 | errors=self.errors 12 | ), 422 13 | 14 | def addError(self, field, message): 15 | self.errors[field] = message 16 | 17 | def hasErrors(self): 18 | return True if self.errors else False 19 | -------------------------------------------------------------------------------- /flask_validator/validators/__init__.py: -------------------------------------------------------------------------------- 1 | from .general import General 2 | from .date import Date 3 | 4 | validators = { 5 | 'required': General.required, 6 | 'max': General.max, 7 | 'min': General.min, 8 | 'alpha': General.alpha, 9 | 'alphanumeric': General.alphanumeric, 10 | 'list': General.list, 11 | 'bool': General.boolean, 12 | 'regex': General.regex, 13 | 'date': Date.date, 14 | 'date_after': Date.after, 15 | 'date_after_or_equal': Date.after_or_equal, 16 | 'date_before': Date.before, 17 | 'date_before_or_equal': Date.before_or_equal 18 | } 19 | -------------------------------------------------------------------------------- /tests/test_rule_spliter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from flask_validator.validator_engine import ValidatorEngine 3 | 4 | 5 | class TestRuleSplitter(unittest.TestCase): 6 | 7 | def testRuleOnly(self): 8 | result = ValidatorEngine().ruleSplitter('max') 9 | print(result) 10 | self.assertEqual(result[0], 'max') 11 | self.assertEqual(len(result[1]), 0) 12 | 13 | def testRuleWithSingleData(self): 14 | result = ValidatorEngine().ruleSplitter('min:12') 15 | print(result) 16 | self.assertEqual(result[0], 'min') 17 | self.assertEqual(len(result[1]), 1) 18 | 19 | def testRuleWithDoubleData(self): 20 | result = ValidatorEngine().ruleSplitter('exists:users,id') 21 | self.assertEqual(result[0], 'exists') 22 | self.assertEqual(len(result[1]), 2) 23 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify 2 | from flask_validator import ValidatorEngine 3 | 4 | app = Flask(__name__) 5 | app.config['TESTING'] = True 6 | app.config['SECRET_KEY'] = "This is test secret" 7 | validator = ValidatorEngine(app) 8 | 9 | @app.route('/index', methods=['POST']) 10 | @validator('json', { 11 | 'name': ['required', 'max:10'] 12 | }) 13 | def index(): 14 | return jsonify( 15 | status=True 16 | ),200 17 | 18 | 19 | @app.route('/exception', methods=['POST']) 20 | @validator('pro', { 21 | 'name': ['required', 'max:10', 'min:3'] 22 | }) 23 | def test_exp(): 24 | return jsonify( 25 | status=True 26 | ),200 27 | 28 | 29 | @app.route('/query', methods=['GET']) 30 | @validator('query_string', { 31 | 'name': ['required', 'max:10'] 32 | }) 33 | def query_string(): 34 | return jsonify( 35 | status=True 36 | ),200 37 | 38 | 39 | if __name__ == '__main__': 40 | app.run(debug=True) 41 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='Flask-Validator', 5 | version='1.0', 6 | url='https://github.com/adekoder/flask-validator', 7 | license='BSD', 8 | author='Ogunbiyi Ibrahim', 9 | author_email='adwumiogunbiyi@gmail.com', 10 | description='A Flask request data validator library', 11 | long_description=__doc__, 12 | #py_modules=['flask_validator'], 13 | # if you would be using a package instead use packages instead 14 | # of py_modules: 15 | packages=['flask_validator'], 16 | zip_safe=False, 17 | include_package_data=True, 18 | platforms='any', 19 | install_requires=[ 20 | 'Flask' 21 | ], 22 | classifiers=[ 23 | 'Environment :: Web Environment', 24 | 'Intended Audience :: Developers', 25 | 'License :: OSI Approved :: BSD License', 26 | 'Operating System :: OS Independent', 27 | 'Programming Language :: Python', 28 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 29 | 'Topic :: Software Development :: Libraries :: Python Modules' 30 | ] 31 | ) -------------------------------------------------------------------------------- /tests/test_validator_engine.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from flask import json, session 3 | from . import app 4 | from flask_validator.exceptions import ValidatorAttributeError 5 | 6 | class TestValidator(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.app = app 10 | self.app_context = self.app.app_context() 11 | self.app_context.push() 12 | self.client = self.app.test_client() 13 | 14 | def test_validator_with_bad_json_data(self): 15 | response = self.client.post('/index', data=json.dumps({'name': 20}), 16 | headers={ 17 | 'content-type': 'application/json' 18 | }) 19 | data = response.get_json() 20 | print(data) 21 | self.assertEqual(response.status_code, 422) 22 | self.assertEqual(data['status'], False) 23 | self.assertIn('errors', data) 24 | 25 | def test_validator_with_bad_query_string(self): 26 | response = self.client.get('/query?name="jamesbond1233"&age=23', 27 | headers={ 28 | 'content-type': 'application/json' 29 | }) 30 | data = response.get_json() 31 | print(data) 32 | self.assertEqual(response.status_code, 422) 33 | self.assertEqual(data['status'], False) 34 | self.assertIn('errors', data) 35 | 36 | def test_validator_with_good_query_string(self): 37 | response = self.client.get('/query?name=jamesbond&age=23', 38 | headers={ 39 | 'content-type': 'application/json' 40 | }) 41 | data = response.get_json() 42 | print(data) 43 | self.assertEqual(response.status_code, 200) 44 | self.assertEqual(data['status'], True) 45 | -------------------------------------------------------------------------------- /flask_validator/validator_engine.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import traceback 3 | 4 | from functools import wraps 5 | 6 | from flask import current_app, request, jsonify, session 7 | 8 | from .error_bag import ErrorBag 9 | from .validators import validators 10 | from .exceptions import ValidatorAttributeError, ValidatorKeyError 11 | 12 | class ValidatorEngine(object): 13 | 14 | def __init__(self, app=None, db=None): 15 | self.app = app 16 | if app is not None and db is not None: 17 | self.init_app(app, db) 18 | 19 | def init_app(self, app, db=None): 20 | self.app = app 21 | self.db = db 22 | 23 | def __call__(self, validation_type, rules): 24 | def wrapper(func): 25 | @wraps(func) 26 | def inner_wrapper(*args, **kwargs): 27 | try: 28 | validation_type_method = self.__getattribute__( 29 | validation_type) 30 | all_validation_passes = validation_type_method(rules) 31 | if not all_validation_passes: 32 | return self.errors.response() 33 | return func(*args, **kwargs) 34 | except AttributeError: 35 | raise ValidatorAttributeError('AttributeError',\ 36 | '''%s passed, expecting json or form_data or query_string or headers''' \ 37 | % (validation_type)) 38 | return inner_wrapper 39 | return wrapper 40 | 41 | def validate(self, data, validation_rules): 42 | self.errors = ErrorBag() 43 | for field, rules in validation_rules.items(): 44 | for rule in rules: 45 | validator_name, validator_args = self.ruleSplitter(rule) 46 | try: 47 | # validation_result = validators[validator_name](data.get(field, None),\ 48 | # validator_args[0] if len(validator_args) == 1 else validator_args) 49 | validation_result = validators[validator_name]( 50 | data.get(field, None), *validator_args 51 | ) 52 | except KeyError: 53 | raise ValidatorKeyError( 54 | validator_name, 'Built-in validator specified not known') 55 | 56 | if not validation_result['status']: 57 | self.errors.addError(field, validation_result['message']) 58 | break 59 | 60 | @staticmethod 61 | def ruleSplitter(data): 62 | rules = data.split(':') 63 | validator = rules[0] 64 | if not len(rules) > 1: 65 | return validator, [] 66 | args = rules[1].split(',') 67 | return validator, tuple(args) 68 | 69 | def json(self, rules): 70 | data = request.get_json(force=True) 71 | self.validate(data, rules) 72 | if self.errors.hasErrors(): 73 | return False 74 | return True 75 | 76 | def query_string(self, rules): 77 | data = request.args.to_dict() 78 | self.validate(data, rules) 79 | if self.errors.hasErrors(): 80 | return False 81 | return True 82 | 83 | def headers(self, rules): 84 | data = request.headers() 85 | self.validate(data, rules) 86 | if self.errors.hasErrors(): 87 | return False 88 | return True 89 | -------------------------------------------------------------------------------- /tests/test_date_validation.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from flask_validator.validators import validators 4 | from flask_validator.exceptions import ValidationArgumentError 5 | 6 | class TestDateValidators(unittest.TestCase): 7 | 8 | def test_date_with_correct_format(self): 9 | result = validators['date']('2017/02/21 12:02:23', '%Y/%m/%d %H:%M:%S') 10 | self.assertTrue(result['status']) 11 | 12 | def test_date_with_wrong_format(self): 13 | result = validators['date']('1/21/2017 31:01:23', '%Y/%m/%d %H:%M:%S') 14 | self.assertFalse(result['status']) 15 | 16 | def test_date_equal_with_correct_data(self): 17 | result = validators['date']('2017/03/02 00:01:23', *('%Y/%m/%d %H:%M:%S', '2017/03/02 00:01:23')) 18 | self.assertTrue(result['status']) 19 | 20 | def test_date_equal_with_wrong_data(self): 21 | result = validators['date']('2017/02/01 00:01:23', *('%Y/%m/%d %H:%M:%S', '2018/02/01 00:01:23')) 22 | self.assertFalse(result['status']) 23 | 24 | def test_date_after_with_correct_date(self): 25 | result = validators['date_after']('2018/03/02 00:01:23', *('%Y/%m/%d %H:%M:%S', '2017/03/02 00:01:23')) 26 | self.assertTrue(result['status']) 27 | 28 | def test_date_after_with_wrong_date(self): 29 | result = validators['date_after']('2018/03/02 00:01:23', *('%Y/%m/%d %H:%M:%S', '2019/03/02 00:01:23')) 30 | self.assertFalse(result['status']) 31 | 32 | def test_date_after_raise_error(self): 33 | self.assertRaises(ValidationArgumentError, validators['date_after'], '2018/03/02 00:01:23', *('2019/03/02 00:01:23')) 34 | 35 | def test_date_after_or_equal_with_correct_date(self): 36 | result = validators['date_after_or_equal']('2018/03/02 00:01:23', *('%Y/%m/%d %H:%M:%S', '2017/03/02 00:01:23')) 37 | self.assertTrue(result['status']) 38 | 39 | def test_date_after_or_equal_with_wrong_date(self): 40 | result = validators['date_after_or_equal']('2018/03/02 00:01:23', *('%Y/%m/%d %H:%M:%S', '2019/03/02 00:01:23')) 41 | self.assertFalse(result['status']) 42 | 43 | def test_date_after_or_equal_raise_error(self): 44 | self.assertRaises(ValidationArgumentError, validators['date_after_or_equal'], '2018/03/02 00:01:23', *('2019/03/02 00:01:23')) 45 | 46 | def test_date_before_with_correct_date(self): 47 | result = validators['date_before']('2017/03/02 00:01:23', *('%Y/%m/%d %H:%M:%S', '2018/03/02 00:01:23')) 48 | self.assertTrue(result['status']) 49 | 50 | def test_date_before_with_wrong_date(self): 51 | result = validators['date_before']('2019/03/02 00:01:23', *('%Y/%m/%d %H:%M:%S', '2018/03/02 00:01:23')) 52 | self.assertFalse(result['status']) 53 | 54 | def test_date_before_raise_error(self): 55 | self.assertRaises(ValidationArgumentError, validators['date_before'], '2018/03/02 00:01:23', *('2019/03/02 00:01:23')) 56 | 57 | def test_date_before_or_equal_with_correct_date(self): 58 | result = validators['date_before_or_equal']('2019/03/02 00:01:23', *('%Y/%m/%d %H:%M:%S', '2019/03/02 00:01:23')) 59 | self.assertTrue(result['status']) 60 | 61 | def test_date_before_or_equal_with_wrong_date(self): 62 | result = validators['date_before_or_equal']('2019/03/02 00:01:23', *('%Y/%m/%d %H:%M:%S', '2016/03/02 00:01:23')) 63 | self.assertFalse(result['status']) 64 | 65 | def test_date_before_or_equal_raise_error(self): 66 | self.assertRaises(ValidationArgumentError, validators['date_before_or_equal'], '2018/03/02 00:01:23', *('2019/03/02 00:01:23')) 67 | 68 | -------------------------------------------------------------------------------- /flask_validator/validators/general.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | class General(): 5 | @staticmethod 6 | def required(request_data, *validation_args): 7 | error_msg = 'This field is required' 8 | 9 | if request_data is None : 10 | return {'status': False, 'message': error_msg} 11 | 12 | if not isinstance(request_data, int): 13 | if len(request_data) == 0: 14 | return {'status': False, 'message': error_msg} 15 | 16 | return {'status': True} 17 | 18 | @staticmethod 19 | def max(request_data, *validator_args): 20 | error_msg = 'This field must not be greater than {args}'.format( 21 | args=validator_args[0]) 22 | if isinstance(request_data, int): 23 | if request_data > int(validator_args[0]): 24 | return { 'status': False, 'message': error_msg} 25 | else: 26 | if len(request_data) > int(validator_args[0]): 27 | return {'status': False, 'message': error_msg} 28 | return {'status': True} 29 | 30 | @staticmethod 31 | def min(request_data, *validator_args): 32 | error_msg = 'This field must not be less than {args}'.format( 33 | args=validator_args[0]) 34 | if isinstance(request_data, int): 35 | if request_data < int(validator_args[0]): 36 | return {'status': False, 'message': error_msg} 37 | else: 38 | if len(request_data) < int(validator_args[0]): 39 | return {'status': False, 'message': error_msg} 40 | return {'status': True} 41 | 42 | @staticmethod 43 | def alpha(request_data, *validator_args): 44 | error_msg = 'This field must contain on alphabets (A-Za-z)' 45 | if not request_data.isalpha() : 46 | return {'status': False, 'message': error_msg} 47 | return {'status': True} 48 | 49 | @staticmethod 50 | def alphanumeric(request_data, *validator_args): 51 | error_msg = 'This field must contain both alphabets and numbers (A-Za-z0-9)' 52 | if not request_data.isalnum(): 53 | return {'status': False, 'message': error_msg} 54 | return {'status': True} 55 | 56 | @staticmethod 57 | def list(request_data, *validator_args): 58 | print(validator_args) 59 | error_msg = 'This field must be a list' 60 | if not isinstance(request_data, list): 61 | return {'status': False, 'message': error_msg} 62 | if validator_args: 63 | if validator_args[0] and len(request_data) != validator_args[0]: 64 | error_msg += ' with length of {arg}'.format(arg=validator_args[0]) 65 | return {'status': False, 'message': error_msg} 66 | 67 | return {'status': True} 68 | 69 | @staticmethod 70 | def boolean(request_data, *validator_arg): 71 | error_msg = 'This field must be a boolean value (True/False) or (1/0)' 72 | 73 | if isinstance(request_data, bool) or (request_data == 0) or request_data == 1: 74 | return {'status': True} 75 | 76 | return {'status': False, 'message': error_msg} 77 | 78 | @staticmethod 79 | def regex(request_data, *validator_arg): 80 | error_msg = 'This field does not match required pattern' 81 | pattern = validator_arg[0] 82 | 83 | if hasattr(re, 'fullmatch'): 84 | match = re.fullmatch(pattern, request_data) 85 | elif not (pattern.startswith('^') and pattern.endswith('$')): 86 | pattern = '{}{}{}'.format('^', pattern, '$') 87 | match = re.match(pattern, request_data) 88 | else: 89 | match = re.match(pattern, request_data) 90 | 91 | return {'status': True} if match else {'status': False, 'message': error_msg} 92 | -------------------------------------------------------------------------------- /tests/test_validators.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from flask_validator.validators import validators 4 | from flask_validator.exceptions import ValidationArgumentError 5 | 6 | class TestValidators(unittest.TestCase): 7 | 8 | def test_required_with_number(self): 9 | result = validators['required'](8) 10 | self.assertTrue(result['status']) 11 | 12 | def test_required_with_string(self): 13 | result = validators['required']('jamesbond') 14 | self.assertTrue(result['status']) 15 | 16 | def test_required_with_empty_data(self): 17 | result = validators['required']('') 18 | self.assertFalse(result['status']) 19 | 20 | def test_min_with_bigger_input(self): 21 | result = validators['min'](100, 9) 22 | self.assertTrue(result['status']) 23 | 24 | def test_min_with_lesser_input(self): 25 | result = validators['min'](2, 9) 26 | self.assertFalse(result['status']) 27 | 28 | def test_max_with_bigger_input(self): 29 | result = validators['max'](100, 9) 30 | self.assertFalse(result['status']) 31 | 32 | def test_max_with_lesser_input(self): 33 | result = validators['max'](2, 9) 34 | self.assertTrue(result['status']) 35 | 36 | def test_alpha_with_wrong_data(self): 37 | result = validators['alpha']('james_1233') 38 | self.assertFalse(result['status']) 39 | 40 | def test_alpha_with_correct_data(self): 41 | result = validators['alpha']('james') 42 | self.assertTrue(result['status']) 43 | 44 | def test_alpha_with_empty_data(self): 45 | result = validators['alpha']('') 46 | self.assertFalse(result['status']) 47 | 48 | def test_alphanumeric_with_wrong_data(self): 49 | result = validators['alphanumeric']('james_1233') 50 | self.assertFalse(result['status']) 51 | 52 | def test_alphanumeric_with_correct_data(self): 53 | result = validators['alphanumeric']('james') 54 | self.assertTrue(result['status']) 55 | 56 | def test_alphanumeric_with_empty_data(self): 57 | result = validators['alphanumeric']('') 58 | self.assertFalse(result['status']) 59 | 60 | def test_list_with_wrong_data(self): 61 | result = validators['list']('james') 62 | self.assertFalse(result['status']) 63 | 64 | def test_list_with_correct_data(self): 65 | result = validators['list']([1,2,3]) 66 | self.assertTrue(result['status']) 67 | 68 | def test_list_plus_limit_with_wrong_data(self): 69 | result = validators['list']([1,2,3], 2) 70 | self.assertFalse(result['status']) 71 | self.assertEqual(result['message'], 'This field must be a list with length of 2') 72 | 73 | def test_list_plus_limit_with_corret_data(self): 74 | result = validators['list']([1,2,3], 3) 75 | self.assertTrue(result['status']) 76 | 77 | def test_bool_with_correct_data(self): 78 | result = validators['bool'](True) 79 | self.assertTrue(result['status']) 80 | 81 | def test_bool_with_wrong_data(self): 82 | result = validators['bool']('hello world') 83 | self.assertFalse(result['status']) 84 | 85 | def test_bool_with_1_and_0_data(self): 86 | result_1 = validators['bool'](1) 87 | result_2 = validators['bool'](0) 88 | self.assertTrue(result_1['status']) 89 | self.assertTrue(result_2['status']) 90 | 91 | def test_regex_with_valid_pattern(self): 92 | test_email = 'hello@email.com' 93 | test_email_pattern = r'[\w\d]+\@[\w\d]+\.[\w\d\.]+' 94 | result = validators['regex'](test_email, test_email_pattern) 95 | self.assertTrue(result['status']) 96 | 97 | def test_regex_with_invalid_data(self): 98 | test_value = 'abc-123-(__)' 99 | pattern = r'[\w\d\-]+' 100 | result = validators['regex'](test_value, pattern) 101 | self.assertFalse(result['status']) 102 | -------------------------------------------------------------------------------- /flask_validator/validators/date.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from ..exceptions import ValidationArgumentError 3 | 4 | class Date(object): 5 | 6 | 7 | 8 | @staticmethod 9 | def date(request_data, *args): 10 | error_msg = 'This field must be a date that match this format {arg}'\ 11 | .format(arg=args[0]) 12 | try: 13 | date_value_1 = datetime.strptime(request_data, args[0]) 14 | except ValueError: 15 | print('here') 16 | return {'status': False, 'message': error_msg} 17 | 18 | if len(args) == 2: 19 | date_value_2 = datetime.strptime(args[1], args[0]) 20 | if date_value_1 != date_value_2 : 21 | error_msg += 'and value must be {arg}'.format(arg=args[1]) 22 | return {'status': False, 'message': error_msg} 23 | 24 | return {'status': True} 25 | 26 | @staticmethod 27 | def after(request_data, *args): 28 | if(len(args) != 2): 29 | raise ValidationArgumentError('ArgumentError', 'Usage should be date_after:,') 30 | 31 | error_msg = 'This field must be after this date {arg}'\ 32 | .format(arg=args[0]) 33 | 34 | try: 35 | date_value_1 = datetime.strptime(request_data, args[0]) 36 | except ValueError: 37 | return {'status': False, 'message': \ 38 | 'This field must be a date that match this format'.format(arg=args[0])} 39 | 40 | date_value_2 = datetime.strptime(args[1], args[0]) 41 | if not date_value_1 > date_value_2: 42 | return {'status': False, 'message': error_msg} 43 | 44 | return {'status': True} 45 | 46 | @staticmethod 47 | def after_or_equal(request_data, *args): 48 | if(len(args) != 2): 49 | raise ValidationArgumentError('ArgumentError', 'Usage should be date_after_or_equal:,') 50 | 51 | error_msg = 'This field must be after or equal to this date {arg}'\ 52 | .format(arg=args[0]) 53 | 54 | try: 55 | date_value_1 = datetime.strptime(request_data, args[0]) 56 | except ValueError: 57 | return {'status': False, 'message': \ 58 | 'This field must be a date that match this format'.format(arg=args[0])} 59 | 60 | date_value_2 = datetime.strptime(args[1], args[0]) 61 | if not date_value_1 >= date_value_2: 62 | return {'status': False, 'message': error_msg} 63 | 64 | return {'status': True} 65 | 66 | @staticmethod 67 | def before(request_data, *args): 68 | if(len(args) != 2): 69 | raise ValidationArgumentError('ArgumentError', 'Usage should be date_before:,') 70 | 71 | error_msg = 'This field must be before this date {arg}'\ 72 | .format(arg=args[0]) 73 | 74 | try: 75 | date_value_1 = datetime.strptime(request_data, args[0]) 76 | except ValueError: 77 | return {'status': False, 'message': \ 78 | 'This field must be a date that match this format'.format(arg=args[0])} 79 | 80 | date_value_2 = datetime.strptime(args[1], args[0]) 81 | if not date_value_1 < date_value_2: 82 | return {'status': False, 'message': error_msg} 83 | 84 | return {'status': True} 85 | 86 | @staticmethod 87 | def before_or_equal(request_data, *args): 88 | if(len(args) != 2): 89 | raise ValidationArgumentError('ArgumentError', 'Usage should be date_before_or_equal:,') 90 | 91 | error_msg = 'This field must be before or equal to this date {arg}'\ 92 | .format(arg=args[0]) 93 | 94 | try: 95 | date_value_1 = datetime.strptime(request_data, args[0]) 96 | except ValueError: 97 | return {'status': False, 'message': \ 98 | 'This field must be a date that match this format'.format(arg=args[0])} 99 | 100 | date_value_2 = datetime.strptime(args[1], args[0]) 101 | if not date_value_1 <= date_value_2: 102 | return {'status': False, 'message': error_msg} 103 | 104 | return {'status': True} 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask Validator 2 | 3 | This is a validation library for flask app or api 4 | 5 | ## Installation 6 | 7 | `pip install flask-validator` 8 | 9 | ## Example (Usage) 10 | ``` 11 | from flask import Flask, jsonify 12 | from flask_validator import ValidatorEngine 13 | 14 | app = Flask(__name__) 15 | validator = ValidatorEngine(app) 16 | 17 | @app.route('/index', methods=['POST']) 18 | @validator('json', { 19 | 'name': ['required', 'maxa:10'] 20 | }) 21 | def index(): 22 | return jsonify( 23 | status=True 24 | ),200 25 | 26 | 27 | @app.route('/users/', methods=['POST']) 28 | @validator('query_string', { 29 | 'name': ['required', 'max:10', 'min:3'] 30 | }) 31 | def test_exp(name): 32 | return jsonify( 33 | status=True 34 | ),200 35 | ``` 36 | 37 | This library uses function decorator pattern, which means the incomming request data is validated before 38 | the request hit the route function 39 | 40 | ### On Error 41 | When there is a validation error, library return a 422 http response status code and a json data containing the error messages. 42 | ``` 43 | { 44 | status: false, 45 | errors: { 46 | name: ["This field is required"] 47 | } 48 | } 49 | ``` 50 | 51 | ### instantiating ValidatorEngine Class 52 | `validator = ValidatorEngine(app)` 53 | 54 | or when use using flask factory function pattern 55 | 56 | ``` 57 | validator = ValidatorEngine() 58 | validator.init_app(app) 59 | ``` 60 | 61 | ### Using the validator object 62 | Decorate your route function with the validator object like this. 63 | ``` 64 | @validator(, ) 65 | ``` 66 | 67 | so you have 68 | ``` 69 | @validator('json', { 70 | 'name': ['required', 'min:23'] 71 | }) 72 | ``` 73 | 74 | The first argument to the validator decorator is the place where you want the validator to check for incoming data 75 | "json ", "query_sting", "headers ". 76 | 77 | The second arguement is a dictionary holding the validation rules 78 | ``` 79 | { : [ ]} 80 | ``` 81 | 82 | ## Built-in Validation Rules 83 | 84 | ### `json` 85 | 86 | Using the `json` rule, the validator expects a JSON object from the client. It validates fields in JSON data. 87 | 88 | ### `query_string` 89 | The `query_string` rule validates the URL query arguments passed by the client. 90 | 91 | ### `headers` 92 | Validates the request headers 93 | 94 | ## Built-in Validation Rules Args 95 | You can specify extra argument to a rule like character limit for a parameter and required values. 96 | 97 | ### `required` 98 | Specify a required value. 99 | 100 | You can require a value in a JSON payload as follows: 101 | ``` 102 | @validator('json', { 103 | 'name': ['required', 'min:23'] 104 | }) 105 | ``` 106 | 107 | ### `max` and `min` 108 | The maximum and minimum character for a parameter 109 | ``` 110 | @validator('json', { 111 | 'phone': ['required', 'min:8', 'max:16'] 112 | }) 113 | ``` 114 | 115 | ### `alpha` 116 | This check that the input under validation contains only alphabets (A-Za-z) 117 | ``` 118 | @validator('json', { 119 | 'name': ['alpha'] 120 | }) 121 | ``` 122 | 123 | ### `alphanumeric` 124 | This check that the input under validation contains both alphabets and numbers (A-Za-z0-9) 125 | ``` 126 | @validator('json', { 127 | 'username': ['alphanumeric'] 128 | }) 129 | ``` 130 | 131 | ### `list` 132 | This check that the input under validation is a list 133 | ``` 134 | @validator('json', { 135 | 'choice': ['list'] 136 | }) 137 | ``` 138 | You can also set your validation to make sure the list is of a specific length 139 | ``` 140 | @validator('json', { 141 | 'choice': ['list:3'] 142 | }) 143 | ``` 144 | this will check test the data for list and of length 3 145 | 146 | ### `bool` 147 | This check that the input under validation is a boolean datatype (True/False) or (1/0) 148 | ``` 149 | @validator('json', { 150 | 'agreed': ['bool'] 151 | }) 152 | ``` 153 | 154 | ### `regex` 155 | The regex validator matches the _whole string_ for the regular expression pattern 156 | ``` 157 | @validator('json', { 158 | 'abc-123-XYZ': [r'regex:[\w\d\-]+'] 159 | }) 160 | ``` 161 | 162 | ### `date:` 163 | This check that the input under validation is a date that matches the `` provided. 164 | ``` 165 | @validator('json', { 166 | 'delivery_date': ['date:%Y/%m/%d %H:%M:%S'] 167 | }) 168 | ``` 169 | The format is the standard date format codes specified in the datetime library in python 170 | [check it here (date formats codes)](https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior) 171 | 172 | ### `date:,` 173 | You can also validate that the date matches the format and that date value sepecified 174 | ``` 175 | @validator('json', { 176 | 'delivery_date': ['date:%Y/%m/%d %H:%M:%S,2017/03/04 01:02:45'] 177 | }) 178 | ``` 179 | 180 | ### `date_before:,` 181 | Checks that the date matches the format and is before the date value specified 182 | ``` 183 | @validator('json', { 184 | 'delivery_date': ['date_before:%Y/%m/%d %H:%M:%S,2017/03/04 01:02:45'] 185 | }) 186 | ``` 187 | 188 | ### `date_before_or_equal:,` 189 | Checks that the date matches the format and is before or equals to the date value specified 190 | ``` 191 | @validator('json', { 192 | 'delivery_date': ['date_before_or_equal:%Y/%m/%d %H:%M:%S,2017/03/04 01:02:45'] 193 | }) 194 | ``` 195 | 196 | ### `date_after:,` 197 | Checks that the date matches the format and is after the date value specified 198 | ``` 199 | @validator('json', { 200 | 'delivery_date': ['date_after:%Y/%m/%d %H:%M:%S,2017/03/04 01:02:45'] 201 | }) 202 | ``` 203 | 204 | ### `date_after_or_equal:,` 205 | Checks that the date matches the format and is after or equals to the date value specified 206 | ``` 207 | @validator('json', { 208 | 'delivery_date': ['date_after_or_equal:%Y/%m/%d %H:%M:%S,2017/03/04 01:02:45'] 209 | }) 210 | ``` 211 | 212 | 213 | 214 | ## Contributions 215 | ... 216 | ## 217 | 218 | 219 | ### Author 220 | Created by [Adewumi Ogunbiyi](https://github.com/adekoder) 221 | --------------------------------------------------------------------------------