Skip to content

Commit

Permalink
Merge pull request #6 from Attumm/towards_version_three
Browse files Browse the repository at this point in the history
version three
  • Loading branch information
Attumm authored Jun 29, 2021
2 parents c083fdb + 4d08c96 commit 8bdf417
Show file tree
Hide file tree
Showing 10 changed files with 367 additions and 172 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ source =
omit =
setup.py
*test*
maat/version.py

7 changes: 4 additions & 3 deletions maat/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from .maat import scale, validate
from .exceptions import Invalid

from .validations import registered_functions
from .validations import types
from .validations import int_validation, str_validation, float_validation, list_validation, dict_validation

from .transformations import registered_transformation

from .extras import protected
from .version import VERSION as version

__all__ = ['scale', 'protected', 'Invalid', 'registered_functions', 'registered_transformation',
__all__ = ['scale', 'protected', 'Invalid', 'types', 'registered_transformation',
'int_validation', 'str_validation', 'float_validation', 'list_validation', 'dict_validation', 'uuid_validation'
'validate']
'validate', 'version']
126 changes: 63 additions & 63 deletions maat/maat.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,24 @@
from .validations import registered_functions
from .validations import types
from .transformations import registered_transformation
from .exceptions import Invalid

NESTED = "nested"
TYPELIST = "list"
VALIDATOR = "type"
OPTIONAL = "optional"
NULLABLE = "null_able"
TYPEASOARR = "aso_array"
SKIPFAILED = "skip_failed"
TYPELISTDICTS = "list_dicts"
NULLABLE = "nullable"
DEFAULT = "default"
EMPTYLIST = "empty_list"
TRANSFORM = "transform"
PRETRANSFORM = "pre_transform"
SKIPFAILED = "skip_failed"

special_arguments = {VALIDATOR, NESTED, TYPELIST, TYPEASOARR, SKIPFAILED, NULLABLE, OPTIONAL, DEFAULT, PRETRANSFORM,
TRANSFORM, TYPELISTDICTS, EMPTYLIST}


def get_validation_func(item):
try:
return registered_functions[item[VALIDATOR]]
except KeyError:
raise Invalid(f"{item.get(VALIDATOR)} is not registered as type")

TYPE = "type"
TYPELIST = "list"
TYPEDICT = "dict"
TYPEASOARR = "aso_array"
TYPELISTDICTS = "list_dicts"

def get_validation_args(item):
return {k: v for k, v, in item.items() if k not in special_arguments}
special_arguments = {NESTED, TYPELIST, TYPEASOARR, SKIPFAILED, NULLABLE, OPTIONAL, DEFAULT, PRETRANSFORM,
TRANSFORM, TYPELISTDICTS, EMPTYLIST, TYPEDICT, TYPE}


def get_transformation_func(item, type_transformation):
Expand Down Expand Up @@ -64,7 +55,6 @@ def scale(input_dict, counter_dict):

validated_items = {}
for key, item in counter_dict.items():

try:
val = input_dict[key]
except KeyError:
Expand All @@ -76,40 +66,47 @@ def scale(input_dict, counter_dict):
else:
raise Invalid(f'key:"{key}" is not set')

# # if the value is None, check for default value or check if it was required
# if the value is None check if value is allowed to be None
if val is None and item.get(NULLABLE):
validated_items[key] = None
continue

try:
validation_func = registered_functions[item[VALIDATOR]]
validation_func = types[item[TYPE]]
except KeyError:
raise Invalid(f"{item.get(VALIDATOR)} is not registered as type")

validation_args = {k: v for k, v, in item.items() if k not in special_arguments}
raise Invalid(f"{item.get(TYPE)} is not registered as type")

pre_transformation = get_transformation_func(item, PRETRANSFORM)
post_transformation = get_transformation_func(item, TRANSFORM)

# the validation can be done on top level, life is good
if NESTED not in item:
validated_items[key] = post_transformation(validation_func(key=key, val=pre_transformation(val), **validation_args))
validated_items[key] = post_transformation(validation_func(key=key, val=pre_transformation(val), **item))
continue

type_ = item["type"]
if type_ == TYPEDICT:
validation_func(key=key, val=val, **item)
validated_items[key] = post_transformation(scale(pre_transformation(input_dict[key]), counter_dict[key][NESTED]))

elif TYPELIST in item:
elif type_ == TYPELIST:
validation_func(key=key, val=val, **item)
validated_items[key] = item.get(EMPTYLIST, []) if len(val) == 0 else []

try:
validation_func = types[item[NESTED][TYPE]]
except KeyError:
raise Invalid(f"{item[NESTED].get(TYPE)} is not registered as type")

