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

json_field = Checker([JSON])

JSON_BOOL

json_bool_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: Enforce LIST type validation as well

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 (postpone deprecation, target to remove in 1.9.0)

Use @dsv_feature(any_keys_set={...}) instead, see below

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

KEY_COEXISTS (postpone deprecation, target to remove in 1.9.0)

Use COND_EXIST instead, see below

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

If a exists, c must not exist, if b exists, a must exist, if c exists, a must not exist.

Practically, optional=True will be configured in the most use cases, FMI, see test/test_spec.py

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.8.0.tar.gz (25.8 kB view details)

Uploaded Source

File details

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

File metadata

  • Download URL: data-spec-validator-1.8.0.tar.gz
  • Upload date:
  • Size: 25.8 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.8.0.tar.gz
Algorithm Hash digest
SHA256 3c052817ac30488e384b2be4d6d4e9a74ae988edc8e2d776863ed99cc4b73fd4
MD5 57c59653045977f1c6366e9efe847192
BLAKE2b-256 9aea76936d1c0729ab209ed26686125674a2599f2123769475aebf7ac55425dc

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