Skip to main content

Simple validation tool for API

Project description

data-spec-validator

Why

  • To get rid of code snippet like these (... cumbersome and tedious validation)
def do_something(params):
    val_a_must_int = params.get('a', 0)
    val_b_must_be_non_empty_list = params.get('b', [])
    # if key c presents, value c must be a date string between '2000-01-01' to '2020-01-01'
    val_c_might_be_none = params.get('c', None)

    # check type
    if type(val_a_must_int) != int:
      raise XXX

    # check type & value
    if type(val_b_must_list) != list or len(val_b_must_be_non_empty_list) == 0:
      raise XXX

    # if value exists, check its value
    if val_c_might_be_none is not None:
        date_c = datetime.strptime(val_c_might_be_present, '%Y-%m-%d')
        date_20000101 = datetime.date(2000, 1, 1)
        date_20200101 = datetime.date(2020, 1, 1)
        if not (date_20000101 <= date_c <= date_20200101):
          raise XXX
    ...
    # do something actually

Installation

  • Basic usage:
pip install data-spec-validator
  • Advance usage (decorator)
    1. The decorator function dsv depends on Django & djangorestframework.
pip install data-spec-validator[decorator]

Quick Example

  • Do validate_data_spec directly wherever you like
from data_spec_validator.spec import INT, DIGIT_STR, ONE_OF, Checker, CheckerOP, validate_data_spec

class SomeSpec:
    field_a = Checker([INT])
    field_b = Checker([DIGIT_STR], optional=True)
    field_c = Checker([DIGIT_STR, INT], op=CheckerOP.ANY)

some_data = dict(field_a=4, field_b='3', field_c=1, field_dont_care=[5,6])
validate_data_spec(some_data, SomeSpec) # return True

some_data = dict(field_a=4, field_c='1')
validate_data_spec(some_data, SomeSpec) # return True

some_data = dict(field_a=4, field_c=1)
validate_data_spec(some_data, SomeSpec) # return True

some_data = dict(field_a='4', field_c='1')
validate_data_spec(some_data, SomeSpec) # raise Exception

some_data = dict(field_a='4', field_c='1')
validate_data_spec(some_data, SomeSpec, nothrow=True) # return False

class AnotherSpec:
    field = Checker([ONE_OF], ONE_OF=[1, '2', [3, 4], {'5': 6}])

another_data = dict(field=[3, 4])
validate_data_spec(another_data, AnotherSpec) # return True

another_data = dict(field='4')
validate_data_spec(another_data, AnotherSpec) # raise Exception
  • Supported checks & sample usages (see test_spec.py for more cases)
### INT
int_field = Checker([INT])

### STR
str_field = Checker([STR])

### DIGIT_STR
digi_str_field = Checker([DIGIT_STR])

### BOOL
bool_field = Checker([BOOL])

### DICT
dict_field = Checker([DICT])

### LIST
list_field = Checker([LIST])

### NONE
none_field = Checker([NONE])

### JSON
none_field = Checker([JSON])

### JSON_BOOL
none_field = Checker([JSON_BOOL])

### ONE_OF
one_of_field = Checker([ONE_OF], ONE_OF=['a', 'b', 'c'])

### SPEC
spec_field = Checker([SPEC], SPEC=SomeSpecClass)

### LIST_OF
list_of_int_field = Checker([LIST_OF], LIST_OF=INT)
list_of_spec_field = Checker([LIST_OF], LIST_OF=SomeSpecClass)

### LENGTH
length_field = Checker([LENGTH], LENGTH=dict(min=3, max=5))

### AMOUNT
amount_field = Checker([AMOUNT])

### AMOUNT_RANGE
amount_range_field = Checker([AMOUNT_RANGE], AMOUNT_RANGE=dict(min=-2.1, max=3.8))

### DECIMAL_PLACE
decimal_place_field = Checker([DECIMAL_PLACE], DECIMAL_PLACE=4)

### DATE
date_field = Checker([DATE])

### DATE_RANGE
date_range_field = Checker([DATE_RANGE], DATE_RANGE=dict(min='2000-01-01', max='2010-12-31'))

### ANY_KEY_EXISTS
any_key_checker = Checker([ANY_KEY_EXISTS], ANY_KEY_EXISTS={'key1', 'key2', 'key3'})

### KEY_COEXISTS
key1 = Checker([KEY_COEXISTS], KEY_COEXISTS=['key2'])

### EMAIL
email_field = Checker([EMAIL])

### UUID
uuid_field = Checker([UUID])

### REGEX
re_field = Checker([REGEX], REGEX=dict(pattern=r'^The'))
re_field = Checker([REGEX], REGEX=dict(pattern=r'watch out', method='match'))

### COND_EXIST
# 1. If a exists, c must not exist, if b exists, a must exist, if c exists, a must not exist.
a = Checker([COND_EXIST], optional=True, COND_EXIST=dict(WITHOUT=['c']))
b = Checker([COND_EXIST], optional=True, COND_EXIST=dict(WITH=['a']))
c = Checker([COND_EXIST], optional=True, COND_EXIST=dict(WITHOUT=['a']))
  • Decorate a method with dsv, the method must meet one of the following requirements.
    1. It's a view's member function, and the view has a WSGIRequest(django.core.handlers.wsgi.WSGIRequest) attribute.
    2. It's a view's member function, and the 2nd argument of the method is a rest_framework.request.Request instance.
    3. It's already decorated with rest_framework.decorators import api_view, the 1st argument is a rest_framework.request.Request