for nested_item in val:
# within a list a item should be skipable
try:
validated_items[key].append(post_transformation(validation_func(key=key, val=pre_transformation(nested_item), **validation_args)))
validated_items[key].append(post_transformation(validation_func(key=key, val=pre_transformation(nested_item), **item[NESTED])))
except Invalid:
if not item.get(SKIPFAILED):
raise

# the item is nested with a list of dictionary items
elif TYPELISTDICTS in item:

validation_func(key=key, val=val, **validation_args)
elif type_ == TYPELISTDICTS:
validation_func(key=key, val=val, **item)
validated_items[key] = []
for nested_item in val:
try:
Expand All @@ -118,22 +115,20 @@ def scale(input_dict, counter_dict):
if not item.get(SKIPFAILED):
raise

# the item is nested. we have to start over to do the same the one level deeper
elif not item.get(TYPEASOARR, False):
validated_items[key] = post_transformation(scale(pre_transformation(input_dict[key]), counter_dict[key][NESTED]))

# the item is a "associative array" dictionary e.g keys are numerical indexes
else:
validation_func(key=key, val=val, **validation_args)
elif type_ == TYPEASOARR:
validation_func(key=key, val=val, **item)

for nested_key, nested_val in val.items():
# make sure dictionary is present.
# TODO this could be nicer
if key not in validated_items:
validated_items[key] = {}
if nested_key not in validated_items[key]:
validated_items[key][nested_key] = {}

validated_items[key][nested_key] = scale(nested_val, counter_dict[key][NESTED])
validated_items[key][nested_key] = validate(nested_val, counter_dict[key][NESTED])

else:
raise Invalid(f"type {type_} can't handle nested structures, use {TYPEASOARR}, {TYPELISTDICTS}, {TYPEDICT} instead")

return validated_items

Expand All @@ -156,37 +151,44 @@ def validate(input_dict, counter_dict):
else:
raise Invalid(f'key:"{key}" is not set')

# # if the value is None, check for default value or check if it was required
# if the value is None check if value is allowed to be None
if val is None and item.get(NULLABLE):
validated_items[key] = None
continue

try:
validation_func = registered_functions[item[VALIDATOR]]
validation_func = types[item[TYPE]]
except KeyError:
raise Invalid(f"{item.get(VALIDATOR)} is not registered as type")

validation_args = {k: v for k, v, in item.items() if k not in special_arguments}
raise Invalid(f"{item.get(TYPE)} is not registered as type")

# the validation can be done on top level, life is good
if NESTED not in item:
validated_items[key] = validation_func(key=key, val=val, **validation_args)
validated_items[key] = validation_func(key=key, val=val, **item)
continue

elif TYPELIST in item:
type_ = item["type"]
if type_ == TYPEDICT:
validation_func(key=key, val=val, **item)
validated_items[key] = validate(input_dict[key], counter_dict[key][NESTED])

elif type_ == TYPELIST:
validation_func(key=key, val=val, **item)
validated_items[key] = item.get(EMPTYLIST, []) if len(val) == 0 else []

try:
validation_func = types[item[NESTED][TYPE]]
except KeyError:
raise Invalid(f"{item[NESTED].get(TYPE)} is not registered as type")

for nested_item in val:
# within a list a item should be skipable
try:
validated_items[key].append(validation_func(key=key, val=nested_item, **validation_args))
validated_items[key].append(validation_func(key=key, val=nested_item, **item[NESTED])) # ADD test item nested
except Invalid:
if not item.get(SKIPFAILED):
raise

# the item is nested with a list of dictionary items
elif TYPELISTDICTS in item:

validation_func(key=key, val=val, **validation_args)
elif type_ == TYPELISTDICTS:
validation_func(key=key, val=val, **item)
validated_items[key] = []
for nested_item in val:
try:
Expand All @@ -195,21 +197,19 @@ def validate(input_dict, counter_dict):
if not item.get(SKIPFAILED):
raise

# the item is nested. we have to start over to do the same the one level deeper
elif not item.get(TYPEASOARR, False):
validated_items[key] = validate(input_dict[key], counter_dict[key][NESTED])

# the item is a "associative array" dictionary e.g keys are numerical indexes
else:
validation_func(key=key, val=val, **validation_args)
elif type_ == TYPEASOARR:
validation_func(key=key, val=val, **item)

for nested_key, nested_val in val.items():
# make sure dictionary is present.
# TODO this could be nicer
if key not in validated_items:
validated_items[key] = {}
if nested_key not in validated_items[key]:
validated_items[key][nested_key] = {}

validated_items[key][nested_key] = validate(nested_val, counter_dict[key][NESTED])

