11import importlib
22import logging
33import json
4+ from markupsafe import string
45import yaml
56import requests
67import urllib3
@@ -234,35 +235,44 @@ def _clear_errors(self):
234235 else :
235236 self ._validation_errors .clear ()
236237
237- def validate_mac (self , mac ):
238+ def validate_mac (self , path , mac ):
239+ msg = "value of `{}` must be a valid mac address, instead of `{}`" .format (path , mac )
238240 if mac is None or not isinstance (mac , (str , unicode )) or mac .count (" " ) != 0 :
239- return False
241+ self . _append_error ( msg )
240242 try :
241243 if len (mac ) != 17 :
242- return False
243- return all ([0 <= int (oct , 16 ) <= 255 for oct in mac .split (":" )])
244+ self ._append_error (msg )
245+ if all ([0 <= int (oct , 16 ) <= 255 for oct in mac .split (":" )]) is False :
246+ self ._append_error (msg )
244247 except Exception :
245- return False
248+ self . _append_error ( msg )
246249
247- def validate_ipv4 (self , ip ):
250+ def validate_ipv4 (self , path , ip ):
251+ msg = "value of `{}` must be a valid ipv4 address, instead of `{}`" .format (path , ip )
248252 if ip is None or not isinstance (ip , (str , unicode )) or ip .count (" " ) != 0 :
249- return False
253+ self . _append_error ( msg )
250254 if len (ip .split ("." )) != 4 :
251- return False
255+ self . _append_error ( msg )
252256 try :
253- return all ([0 <= int (oct ) <= 255 for oct in ip .split ("." , 3 )])
257+ if all ([0 <= int (oct ) <= 255 for oct in ip .split ("." , 3 )]) is False :
258+ self ._append_error (msg )
254259 except Exception :
255- return False
260+ self . _append_error ( msg )
256261
257- def validate_ipv6 (self , ip ):
262+ def validate_ipv6 (self , path , ip ):
263+ msg = "value of `{}` must be a valid ipv6 address, instead of `{}`" .format (path , ip )
258264 if ip is None or not isinstance (ip , (str , unicode )):
265+ self ._append_error (msg )
259266 return False
260267 ip = ip .strip ()
261268 if ip .count (" " ) > 0 or ip .count (":" ) > 7 or ip .count ("::" ) > 1 or ip .count (":::" ) > 0 :
269+ self ._append_error (msg )
262270 return False
263271 if (ip [0 ] == ":" and ip [:2 ] != "::" ) or (ip [- 1 ] == ":" and ip [- 2 :] != "::" ):
272+ self ._append_error (msg )
264273 return False
265274 if ip .count ("::" ) == 0 and ip .count (":" ) != 7 :
275+ self ._append_error (msg )
266276 return False
267277 if ip == "::" :
268278 return True
@@ -273,68 +283,90 @@ def validate_ipv6(self, ip):
273283 else :
274284 ip = ip .replace ("::" , ":0:" )
275285 try :
276- return all ([True if (0 <= int (oct , 16 ) <= 65535 ) and (1 <= len (oct ) <= 4 ) else False for oct in ip .split (":" )])
286+ verdict = all ([
287+ True if (0 <= int (oct , 16 ) <= 65535 ) and (1 <= len (oct ) <= 4 ) else False for oct in ip .split (":" )
288+ ])
289+ if verdict is False :
290+ self ._append_error (msg )
277291 except Exception :
278- return False
292+ self . _append_error ( msg )
279293
280- def validate_hex (self , hex ):
294+ def validate_hex (self , path , hex ):
295+ msg = "value of `{}` must be a valid hex string, instead of `{}`" .format (path , hex )
281296 if hex is None or not isinstance (hex , (str , unicode )):
282- return False
297+ self . _append_error ( msg )
283298 try :
284299 int (hex , 16 )
285300 return True
286301 except Exception :
287- return False
302+ self . _append_error ( msg )
288303
289- def validate_integer (self , value , min , max ):
304+ def validate_integer (self , path , value ):
290305 if value is None or not isinstance (value , int ):
291- return False
292- if value < 0 :
293- return False
294- if min is not None and value < min :
295- return False
296- if max is not None and value > max :
297- return False
298- return True
306+ self ._append_error ("value of `{}` must be a valid int type, instead of `{}`" .format (
307+ path , value
308+ ))
309+
310+ def validate_min_max (self , path , value , min , max ):
311+ if isinstance (value , str ):
312+ value = len (value )
313+ if (min is not None and value < min ) or (max is not None and value > max ):
314+ self ._append_error ("length of `{}` must be in the range of [{}, {}], instead of `{}`" .format (
315+ path ,
316+ min if min is not None else "" ,
317+ max if max is not None else "" ,
318+ value
319+ ))
299320
300- def validate_float (self , value ):
301- return isinstance (value , (int , float ))
321+ def validate_float (self , path , value ):
322+ if isinstance (value , (int , float )) is False :
323+ self ._append_error ("value of `{}` must be a valid float type, instead of `{}`" .format (
324+ path , value
325+ ))
302326
303- def validate_string (self , value , min_length , max_length ):
327+ def validate_string (self , path , value ):
304328 if value is None or not isinstance (value , (str , unicode )):
305- return False
306- if min_length is not None and len (value ) < min_length :
307- return False
308- if max_length is not None and len (value ) > max_length :
309- return False
310- return True
329+ self ._append_error ("value of `{}` must be a valid string type, instead of `{}`" .format (
330+ path , value
331+ ))
311332
312- def validate_bool (self , value ):
313- return isinstance (value , bool )
333+ def validate_bool (self , path , value ):
334+ if isinstance (value , bool ) is False :
335+ self ._append_error ("value of `{}` must be a valid bool type, instead of `{}`" .format (
336+ path , value
337+ ))
314338
315- def validate_list (self , value , itemtype , min , max , min_length , max_length ):
339+ def validate_list (self , path , value , itemtype , min , max ):
316340 if value is None or not isinstance (value , list ):
317341 return False
318342 v_obj = getattr (self , "validate_{}" .format (itemtype ), None )
319343 if v_obj is None :
320344 raise AttributeError ("{} is not a valid attribute" .format (itemtype ))
321- v_obj_lst = []
322- for item in value :
323- if itemtype == "integer" :
324- v_obj_lst .append (v_obj (item , min , max ))
325- elif itemtype == "string" :
326- v_obj_lst .append (v_obj (item , min_length , max_length ))
345+ for ind , item in enumerate (value ):
346+ if itemtype in ["integer" , "string" , "float" ]:
347+ v_obj (path + "[{}]" .format (ind ), item )
348+ self .validate_min_max (path , item , min , max )
327349 else :
328- v_obj_lst .append (v_obj (item ))
329- return v_obj_lst
350+ v_obj (path + "[{}]" .format (ind ), item )
330351
331- def validate_binary (self , value ):
332- if value is None or not isinstance (value , (str , unicode )):
333- return False
334- return all ([True if int (bin ) == 0 or int (bin ) == 1 else False for bin in value ])
352+ def validate_binary (self , path , value ):
353+ if value is None or not isinstance (value , (str , unicode )) or \
354+ all ([True if int (bin ) == 0 or int (bin ) == 1 else False for bin in value ]) is False :
355+ self ._append_error ("value of `{}` must be a valid binary string, instead of `{}`" .format (
356+ path , value
357+ ))
335358
336- def types_validation (self , value , type_ , err_msg , itemtype = None , min = None , max = None , min_length = None , max_length = None ):
337- type_map = {int : "integer" , str : "string" , float : "float" , bool : "bool" , list : "list" , "int64" : "integer" , "int32" : "integer" , "double" : "float" }
359+ def types_validation (self , value , type_ , path , itemtype = None , min = None , max = None ):
360+ type_map = {
361+ int : "integer" ,
362+ str : "string" ,
363+ float : "float" ,
364+ bool : "bool" ,
365+ list : "list" ,
366+ "int64" : "integer" ,
367+ "int32" : "integer" ,
368+ "double" : "float"
369+ }
338370 if type_ in type_map :
339371 type_ = type_map [type_ ]
340372 if itemtype is not None and itemtype in type_map :
@@ -343,37 +375,9 @@ def types_validation(self, value, type_, err_msg, itemtype=None, min=None, max=N
343375 if v_obj is None :
344376 msg = "{} is not a valid or unsupported format" .format (type_ )
345377 raise TypeError (msg )
346- if type_ == "list" :
347- verdict = v_obj (value , itemtype , min , max , min_length , max_length )
348- if all (verdict ) is True :
349- return
350- err_msg = "{} \n {} are not valid" .format (err_msg , [value [index ] for index , item in enumerate (verdict ) if item is False ])
351- verdict = False
352- elif type_ == "integer" :
353- verdict = v_obj (value , min , max )
354- if verdict is True :
355- return
356- min_max = ""
357- if min is not None :
358- min_max = ", expected min {}" .format (min )
359- if max is not None :
360- min_max = min_max + ", expected max {}" .format (max )
361- err_msg = "{} \n got {} of type {} {}" .format (err_msg , value , type (value ), min_max )
362- elif type_ == "string" :
363- verdict = v_obj (value , min_length , max_length )
364- if verdict is True :
365- return
366- msg = ""
367- if min_length is not None :
368- msg = ", expected min {}" .format (min_length )
369- if max_length is not None :
370- msg = msg + ", expected max {}" .format (max_length )
371- err_msg = "{} \n got {} of type {} {}" .format (err_msg , value , type (value ), msg )
372- else :
373- verdict = v_obj (value )
374- if verdict is False :
375- self ._append_error (err_msg )
376- # raise TypeError(err_msg)
378+ v_obj (path , value ) if type_ != "list" else v_obj (path , value , itemtype , min , max )
379+ if type_ in ["integer" , "string" , "float" ]:
380+ self .validate_min_max (path , value , min , max )
377381
378382 def _raise_validation (self ):
379383 errors = "\n " .join (self ._validation_errors )
@@ -393,6 +397,8 @@ class OpenApiObject(OpenApiBase, OpenApiValidator):
393397
394398 __slots__ = ("_properties" , "_parent" , "_choice" )
395399
400+ _JSON_NAME = ""
401+
396402 _DEFAULTS = {}
397403 _TYPES = {}
398404 _REQUIRED = []
@@ -433,9 +439,6 @@ def _get_property(self, name, default_value=None, parent=None, choice=None):
433439 return self ._properties [name ]
434440 if isinstance (default_value , type ) is True :
435441 self ._set_choice (name )
436- # if "_choice" in default_value.__slots__:
437- # self._properties[name] = default_value(parent=parent, choice=choice)
438- # else:
439442 self ._properties [name ] = default_value (parent = parent )
440443 if "_DEFAULTS" in dir (self ._properties [name ]) and "choice" in self ._properties [name ]._DEFAULTS :
441444 getattr (self ._properties [name ], self ._properties [name ]._DEFAULTS ["choice" ])
@@ -462,17 +465,15 @@ def _set_property(self, name, value, choice=None):
462465
463466 def _encode (self ):
464467 """Helper method for serialization"""
468+ self ._validate (self ._JSON_NAME )
465469 output = {}
466- self ._validate_required ()
467470 for key , value in self ._properties .items ():
468- self ._validate_types (key , value )
469471 if isinstance (value , (OpenApiObject , OpenApiIter )):
470472 output [key ] = value ._encode ()
471473 elif value is not None :
472474 if key in self ._TYPES and "format" in self ._TYPES [key ] and self ._TYPES [key ]["format" ] == "int64" :
473475 value = str (value )
474476 output [key ] = value
475- self ._raise_validation ()
476477 return output
477478
478479 def _decode (self , obj ):
@@ -501,9 +502,7 @@ def _decode(self, obj):
501502 if "format" in self ._TYPES [property_name ] and self ._TYPES [property_name ]["format" ] == "int64" :
502503 property_value = int (property_value )
503504 self ._properties [property_name ] = property_value
504- self ._validate_types (property_name , property_value )
505- self ._validate_required ()
506- self ._raise_validation ()
505+ self ._validate (self ._JSON_NAME )
507506 return self
508507
509508 def _get_child_class (self , property_name , is_property_list = False ):
@@ -534,76 +533,73 @@ def clone(self):
534533 """Creates a deep copy of the current object"""
535534 return self .__deepcopy__ (None )
536535
537- def _validate_required (self ):
536+ def _validate_required (self , path ):
538537 """Validates the required properties are set
539538 Use getattr as it will set any defaults prior to validating
540539 """
541540 if getattr (self , "_REQUIRED" , None ) is None :
542541 return
543542 for name in self ._REQUIRED :
544543 if self ._properties .get (name ) is None :
545- msg = "{} is a mandatory property of {}" " and should not be set to None" .format (
546- name ,
547- self .__class__ ,
544+ msg = "required field `{}.{}` must not be empty" .format (
545+ path , name
548546 )
549- # raise ValueError(msg)
550547 self ._append_error (msg )
551548
552- def _validate_types (self , property_name , property_value ):
549+ def _validate_types (self , path , property_name , property_value ):
553550 common_data_types = [list , str , int , float , bool ]
554551 if property_name not in self ._TYPES :
555- # raise ValueError("Invalid Property {}".format(property_name))
556552 return
557553 details = self ._TYPES [property_name ]
558554 if property_value is None :
559555 return
560556 if "enum" in details and property_value not in details ["enum" ]:
561- msg = "property {} shall be one of these" " {} enum, but got {} at {}"
562- self ._append_error (
563- msg .format (property_name , details ["enum" ], property_value , self .__class__ )
557+ msg = "enum field `{}` must be one of {}, instead of `{}`" .format (
558+ path , details ["enum" ], property_value
564559 )
565- # raise TypeError(msg.format(property_name, details["enum"], property_value, self.__class__) )
560+ self ._append_error ( msg )
566561 if details ["type" ] in common_data_types and "format" not in details :
567- msg = "property {} shall be of type {} at {}" .format (property_name , details ["type" ], self .__class__ )
568- self .types_validation (property_value , details ["type" ], msg , details .get ("itemtype" ), details .get ("minimum" ), details .get ("maximum" ),
569- details .get ("minLength" ), details .get ("maxLength" ))
562+ self .types_validation (
563+ property_value , details ["type" ], path , details .get ("itemtype" ),
564+ details .get ("minimum" , details .get ("minLength" )),
565+ details .get ("maximum" , details .get ("maxLength" ))
566+ )
570567
571568 if details ["type" ] not in common_data_types :
572569 class_name = details ["type" ]
573570 # TODO Need to revisit importlib
574571 module = importlib .import_module (self .__module__ )
575572 object_class = getattr (module , class_name )
576573 if not isinstance (property_value , object_class ):
577- msg = "property {} shall be of type {}," " but got {} at {} "
574+ msg = "value of `{}` must be a valid {} type, instead of `{}` "
578575 self ._append_error (
579- msg .format (property_name , class_name , type (property_value ), self . __class__ )
576+ msg .format (path , class_name , type (property_value ))
580577 )
581- # raise TypeError(msg.format(property_name, class_name, type(property_value), self.__class__))
582578 if "format" in details :
583- msg = "Invalid {} format on property {}, expected {} at {}" .format (
584- property_value , property_name , details ["format" ], self .__class__
585- )
586579 _type = details ["type" ] if details ["type" ] is list else details ["format" ]
587- self .types_validation (property_value , _type , msg , details ["format" ], details .get ("minimum" ), details .get ("maximum" ),
588- details .get ("minLength" ), details .get ("maxLength" ))
580+ self .types_validation (
581+ property_value , _type , path , details ["format" ],
582+ details .get ("minimum" , details .get ("minLength" )),
583+ details .get ("maximum" , details .get ("maxLength" ))
584+ )
589585
590- def _validate (self , skip_exception = False ):
591- self ._validate_required ()
586+ def _validate (self , path , skip_exception = False ):
587+ self ._validate_required (path )
592588 for key , value in self ._properties .items ():
593589 if isinstance (value , OpenApiObject ):
594- value ._validate (True )
590+ value ._validate (path + ".%s" % key , True )
595591 elif isinstance (value , OpenApiIter ):
596- for item in value :
592+ for ind , item in enumerate ( value ) :
597593 if not isinstance (item , OpenApiObject ):
598594 continue
599- item ._validate (True )
600- self ._validate_types (key , value )
595+ item ._validate (path + ".%s[%d]" % ( key , ind ), True )
596+ self ._validate_types (path + ".%s" % ( key ), key , value )
601597 if skip_exception :
602598 return self ._validation_errors
603599 self ._raise_validation ()
604600
605601 def validate (self ):
606- return self ._validate ()
602+ return self ._validate (self . _JSON_NAME )
607603
608604 def get (self , name , with_default = False ):
609605 """
0 commit comments