from rest_framework.decorators import api_view
from rest_framework.views import APIView

from data_spec_validator.decorator import dsv
from data_spec_validator.spec import UUID, EMAIL, Checker

class SomeViewSpec:
  param_a = Checker([UUID])
  param_b = Checker([EMAIL])

class SomeView(APIView):
    @dsv(SomeViewSpec)
    def get(self, request):
        pass

@api_view(('POST',))
@dsv(SomeViewSpec)
def customer_create(request):
    pass
  • Decorate another method with dsv_request_meta can help you validate the META in request header.

Register Custom Spec Check & Validator

  • Define custom CHECK constant (gt_check in this case) and write custom Validator(GreaterThanValidator in this case)
gt_check = 'gt_check'
from data_spec_validator.spec.defines import BaseValidator
class GreaterThanValidator(BaseValidator):
    name = gt_check

    @staticmethod
    def validate(value, extra, data):
        criteria = extra.get(GreaterThanValidator.name)
        return value > criteria, ValueError(f'{value} is not greater than {criteria}')
  • Register custom check & validator into data_spec_validator
from data_spec_validator.spec import custom_spec, Checker, validate_data_spec
custom_spec.register(dict(gt_check=GreaterThanValidator()))

class GreaterThanSpec:
    key = Checker(['gt_check'], GT_CHECK=10)

ok_data = dict(key=11)
validate_data_spec(ok_data, GreaterThanSpec) # return True

nok_data = dict(key=9)
validate_data_spec(ok_data, GreaterThanSpec) # raise Exception

Message Level

  • 2 modes (Default v.s. Vague), can be switched by calling reset_msg_level(vague=True)
# In default mode, any exception happens, there will be a reason in the message
"field: XXX, reason: '3' is not a integer"

# In vague mode, any exception happens, a general message is shown
"field: XXX not well-formatted"

Feature: Strict Mode

  • A spec class decorated with dsv_feature(strict=True) detects unexpected key/value in data
from data_spec_validator.spec import Checker, validate_data_spec, dsv_feature, BOOL

@dsv_feature(strict=True)
class StrictSpec:
    a = Checker([BOOL])

ok_data = dict(a=True)
validate_data_spec(ok_data, StrictSpec) # return True

nok_data = dict(a=True, b=1)
validate_data_spec(nok_data, StrictSpec) # raise Exception

Feature: Any Keys Set

  • A spec class decorated with e.g. dsv_feature(any_keys_set={...}) means that at least one key among a keys tuple from the set must exist.
from data_spec_validator.spec import Checker, validate_data_spec, dsv_feature, INT

@dsv_feature(any_keys_set={('a', 'b'), ('c', 'd')})
class _AnyKeysSetSpec:
    a = Checker([INT], optional=True)
    b = Checker([INT], optional=True)
    c = Checker([INT], optional=True)
    d = Checker([INT], optional=True)

validate_data_spec(dict(a=1, c=1, d=1), _AnyKeysSetSpec)
validate_data_spec(dict(a=1, c=1), _AnyKeysSetSpec)
validate_data_spec(dict(a=1, d=1), _AnyKeysSetSpec)
validate_data_spec(dict(b=1, c=1, d=1), _AnyKeysSetSpec)
validate_data_spec(dict(b=1, c=1), _AnyKeysSetSpec)
validate_data_spec(dict(b=1, d=1), _AnyKeysSetSpec)
validate_data_spec(dict(a=1, b=1, c=1), _AnyKeysSetSpec)
validate_data_spec(dict(a=1, b=1, d=1), _AnyKeysSetSpec)
validate_data_spec(dict(a=1, b=1, c=1, d=1), _AnyKeysSetSpec)

validate_data_spec(dict(a=1), _AnyKeysSetSpec) # raise exception
validate_data_spec(dict(b=1), _AnyKeysSetSpec) # raise exception
validate_data_spec(dict(c=1), _AnyKeysSetSpec) # raise exception
validate_data_spec(dict(d=1), _AnyKeysSetSpec) # raise exception
validate_data_spec(dict(e=1), _AnyKeysSetSpec) # raise exception

Test

python -m unittest test.test_spec

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

data-spec-validator-1.7.0.tar.gz (24.2 kB view details)

Uploaded Source

File details

Details for the file data-spec-validator-1.7.0.tar.gz.

File metadata

  • Download URL: data-spec-validator-1.7.0.tar.gz
  • Upload date:
  • Size: 24.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.2 importlib_metadata/4.10.1 pkginfo/1.8.2 requests/2.27.1 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.10.2

File hashes

Hashes for data-spec-validator-1.7.0.tar.gz
Algorithm Hash digest
SHA256 938584b5807c030b6c5de4fa84082f5eb3ea2451e8d64b3109d643bc1578171c
MD5 019dc74b24292b5c13859e066287648f
BLAKE2b-256 cd4bc87af7428bffd58f9053ac71273c9a88969ccdcd103d44d804ddec9f9875

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page