else:
raise Invalid(f"type {type_} can't handle nested structures, use {TYPEASOARR}, {TYPELISTDICTS}, {TYPEDICT} instead")

return validated_items
28 changes: 17 additions & 11 deletions maat/validations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
from .exceptions import Invalid


def str_validation(val, key=None, min_length=None, max_length=None, regex=None, choices=None, cast=None):
def str_validation(val, key=None, min_length=None, max_length=None, regex=None, choices=None, cast=None, *args, **kwargs):
if cast:
try:
val = str(val)
except (ValueError, TypeError):
raise Invalid(f'key: "{key}" contains invalid item "{type(val).__name__}": unable to convert from type "{val}" to str')
raise Invalid(f'key: "{key}" contains invalid item "{type(val).__name__}": unable to convert from type to str')

if not isinstance(val, str):
raise Invalid(f'key: "{key}" contains invalid item "{val}" with type "{type(val).__name__}": not of type string')
Expand All @@ -31,7 +31,7 @@ def str_validation(val, key=None, min_length=None, max_length=None, regex=None,
return val


def int_validation(val, key=None, min_amount=None, max_amount=None, cast=None):
def int_validation(val, key=None, min_amount=None, max_amount=None, cast=None, *args, **kwargs):
if cast:
try:
val = int(val)
Expand All @@ -50,7 +50,7 @@ def int_validation(val, key=None, min_amount=None, max_amount=None, cast=None):
return val


def float_validation(val, key=None, min_amount=None, max_amount=None, cast=None):
def float_validation(val, key=None, min_amount=None, max_amount=None, cast=None, *args, **kwargs):
if cast:
try:
val = float(val)
Expand All @@ -69,7 +69,7 @@ def float_validation(val, key=None, min_amount=None, max_amount=None, cast=None)
return val


def list_validation(val, key=None, min_amount=None, max_amount=None):
def list_validation(val, key=None, min_amount=None, max_amount=None, *args, **kwargs):
if not isinstance(val, list):
raise Invalid(f'key: "{key}" contains invalid item "{val}" with type "{type(val).__name__}": not of type list')

Expand All @@ -82,23 +82,27 @@ def list_validation(val, key=None, min_amount=None, max_amount=None):
return val


def dict_validation(val, key=None, min_amount=None, max_amount=None, key_min=None, key_max=None, key_regex=None):
def dict_validation(val, key=None, min_amount=None, max_amount=None, key_min=None, key_max=None, key_regex=None, *args, **kwargs):
if not isinstance(val, dict):
raise Invalid(f'"{key}": is not a dictionary')

if min_amount is not None and len(val) < min_amount:
raise Invalid(f'key: "{key}" contains invalid item "{val}": contains less then minimal amount of {min_amount}')
raise Invalid(f'key: "{key}" contains invalid item "{val}": {len(val)} contains less then minimal amount of {min_amount}')

if max_amount is not None and len(val) > max_amount:
raise Invalid(f'key: "{key}" contains invalid item "{val}": contains more then maximum amount of {max_amount}')
raise Invalid(f'key: "{key}" contains invalid item "{val}": {len(val)} contains more then maximum amount of {max_amount}')

if key_regex is not None and not all(bool(re.match(key_regex, str(i))) for i in val.keys()):
raise Invalid(f'{key}: has dictionary key that does not adhere to regex {key_regex}')
for i in val.keys():
if not re.match(key_regex, str(i)):
failed = str(i)
break
raise Invalid(f'{key}: has dictionary key "{failed}" that does not adhere to regex {key_regex}')

return val


def uuid_validation(val, key=None):
def uuid_validation(val, key=None, *args, **kwargs):
try:
_ = UUID(val, version=4)
except (ValueError, AttributeError, TypeError):
Expand All @@ -107,11 +111,13 @@ def uuid_validation(val, key=None):
return val


registered_functions = {
types = {
'int': int_validation,
'str': str_validation,
'float': float_validation,
'uuid': uuid_validation,
'list': list_validation,
'list_dicts': list_validation,
'dict': dict_validation,
'aso_array': dict_validation,
}
1 change: 1 addition & 0 deletions maat/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VERSION = "3.0.0"
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
name='Maat',
author='Melvin Bijman',
author_email='[email protected]',
version='2.0.0',
version='3.0.0',
license='MIT',

py_modules=['maat'],
packages=['maat'],

platforms=['any'],

description='Validate like Maat',
description='Validate like a Maat',
long_description=long_description,
long_description_content_type='text/markdown',

Expand Down
Loading

0 comments on commit 8bdf417

Please sign in to comment.