Module lib.repository.validator
Contains all the validation logic for the backend
Expand source code
"""
Contains all the validation logic for the backend
"""
import re
from abc import abstractmethod
class ValidationException(Exception):
"""
Thrown when a validation rule is violated
"""
def __init__(self, message, database_field, *args):
super().__init__(message, *args)
self.database_field = database_field
class HasValidation:
"""
Trait that adds validation to Models
"""
def __setattr__(self, key, value):
"""
This overrides when 'myobject.mykey' is set when the 'mykey' attribute doesn't exist
This will run the value being set through a validator
:param key: field to set
:param value: value to set to field
:return: None
"""
if key == 'data':
for data_key, data_value in value.items():
if not self.validate(data_key, data_value):
raise ValidationException(f'{data_key} given is invalid', data_key)
super().__setattr__(key, value)
else:
if self.validate(key, value):
super().__setattr__(key, value)
else:
raise ValidationException(f'{key} given is invalid', key)
def validate(self, key, new_value):
"""
Validates a property using the field_validators dict
:param key: property name.
Models can specify the field_validators dict
to tie properties to certain validation rules
:param new_value:
:return: True if new_value is valid
"""
if self.field_validators and key in self.field_validators:
return is_valid_against(self.field_validators[key], new_value)
if self.field_optional_validators and key in self.field_optional_validators:
if new_value and new_value != '' and new_value != 'None':
return is_valid_against(self.field_optional_validators[key], new_value)
return True
@property
@classmethod
@abstractmethod
def field_validators(cls) -> dict:
"""
The field_validators dictionary specifies validation
rules for model properties
It is recommended to set this as a class-wide variable.
Must be a dictionary.
Example:
field_validators = {
'myfield': 'phone'
}
"""
raise NotImplementedError
@property
@classmethod
@abstractmethod
def field_optional_validators(cls) -> dict:
"""
The field_validators dictionary specifies validation
rules for model properties
It is recommended to set this as a class-wide variable.
Must be a dictionary.
These fields can also be blank
Example:
field_optional_validators = {
'myfield': 'phone'
}
"""
raise NotImplementedError
def is_valid_against(validator_rule, value):
"""
Called by HasValidation.validate to properly dispatch
the validation rule to the method needed
:param validator_rule: rule string
:param value: new value
:return: bool
"""
if isinstance(validator_rule, list):
return _generic_regex(validator_rule, value)
if callable(validator_rule):
return validator_rule(value)
if validator_rule not in _validators:
raise NotImplementedError("Validator Type does not exist")
return _validators[validator_rule](value)
def _generic_regex(regexes: list, value):
"""
A generic regex validator.
Note that re.match returns the match or None instead
of True or False.
:param regexes: list of Regular Expressions to match
:param value: value to check against
:return: if value matches the regexes given
"""
if isinstance(regexes, str):
return re.match(re.compile(regexes), value) is not None
for regex in regexes:
result = re.match(re.compile(regex), str(value))
if result is None:
return None
return True
def _phone_number(value):
"""
Validates fields to be phone number formats only
:param value:
:return: if string is alphabetic
"""
return _generic_regex([r'^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$'], value)
def _alpha(value: str):
"""
Validates fields to be alphabetic only
:param value:
:return: if string is alphabetic
"""
return _generic_regex([r'^[A-Za-z\.\s]+$'], value)
def _numeric(value: str):
"""
Validates fields to be numeric only
:param value:
:return: if string is alphabetic
"""
return _generic_regex([r'^[0-9\-\.]+$'], value)
def _notnull(value):
"""
Ensure the field is not null
:param value:
:return: if not null
"""
return value is not None
def _password(value):
"""
Enforces a password policy of:
- at least 9 characters total
- 1 lowercase letter
- 1 uppercase
- 1 number
- 1 symbol
:param value:
:return: if acceptable
"""
return _generic_regex([r'(?=.{9,})(?=.*?[^\w\s])(?=.*?[0-9])(?=.*?[A-Z]).*?[a-z].*'], value)
def _ssn(value):
"""
Validates a field to match the US Social Security Number convention
:param value:
:return: if acceptable
"""
return _generic_regex(
[r'^(?!000)(?!666)(?!9[0-9][0-9])\d{3}[- ]?(?!00)\d{2}[- ]?(?!0000)\d{4}$'],
value)
def _state_code(value):
"""
Checks if a state code is valid
:param value: state code
:return: if valid US state code
"""
return value in ["AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DC", "DE", "FL", "GA",
"HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD",
"MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ",
"NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI", "SC",
"SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI", "WY", "INVALID"]
def _role(value):
"""
Checks if a role is valid
:param value: role
:return: if valid role
"""
# TODOFuture fix me, this shouldn't be hardcoded
return value in ['Viewer', 'Accounting', 'Reporter', 'Admin']
def _email(value):
"""
Validates a field for an email
:param value: email
:return: if acceptable
"""
return _generic_regex([r'(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)'], value)
def _bank_routing(value):
"""
Validates a field for a bank routing number
:param value: bank route
:return: if acceptable
"""
return _generic_regex([r'^[0-9\-\.A-Za-z\s]+$'], value)
def _date(value):
"""
Validates a field for a bank routing number
:param value: bank route
:return: if acceptable
"""
return _generic_regex([r'\d{1,2}\/\d{1,2}\/\d{2,4}'], value)
_validators = {
'phone': _phone_number,
'alpha': _alpha,
'numeric': _numeric,
'notnull': _notnull,
'password': _password,
'ssn': _ssn,
'state_code': _state_code,
'role': _role,
'email': _email,
'date': _date,
'bank_routing': _bank_routing
}
Functions
def is_valid_against(validator_rule, value)
-
Called by HasValidation.validate to properly dispatch the validation rule to the method needed :param validator_rule: rule string :param value: new value :return: bool
Expand source code
def is_valid_against(validator_rule, value): """ Called by HasValidation.validate to properly dispatch the validation rule to the method needed :param validator_rule: rule string :param value: new value :return: bool """ if isinstance(validator_rule, list): return _generic_regex(validator_rule, value) if callable(validator_rule): return validator_rule(value) if validator_rule not in _validators: raise NotImplementedError("Validator Type does not exist") return _validators[validator_rule](value)
Classes
class HasValidation
-
Trait that adds validation to Models
Expand source code
class HasValidation: """ Trait that adds validation to Models """ def __setattr__(self, key, value): """ This overrides when 'myobject.mykey' is set when the 'mykey' attribute doesn't exist This will run the value being set through a validator :param key: field to set :param value: value to set to field :return: None """ if key == 'data': for data_key, data_value in value.items(): if not self.validate(data_key, data_value): raise ValidationException(f'{data_key} given is invalid', data_key) super().__setattr__(key, value) else: if self.validate(key, value): super().__setattr__(key, value) else: raise ValidationException(f'{key} given is invalid', key) def validate(self, key, new_value): """ Validates a property using the field_validators dict :param key: property name. Models can specify the field_validators dict to tie properties to certain validation rules :param new_value: :return: True if new_value is valid """ if self.field_validators and key in self.field_validators: return is_valid_against(self.field_validators[key], new_value) if self.field_optional_validators and key in self.field_optional_validators: if new_value and new_value != '' and new_value != 'None': return is_valid_against(self.field_optional_validators[key], new_value) return True @property @classmethod @abstractmethod def field_validators(cls) -> dict: """ The field_validators dictionary specifies validation rules for model properties It is recommended to set this as a class-wide variable. Must be a dictionary. Example: field_validators = { 'myfield': 'phone' } """ raise NotImplementedError @property @classmethod @abstractmethod def field_optional_validators(cls) -> dict: """ The field_validators dictionary specifies validation rules for model properties It is recommended to set this as a class-wide variable. Must be a dictionary. These fields can also be blank Example: field_optional_validators = { 'myfield': 'phone' } """ raise NotImplementedError
Subclasses
Instance variables
var field_optional_validators
-
classmethod(function) -> method
Convert a function to be a class method.
A class method receives the class as implicit first argument, just like an instance method receives the instance. To declare a class method, use this idiom:
class C: @classmethod def f(cls, arg1, arg2, …): …
It can be called either on the class (e.g. C.f()) or on an instance (e.g. C().f()). The instance is ignored except for its class. If a class method is called for a derived class, the derived class object is passed as the implied first argument.
Class methods are different than C++ or Java static methods. If you want those, see the staticmethod builtin.
var field_validators
-
classmethod(function) -> method
Convert a function to be a class method.
A class method receives the class as implicit first argument, just like an instance method receives the instance. To declare a class method, use this idiom:
class C: @classmethod def f(cls, arg1, arg2, …): …
It can be called either on the class (e.g. C.f()) or on an instance (e.g. C().f()). The instance is ignored except for its class. If a class method is called for a derived class, the derived class object is passed as the implied first argument.
Class methods are different than C++ or Java static methods. If you want those, see the staticmethod builtin.
Methods
def validate(self, key, new_value)
-
Validates a property using the field_validators dict :param key: property name. Models can specify the field_validators dict to tie properties to certain validation rules :param new_value: :return: True if new_value is valid
Expand source code
def validate(self, key, new_value): """ Validates a property using the field_validators dict :param key: property name. Models can specify the field_validators dict to tie properties to certain validation rules :param new_value: :return: True if new_value is valid """ if self.field_validators and key in self.field_validators: return is_valid_against(self.field_validators[key], new_value) if self.field_optional_validators and key in self.field_optional_validators: if new_value and new_value != '' and new_value != 'None': return is_valid_against(self.field_optional_validators[key], new_value) return True
class ValidationException (message, database_field, *args)
-
Thrown when a validation rule is violated
Expand source code
class ValidationException(Exception): """ Thrown when a validation rule is violated """ def __init__(self, message, database_field, *args): super().__init__(message, *args) self.database_field = database_field
Ancestors
- builtins.Exception
- builtins.BaseException