PHP Classes

File: src/python/Formal.py

Recommend this page to a friend!
  Classes of Nikos M.   Formal PHP Validation Library   src/python/Formal.py   Download  
File: src/python/Formal.py
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Formal PHP Validation Library
Validate a set values with support to type casting
Author: By
Last change: v.1.2.0

* correctly handle (wildcard) missing keys in typecast/validate
* correctly handle some edge cases when input data is not array or object
* update tests
Date: 1 year ago
Size: 36,981 bytes
 

Contents

Class file image Download
## # Formal # validate nested (form) data with built-in and custom rules for PHP, JavaScript, Python # # @version 1.2.0 # https://github.com/foo123/Formal # ## import math, re, functools, os.path EMAIL_RE = re.compile(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$') URL_RE = re.compile('^(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$', re.I) def is_numeric(x): return not math.isnan(int(x)) def is_string(x): return isinstance(x, str) def is_array(x): return isinstance(x, (list, tuple)) def is_object(x): return isinstance(x, dict) def is_array_or_object(x): return isinstance(x, (list, tuple, dict)) def is_file(x): return os.path.isfile(str(x)) def is_callable(x): return callable(x) def method_exists(o, m): return hasattr(o, m) and callable(getattr(o, m)) def array(a): return a if isinstance(a, list) else (list(a) if isinstance(a, tuple) else [a]) def array_keys(o): if isinstance(o, (list, tuple)): return list(map(str, range(0, len(o)))) if isinstance(o, dict): return list(o.keys()) return [] def array_values(o): if isinstance(o, list): return o[:] if isinstance(o, tuple): return list(o) if isinstance(o, dict): return list(o.values()) return [] def array_key_exists(k, o): if isinstance(o, dict): return (str(k) in o) elif isinstance(o, (list, tuple)): try: k = int(k) except ValueError: return False return 0 <= k and k < len(o) return False def key_value(k, o): if isinstance(o, dict): return o[str(k)] elif isinstance(o, (list, tuple)): return o[int(k)] return None def set_key_value(k, v, o): if isinstance(o, (list, tuple)): k = int(k) l = len(o) if k >= l: o.extend([None for i in range(k+1-l)]) o[k] = v elif isinstance(o, dict): o[str(k)] = v return o def empty(x): return (x is None) or (x is False) or (0 == x) or ('' == x) or (isinstance(x, (dict, list, tuple)) and not len(x)) def is_null(x): return x is None def esc_re(s): return re.escape(str(s)) def by_length_desc(a, b): return len(b)-len(a) def get_alternate_pattern(alts): alts.sort(key=functools.cmp_to_key(by_length_desc)) return '|'.join(map(esc_re, alts)) def clone(o): if isinstance(o, (list, tuple)): return [clone(x) for x in o] elif isinstance(o, dict): oo = {} for k in o: oo[k] = clone(o[k]) return oo else: return o class FormalException(Exception): pass class FormalField: def __init__(self, field): self.field = str(field) class FormalDateTime: def __init__(self, format, locale = None): if not locale: locale = { 'day_short' : ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], 'day' : ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], 'month_short' : ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 'month' : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], 'meridian' : {'am' : 'am', 'pm' : 'pm', 'AM' : 'AM', 'PM' : 'PM'}, 'timezone_short' : ['UTC'], 'timezone' : ['UTC'], 'ordinal' : {'ord' : {'1' : 'st', '2' : 'nd', '3' : 'rd'}, 'nth' : 'th'}, } # (php) date formats # http://php.net/manual/en/function.date.php D = { # Day -- # Day of month w/leading 0; 01..31 'd': '(31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|09|08|07|06|05|04|03|02|01)' # Shorthand day name; Mon...Sun ,'D': '(' + get_alternate_pattern(locale['day_short']) + ')' # Day of month; 1..31 ,'j': '(31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1)' # Full day name; Monday...Sunday ,'l': '(' + get_alternate_pattern(locale['day']) + ')' # ISO-8601 day of week; 1[Mon]..7[Sun] ,'N': '([1-7])' # Ordinal suffix for day of month; st, nd, rd, th ,'S': '' # added below # Day of week; 0[Sun]..6[Sat] ,'w': '([0-6])' # Day of year; 0..365 ,'z': '([1-3]?[0-9]{1,2})' # Week -- # ISO-8601 week number ,'W': '([0-5]?[0-9])' # Month -- # Full month name; January...December ,'F': '(' + get_alternate_pattern(locale['month']) + ')' # Month w/leading 0; 01...12 ,'m': '(12|11|10|09|08|07|06|05|04|03|02|01)' # Shorthand month name; Jan...Dec ,'M': '(' + get_alternate_pattern(locale['month_short']) + ')' # Month; 1...12 ,'n': '(12|11|10|9|8|7|6|5|4|3|2|1)' # Days in month; 28...31 ,'t': '(31|30|29|28)' # Year -- # Is leap year?; 0 or 1 ,'L': '([01])' # ISO-8601 year ,'o': '(\\d{2,4})' # Full year; e.g. 1980...2010 ,'Y': '([12][0-9]{3})' # Last two digits of year; 00...99 ,'y': '([0-9]{2})' # Time -- # am or pm ,'a': '(' + get_alternate_pattern([ locale['meridian']['am'], locale['meridian']['pm'] ]) + ')' # AM or PM ,'A': '(' + get_alternate_pattern([ locale['meridian']['AM'], locale['meridian']['PM'] ]) + ')' # Swatch Internet time; 000..999 ,'B': '([0-9]{3})' # 12-Hours; 1..12 ,'g': '(12|11|10|9|8|7|6|5|4|3|2|1)' # 24-Hours; 0..23 ,'G': '(23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0)' # 12-Hours w/leading 0; 01..12 ,'h': '(12|11|10|09|08|07|06|05|04|03|02|01)' # 24-Hours w/leading 0; 00..23 ,'H': '(23|22|21|20|19|18|17|16|15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00)' # Minutes w/leading 0; 00..59 ,'i': '([0-5][0-9])' # Seconds w/leading 0; 00..59 ,'s': '([0-5][0-9])' # Microseconds; 000000-999000 ,'u': '([0-9]{6})' # Timezone -- # Timezone identifier; e.g. Atlantic/Azores, ... ,'e': '(' + get_alternate_pattern(locale['timezone']) + ')' # DST observed?; 0 or 1 ,'I': '([01])' # Difference to GMT in hour format; e.g. +0200 ,'O': '([+-][0-9]{4})' # Difference to GMT w/colon; e.g. +02:00 ,'P': '([+-][0-9]{2}:[0-9]{2})' # Timezone abbreviation; e.g. EST, MDT, ... ,'T': '(' + get_alternate_pattern(locale['timezone_short']) + ')' # Timezone offset in seconds (-43200...50400) ,'Z': '(-?[0-9]{5})' # Full Date/Time -- # Seconds since UNIX epoch ,'U': '([0-9]{1,8})' # ISO-8601 date. Y-m-d\\TH:i:sP ,'c': '' # added below # RFC 2822 D, d M Y H:i:s O ,'r': '' # added below } # Ordinal suffix for day of month; st, nd, rd, th lords = array_values(locale['ordinal']['ord']) lords.append(locale['ordinal']['nth']) D['S'] = '(' + get_alternate_pattern(lords) + ')' # ISO-8601 date. Y-m-d\\TH:i:sP D['c'] = D['Y']+'-'+D['m']+'-'+D['d']+'\\\\'+D['T']+D['H']+':'+D['i']+':'+D['s']+D['P'] # RFC 2822 D, d M Y H:i:s O D['r'] = D['D']+',\\s'+D['d']+'\\s'+D['M']+'\\s'+D['Y']+'\\s'+D['H']+':'+D['i']+':'+D['s']+'\\s'+D['O'] format = str(format); rex = '' for i in range(len(format)): f = format[i] rex += D[ f ] if f in D else esc_re(f) self.format = format self.pattern = re.compile('^' + rex + '$') def getFormat(self): return self.format def getPattern(self): return self.pattern def __str__(self): return str(self.pattern.pattern) class FormalType: def __init__(self, type, args = None): if isinstance(type, FormalType): self.func = type.func self.inp = type.inp else: method = 't_' + str(type).strip().lower() if is_string(type) else None self.func = method if method and method_exists(self, method) else (type if is_callable(type) else None) self.inp = args def exec(self, v, k = None, m = None): if is_string(self.func): v = getattr(self, self.func)(v, k, m) elif is_callable(self.func): v = self.func(v, self.inp, k, m) return v def t_composite(self, v, k, m): types = array(self.inp) for i in range(len(types)): v = types[i].exec(v, k, m) return v #def t_fields(self, v, k, m): # SEPARATOR = m.option('SEPARATOR') # if is_object(v): # for field in self.inp: # type = self.inp[field] # v[field] = type.exec(v[field] if field in v else None, field if empty(k) else k+SEPARATOR+field, m) # elif is_array(v): # for field in self.inp: # type = self.inp[field] # v[int(field)] = type.exec(v[int(field)] if field in array_keys(v) else None, field if empty(k) else k+SEPARATOR+field, m) # return v # #def t_default(self, v, k, m): # defaultValue = self.inp # if is_null(v) or (is_string(v) and not len(v.strip())): # v = defaultValue # return v def t_bool(self, v, k, m): # handle string representation of booleans as well if is_string(v) and len(v): vs = v.lower() return 'true' == vs or 'on' == vs or '1' == vs return bool(v) def t_int(self, v, k, m): return int(v) def t_float(self, v, k, m): return float(v) def t_str(self, v, k, m): return str(v) def t_min(self, v, k, m): min = self.inp return min if v < min else v def t_max(self, v, k, m): max = self.inp return max if v > max else v def t_clamp(self, v, k, m): min = self.inp[0] max = self.inp[1] return min if v < min else (max if v > max else v) def t_trim(self, v, k, m): return str(v).strip() def t_lower(self, v, k, m): return str(v).lower() def t_upper(self, v, k, m): return str(v).upper() class FormalValidator: def __init__(self, validator, args = None, msg = None): if isinstance(validator, FormalValidator): self.func = validator.func self.inp = validator.inp self.msg = validator.msg if empty(msg) else msg else: method = 'v_' + str(validator).strip().lower() if is_string(validator) else None self.func = method if method and method_exists(self, method) else (validator if is_callable(validator) else None) self.inp = args self.msg = msg def _and_(self, validator): return FormalValidator('and', (self, validator)) def _or_(self, validator): return FormalValidator('or', (self, validator)) def _not_(self, msg = None): return FormalValidator('not', self, msg) def exec(self, v, k = None, m = None, missingValue = False): valid = True if is_string(self.func): valid = bool(getattr(self, self.func)(v, k, m, missingValue)) elif is_callable(self.func): valid = bool(self.func(v, self.inp, k, m, missingValue, self.msg)) return valid def v_and(self, v, k, m, missingValue): valid = self.inp[0].exec(v, k, m, missingValue) and self.inp[1].exec(v, k, m, missingValue) return valid def v_or(self, v, k, m, missingValue): msg1 = None msg2 = None valid1 = False valid2 = False try: valid1 = self.inp[0].exec(v, k, m, missingValue) except FormalException as e: valid1 = False msg1 = str(e) if not valid1: try: valid2 = self.inp[1].exec(v, k, m, missingValue) except FormalException as e: valid2 = False msg2 = str(e) valid = valid1 or valid2 if not valid and (not empty(msg1) or not empty(msg2)): raise FormalException(msg2 if empty(msg1) else msg1) return valid def v_not(self, v, k, m, missingValue): try: valid = not (self.inp.exec(v, k, m, missingValue)) except FormalException as e: valid = True if (not valid) and (not empty(self.msg)): raise FormalException(self.msg.replace('{key}', k).replace('{args}', '')) return valid def v_optional(self, v, k, m, missingValue): valid = True if not missingValue: valid = self.inp.exec(v, k, m, False) return valid def v_required(self, v, k, m, missingValue): valid = not missingValue and not is_null(v) if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', '') if not empty(self.msg) else "\""+k+"\" is required!") return valid #def v_fields(self, v, k, m, missingValue): # if not is_object(v) and not is_array(v): return False # SEPARATOR = m.option('SEPARATOR') # for field in self.inp: # validator = self.inp[field] # if is_object(v): # if not field in v: # if not validator.exec(None, field if empty(k) else k+SEPARATOR+field, m, True): # return False # else: # if not validator.exec(v[field], field if empty(k) else k+SEPARATOR+field, m, missingValue) # return False # elif is_array(v): # if not field in array_keys(v): # if not validator.exec(None, field if empty(k) else k+SEPARATOR+field, m, True): # return False # else: # if not validator.exec(v[int(field)], field if empty(k) else k+SEPARATOR+field, m, missingValue) # return False # return True def v_numeric(self, v, k, m, missingValue): valid = is_numeric(v) if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', '') if not empty(self.msg) else "\""+k+"\" must be numeric value!") return valid def v_object(self, v, k, m, missingValue): valid = is_object(v) if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', '') if not empty(self.msg) else "\""+k+"\" must be an object!") return valid def v_array(self, v, k, m, missingValue): valid = is_array(v) if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', '') if not empty(self.msg) else "\""+k+"\" must be an array!") return valid def v_file(self, v, k, m, missingValue): valid = is_file(str(v)) if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', '') if not empty(self.msg) else "\""+k+"\" must be a file!") return valid def v_empty(self, v, k, m, missingValue): valid = missingValue or is_null(v) or (not len(v) if is_array(v) or is_object(v) else not len(str(v).strip())) if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', '') if not empty(self.msg) else "\""+k+"\" must be empty!") return valid def v_maxitems(self, v, k, m, missingValue): valid = len(v) <= self.inp if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', str(self.inp)) if not empty(self.msg) else "\""+k+"\" must have at most "+str(self.inp)+" items!") return valid def v_minitems(self, v, k, m, missingValue): valid = len(v) >= self.inp if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', str(self.inp)) if not empty(self.msg) else "\""+k+"\" must have at least "+str(self.inp)+" items!") return valid def v_maxchars(self, v, k, m, missingValue): valid = len(v) <= self.inp if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', str(self.inp)) if not empty(self.msg) else "\""+k+"\" must have at most "+str(self.inp)+" characters!") return valid def v_minchars(self, v, k, m, missingValue): valid = len(v) >= self.inp if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', str(self.inp)) if not empty(self.msg) else "\""+k+"\" must have at least "+str(self.inp)+" characters!") return valid def v_maxsize(self, v, k, m, missingValue): fs = False try: fs = os.path.getsize(str(v)) except OSError: fs = False valid = False if fs is False else (fs <= self.inp) if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', str(self.inp)) if not empty(self.msg) else "\""+k+"\" must have at most "+str(self.inp)+" bytes!") return valid def v_minsize(self, v, k, m, missingValue): fs = False try: fs = os.path.getsize(str(v)) except OSError: fs = False valid = False if fs is False else (fs >= self.inp) if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', str(self.inp)) if not empty(self.msg) else "\""+k+"\" must have at least "+str(self.inp)+" bytes!") return valid def v_eq(self, v, k, m, missingValue): val = self.inp valm = val if isinstance(val, FormalField): valm = val.field if not empty(self.msg) else '"' + val.field + '"' val = m.get(val.field) valid = val == v if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', str(valm)) if not empty(self.msg) else "\""+k+"\" must be equal to "+str(valm)+"!") return valid def v_neq(self, v, k, m, missingValue): val = self.inp valm = val if isinstance(val, FormalField): valm = val.field if not empty(self.msg) else '"' + val.field + '"' val = m.get(val.field) valid = val != v if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', str(valm)) if not empty(self.msg) else "\""+k+"\" must not be equal to "+str(valm)+"!") return valid def v_gt(self, v, k, m, missingValue): val = self.inp valm = val if isinstance(val, FormalField): valm = val.field if not empty(self.msg) else '"' + val.field + '"' val = m.get(val.field) valid = v > val if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', str(valm)) if not empty(self.msg) else "\""+k+"\" must be greater than "+str(valm)+"!") return valid def v_gte(self, v, k, m, missingValue): val = self.inp valm = val if isinstance(val, FormalField): valm = val.field if not empty(self.msg) else '"' + val.field + '"' val = m.get(val.field) valid = v >= val if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', str(valm)) if not empty(self.msg) else "\""+k+"\" must be greater than or equal to "+str(valm)+"!") return valid def v_lt(self, v, k, m, missingValue): val = self.inp valm = val if isinstance(val, FormalField): valm = val.field if not empty(self.msg) else '"' + val.field + '"' val = m.get(val.field) valid = v < val if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', str(valm)) if not empty(self.msg) else "\""+k+"\" must be less than "+str(valm)+"!") return valid def v_lte(self, v, k, m, missingValue): val = self.inp valm = val if isinstance(val, FormalField): valm = val.field if not empty(self.msg) else '"' + val.field + '"' val = m.get(val.field) valid = v <= val if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', str(valm)) if not empty(self.msg) else "\""+k+"\" must be less than or equal to "+str(valm)+"}!") return valid def v_between(self, v, k, m, missingValue): min = self.inp[0] max = self.inp[1] minm = min maxm = max if isinstance(min, FormalField): minm = min.field if not empty(self.msg) else '"' + min.field + '"' min = m.get(min.field) if isinstance(max, FormalField): maxm = max.field if not empty(self.msg) else '"' + max.field + '"' max = m.get(max.field) valid = (min <= v) and (v <= max) if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', ','.join([str(minm), str(maxm)])) if not empty(self.msg) else "\""+k+"\" must be between "+str(minm)+" and "+str(maxm)+"!") return valid def v_in(self, v, k, m, missingValue): val = self.inp if isinstance(val, FormalField): valm = val.field if not empty(self.msg) else '"' + val.field + '"' val = m.get(val.field) else: valm = ','.join(map(str, array(val))) if not empty(self.msg) else '[' + ','.join(map(str, array(val))) + ']' valid = v in array(val) if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', str(valm)) if not empty(self.msg) else "\""+k+"\" must be one of "+str(valm)+"!") return valid def v_not_in(self, v, k, m, missingValue): val = self.inp if isinstance(val, FormalField): valm = val.field if not empty(self.msg) else '"' + val.field + '"' val = m.get(val.field) else: valm = ','.join(map(str, array(val))) if not empty(self.msg) else '[' + ','.join(map(str, array(val))) + ']' valid = v not in array(val) if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', str(valm)) if not empty(self.msg) else "\""+k+"\" must not be one of "+str(valm)+"!") return valid def v_match(self, v, k, m, missingValue): rex = self.inp.getPattern() if isinstance(self.inp, FormalDateTime) else self.inp valid = bool(re.match(rex, str(v))) if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', self.inp.getFormat() if isinstance(self.inp, FormalDateTime) else str(self.inp)) if not empty(self.msg) else "\""+k+"\" must match " + (self.inp.getFormat() if isinstance(self.inp, FormalDateTime) else 'the') + " pattern!") return valid def v_email(self, v, k, m, missingValue): valid = bool(re.match(EMAIL_RE, str(v))) if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', '') if not empty(self.msg) else "\""+k+"\" must be valid email pattern!") return valid def v_url(self, v, k, m, missingValue): valid = bool(re.match(URL_RE, str(v))) if not valid: raise FormalException(self.msg.replace('{key}', k).replace('{args}', '') if not empty(self.msg) else "\""+k+"\" must be valid url pattern!") return valid class FormalError: def __init__(self, msg = '', key = list()): self.msg = str(msg) self.key = key def getMsg(self): return self.msg def getKey(self): return self.key def __str__(self): return self.msg class Formal: """ Formal for Python, https://github.com/foo123/Formal """ VERSION = "1.2.0" # export these Exception = FormalException Field = FormalField DateTime = FormalDateTime Type = FormalType Validator = FormalValidator Error = FormalError @staticmethod def field(field): return FormalField(field) @staticmethod def datetime(format, locale = None): return FormalDateTime(format, locale) @staticmethod def typecast(type, args = None): return FormalType(type, args) @staticmethod def validate(validator, args = None, msg = None): return FormalValidator(validator, args, msg) def __init__(self): self.opts = {} self.err = [] self.data = None self.option('WILDCARD', '*').option('SEPARATOR', '.').option('break_on_first_error', False).option('invalid_value_msg', 'Invalid Value in "{key}"!').option('missing_value_msg', 'Missing Value in "{key}"!').option('defaults', {}).option('typecasters', {}).option('validators', {}) def option(self, *args): nargs = len(args) if 1 == nargs: key = str(args[0]) return self.opts[key] if key in self.opts else None elif 1 < nargs: key = str(args[0]) val = args[1] self.opts[key] = val return self def process(self, data): WILDCARD = self.option('WILDCARD') SEPARATOR = self.option('SEPARATOR') self.data = None self.err = [] data = clone(data) data = self.doMergeDefaults(data, self.option('defaults'), WILDCARD, SEPARATOR) data = self.doTypecast(data, self.option('typecasters'), [], [], WILDCARD, SEPARATOR) self.data = data self.doValidate(data, self.option('validators'), [], [], WILDCARD, SEPARATOR) self.data = None return data def getErrors(self): return self.err def get(self, field, _default = None, data = None): if data is None: data = self.data WILDCARD = self.option('WILDCARD') SEPARATOR = self.option('SEPARATOR') is_array_result = False is_result_set = False result = None if (is_string(field) or is_numeric(field)) and isinstance(data, (list, tuple, dict)): stack = [(data, str(field))] while len(stack): o, key = stack.pop(0) p = key.split(SEPARATOR) i = 0 l = len(p) while i < l: k = p[i] i += 1 if i < l: if isinstance(o, (list, tuple, dict)): if WILDCARD == k: is_array_result = True k = SEPARATOR.join(p[i:]) for kk in array_keys(o): stack.append((o, kk+SEPARATOR+k)) break elif array_key_exists(k, o): o = key_value(k, o) else: break else: break else: if isinstance(o, (list, tuple, dict)): if WILDCARD == k: is_array_result = True if not is_result_set: result = [] result += array_values(o) is_result_set = True elif array_key_exists(k, o): if is_array_result: if not is_result_set: result = [] result.append(key_value(k, o)) else: result = key_value(k, o) is_result_set = True else: if is_array_result: if not is_result_set: result = [] result.append(_default) else: result = _default is_result_set = True return result if is_result_set else _default return _default def doMergeKeys(self, keys, _def): defaults = _def for k in reversed(keys): o = {} if is_array(k): for kk in k: o = set_key_value(kk, clone(defaults), o) else: o = set_key_value(k, clone(defaults), o) defaults = o return defaults def doMergeDefaults(self, data, defaults, WILDCARD = '*', SEPARATOR = '.'): import json if is_array_or_object(data) and is_array_or_object(defaults): for key in array_keys(defaults): _def = key_value(key, defaults) kk = key.split(SEPARATOR) n = len(kk) if 1 < n: o = data keys = [] doMerge = True for i in range(n): k = kk[i] if WILDCARD == k: ok = array_keys(o) if not len(ok): doMerge = False break keys.append(ok) o = key_value(ok[0], o) elif array_key_exists(k, o): keys.append(k) o = key_value(k, o) elif i == n-1: keys.append(k) else: doMerge = False break if doMerge: data = self.doMergeDefaults(data, self.doMergeKeys(keys, _def), WILDCARD, SEPARATOR) else: if array_key_exists(key, data): data_key = key_value(key, data) if is_array_or_object(data_key) and is_array_or_object(_def): data = set_key_value(key, self.doMergeDefaults(data_key, _def, WILDCARD, SEPARATOR), data) elif is_null(data_key) or (is_string(data_key) and not len(data_key.strip())): data = set_key_value(key, clone(_def), data) else: data = set_key_value(key, clone(_def), data) elif is_null(data) or (is_string(data) and not len(data.strip())): data = clone(defaults) return data def doTypecast(self, data, typecaster, key = list(), root = list(), WILDCARD = '*', SEPARATOR = '.'): if isinstance(typecaster, FormalType): n = len(key) i = 0 if i < n: k = key[i] i += 1 if '' == k: return data elif WILDCARD == k: if i < n: kk = array_keys(data) if len(kk): rk = key[i:] root = root + key[0:i-1] for ok in kk: data = set_key_value(ok, self.doTypecast(key_value(ok, data), typecaster, rk, root + [ok], WILDCARD, SEPARATOR), data) else: kk = array_keys(data) if len(kk): root = root + key[0:i-1] for ok in kk: data = self.doTypecast(data, typecaster, [ok], root, WILDCARD, SEPARATOR) return data elif array_key_exists(k, data): rk = key[i:] root = root + key[0:i] data = set_key_value(k, self.doTypecast(key_value(k, data), typecaster, rk, root, WILDCARD, SEPARATOR), data) else: return data else: KEY = SEPARATOR.join(root + key) data = typecaster.exec(data, KEY, self) elif is_array_or_object(typecaster): for k in array_keys(typecaster): data = self.doTypecast(data, key_value(k, typecaster), k.split(SEPARATOR) if empty(key) else key + k.split(SEPARATOR), root, WILDCARD, SEPARATOR) return data def doValidate(self, data, validator, key = list(), root = list(), WILDCARD = '*', SEPARATOR = '.'): if self.option('break_on_first_error') and len(self.err): return if isinstance(validator, FormalValidator): n = len(key) i = 0 while i < n: k = key[i] i += 1 if '' == k: continue elif WILDCARD == k: if i < n: kk = array_keys(data) if not len(kk): KEY_ = root + key KEY = SEPARATOR.join(KEY_) err = None try: valid = validator.exec(None, KEY, self, True) except FormalException as e: valid = False err = str(e) if not valid: self.err.append(FormalError(self.option('missing_value_msg').replace('{key}', KEY).replace('{args}', '') if empty(err) else err, KEY_)) return else: rk = key[i:] root = root + key[0:i-1] for ok in kk: self.doValidate(key_value(ok, data), validator, rk, root + [ok], WILDCARD, SEPARATOR) else: kk = array_keys(data) if not len(kk): KEY_ = root + key KEY = SEPARATOR.join(KEY_) err = None try: valid = validator.exec(None, KEY, self, True) except FormalException as e: valid = False err = str(e) if not valid: self.err.append(FormalError(self.option('missing_value_msg').replace('{key}', KEY).replace('{args}', '') if empty(err) else err, KEY_)) else: root = root + key[0:i-1] for ok in kk: self.doValidate(data, validator, [ok], root, WILDCARD, SEPARATOR) return elif array_key_exists(k, data): data = key_value(k, data) else: KEY_ = root + key KEY = SEPARATOR.join(KEY_) err = None try: valid = validator.exec(None, KEY, self, True) except FormalException as e: valid = False err = str(e) if not valid: self.err.append(FormalError(self.option('missing_value_msg').replace('{key}', KEY).replace('{args}', '') if empty(err) else err, KEY_)) return KEY_ = root + key KEY = SEPARATOR.join(KEY_) err = None try: valid = validator.exec(data, KEY, self, False) except FormalException as e: valid = False err = str(e) if not valid: self.err.append(FormalError(self.option('invalid_value_msg').replace('{key}', KEY).replace('{args}', '') if empty(err) else err, KEY_)) elif is_array_or_object(validator): for k in array_keys(validator): self.doValidate(data, key_value(k, validator), k.split(SEPARATOR) if empty(key) else key + k.split(SEPARATOR), root, WILDCARD, SEPARATOR) __all__ = ['Formal']