Skip to content

Latest commit

 

History

History
267 lines (171 loc) · 5.76 KB

README.md

File metadata and controls

267 lines (171 loc) · 5.76 KB

Validatedata

build workflow PyPI version  

An easier way to validate data in python

Installation

pip install validatedata

Usage

There are three ways to validate data:

1. Using the validate_types decorator

from validatedata import validate_types


@validate_types()
def sum(num1:int, num2:int):
    return num1 + num2

print(sum(4, 6))

# raises exception
sum('bread', 'butter')

2. Using the validate decorator

from validatedata import validate

rules = ['int', 'int']

@validate(rules)
def sum(a, b):
    return a + b

Parameters

  • rule - str or list or tuple or dict - rule(s) used to validate data. They should have the same index as the data.
  • raise_exceptions - bool - whether exceptions should be thrown when data is invalid. Default is False
  • is_class - bool - required for class methods that don't have the self parameter
  • kwds - dict - takes configuration parameters that aren't explicitly catered for, e.g log_errors and group_errors

Set log_errors to True if you wish to log unhandled errors occuring in the background i.e @validate(rules, ..., kwds={'log_errors': True})

Set group_errors to False if you wish to disable grouping of errors

When the data does not match the rules, a dictionary is returned in the following format:

{
    'errors': [[group1], [group2], ...]
}

or

{
    'errors': [error1, error2, ...]
}

3. Using the validate_data function

from validatedata import validate_data

rules = [{
    'type': 'int',
    'range': (1, 'any'),
    'range-message': 'value should be an integer greater than zero'
}, {
    'type': 'int',
    'range': (1, 'any')
}]

def sum(a, b):
    total = 0

    result = validate_data(data=[a, b], rule=rules)

    if result.ok:
        total = a + b
    
    return total

Custom messages can be set by adding a key that matches the format, {rule}-message e.g range-message, length-message, et cetera. They are recommended since the present defaults might be too generic in some circumstances

Parameters

  • data - list or str or dict - the data to be validated
  • rule - same as decorator
  • raise_exceptions - same as decorator
  • is_class - same as decorator
  • kwds - same as decorator

When the data parameter is a dict, rule should also be a dict in this format:

{'keys': OrderedDict({
    'key1': {'type':'<type>', ...},
    'key2': {'type': 'int', 'range': (5, 1000)}
})}

For Python versions >= 3.7 you can replace OrderedDict with a standard dict since they are said to maintain insertion order

 

A SimpleNamespace object with the attributes ok and errors is returned. It can be accessed like so:

result = validate_data(...)

if result.ok:
    pass # do x

else:
    errors = result.errors
    pass # do y

Types (validate and validate_data)

  • bool
  • date
  • email
  • even
  • float
  • int
  • odd
  • str
  • dict
  • list
  • regex
  • set
  • tuple
  • object

 

Rules

  • length - integer - expected length of a string, int, or object
  • contains - string or tuple of values expected in an object
  • excludes - string or tuple of values not permitted
  • options - tuple - a tuple of permitted values
  • strict - boolean indicating whether data should be type cast or not
  • expression - string - ensures data matches a given regular expression
  • type - string - specifies type of data expected. Should always be included
  • range - tuple - specifies permitted range or values. Used with numbers and dates
  • startswith - object - string, int, et cetera that a type starts with
  • endswith - object - string, int, et cetera that a type ends with

 

Examples

signup_rules = [{
    'type': 'str',
    'expression': r'^[^\d\W_]+[\w\d_-]{2,31}$',
    'expression-message': 'invalid username'
}, 
'email:msg:invalid email',
 {
    'type':'str',
    'expression':r'(?=\S*[a-z])(?=\S*[A-Z])(?=\S*\d)(?=\S*[^\w\s])\S{8,}$',
    'message':'password must contain a number, an uppercase letter, a symbol, and should be at least 8 characters long without spaces'
}]


class User:
    @validate(signup_rules, raise_exceptions=True)
    def signup(self, username, email, password):
        return "Account Created"


user = User()
response = user.signup('helterskelter', 'paddle', 'Arosebyanyname?1')
# response = user.signup('helterskelter', 'paddle@gmail.com', 'Arosebyanyname?1')
print(response)



rules = ['str:20', 'int:10', 'list:5']

rules = [{'type':'str', 'length':20}, {'type':'int', 'length':10}, {'type':'list', 'length': 5}]

rules = [{'type':'date', 'range': ('01-Jan-2021', 'any'), 'range-message':'the lowest date is 1st Jan 2021'}]



class User:

    @classmethod
    @validate(rule=['str', 'str'], is_class=True)
    def format_name(cls, firstname, lastname):
        return f'{firstname} {lastname}'

 

Additional Notes

  • The functionality of a rule depends on the type it's working upon, e.g
{'type':'int', 'range':(2, 100)} # int >= 2 and <= 100

{'type': 'str', 'range':(2, 100)} # string of variable length: len(s) >= 2 and len(s) <= 100
  • The equivalence rules equal to and not equal to aren't included but their effect can be achieved using options and excludes
{..., 'options': (200, )}

{..., 'excludes': ('Bill', ... )}
  • Object caters to custom types
class Person:
    pass

rules = [{'type':'object', 'object':Person, 'message':'Person object expected'}]

result = validate_data(data=[12], rule=rules)

# person = Person()
# result = validate_data(data=[person], rule=rules)
  • The current version does not support nested data

License

MIT