github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/clients/python-legacy/lakefs_client/model_utils.py (about) 1 """ 2 lakeFS API 3 4 lakeFS HTTP API # noqa: E501 5 6 The version of the OpenAPI document: 1.0.0 7 Contact: services@treeverse.io 8 Generated by: https://openapi-generator.tech 9 """ 10 11 12 from datetime import date, datetime # noqa: F401 13 from copy import deepcopy 14 import inspect 15 import io 16 import os 17 import pprint 18 import re 19 import tempfile 20 21 from dateutil.parser import parse 22 23 from lakefs_client.exceptions import ( 24 ApiKeyError, 25 ApiAttributeError, 26 ApiTypeError, 27 ApiValueError, 28 ) 29 30 none_type = type(None) 31 file_type = io.IOBase 32 33 34 def convert_js_args_to_python_args(fn): 35 from functools import wraps 36 @wraps(fn) 37 def wrapped_init(_self, *args, **kwargs): 38 """ 39 An attribute named `self` received from the api will conflicts with the reserved `self` 40 parameter of a class method. During generation, `self` attributes are mapped 41 to `_self` in models. Here, we name `_self` instead of `self` to avoid conflicts. 42 """ 43 spec_property_naming = kwargs.get('_spec_property_naming', False) 44 if spec_property_naming: 45 kwargs = change_keys_js_to_python(kwargs, _self if isinstance(_self, type) else _self.__class__) 46 return fn(_self, *args, **kwargs) 47 return wrapped_init 48 49 50 class cached_property(object): 51 # this caches the result of the function call for fn with no inputs 52 # use this as a decorator on function methods that you want converted 53 # into cached properties 54 result_key = '_results' 55 56 def __init__(self, fn): 57 self._fn = fn 58 59 def __get__(self, instance, cls=None): 60 if self.result_key in vars(self): 61 return vars(self)[self.result_key] 62 else: 63 result = self._fn() 64 setattr(self, self.result_key, result) 65 return result 66 67 68 PRIMITIVE_TYPES = (list, float, int, bool, datetime, date, str, file_type) 69 70 def allows_single_value_input(cls): 71 """ 72 This function returns True if the input composed schema model or any 73 descendant model allows a value only input 74 This is true for cases where oneOf contains items like: 75 oneOf: 76 - float 77 - NumberWithValidation 78 - StringEnum 79 - ArrayModel 80 - null 81 TODO: lru_cache this 82 """ 83 if ( 84 issubclass(cls, ModelSimple) or 85 cls in PRIMITIVE_TYPES 86 ): 87 return True 88 elif issubclass(cls, ModelComposed): 89 if not cls._composed_schemas['oneOf']: 90 return False 91 return any(allows_single_value_input(c) for c in cls._composed_schemas['oneOf']) 92 return False 93 94 def composed_model_input_classes(cls): 95 """ 96 This function returns a list of the possible models that can be accepted as 97 inputs. 98 TODO: lru_cache this 99 """ 100 if issubclass(cls, ModelSimple) or cls in PRIMITIVE_TYPES: 101 return [cls] 102 elif issubclass(cls, ModelNormal): 103 if cls.discriminator is None: 104 return [cls] 105 else: 106 return get_discriminated_classes(cls) 107 elif issubclass(cls, ModelComposed): 108 if not cls._composed_schemas['oneOf']: 109 return [] 110 if cls.discriminator is None: 111 input_classes = [] 112 for c in cls._composed_schemas['oneOf']: 113 input_classes.extend(composed_model_input_classes(c)) 114 return input_classes 115 else: 116 return get_discriminated_classes(cls) 117 return [] 118 119 120 class OpenApiModel(object): 121 """The base class for all OpenAPIModels""" 122 123 def set_attribute(self, name, value): 124 # this is only used to set properties on self 125 126 path_to_item = [] 127 if self._path_to_item: 128 path_to_item.extend(self._path_to_item) 129 path_to_item.append(name) 130 131 if name in self.openapi_types: 132 required_types_mixed = self.openapi_types[name] 133 elif self.additional_properties_type is None: 134 raise ApiAttributeError( 135 "{0} has no attribute '{1}'".format( 136 type(self).__name__, name), 137 path_to_item 138 ) 139 elif self.additional_properties_type is not None: 140 required_types_mixed = self.additional_properties_type 141 142 if get_simple_class(name) != str: 143 error_msg = type_error_message( 144 var_name=name, 145 var_value=name, 146 valid_classes=(str,), 147 key_type=True 148 ) 149 raise ApiTypeError( 150 error_msg, 151 path_to_item=path_to_item, 152 valid_classes=(str,), 153 key_type=True 154 ) 155 156 if self._check_type: 157 value = validate_and_convert_types( 158 value, required_types_mixed, path_to_item, self._spec_property_naming, 159 self._check_type, configuration=self._configuration) 160 if (name,) in self.allowed_values: 161 check_allowed_values( 162 self.allowed_values, 163 (name,), 164 value 165 ) 166 if (name,) in self.validations: 167 check_validations( 168 self.validations, 169 (name,), 170 value, 171 self._configuration 172 ) 173 self.__dict__['_data_store'][name] = value 174 175 def __repr__(self): 176 """For `print` and `pprint`""" 177 return self.to_str() 178 179 def __ne__(self, other): 180 """Returns true if both objects are not equal""" 181 return not self == other 182 183 def __setattr__(self, attr, value): 184 """set the value of an attribute using dot notation: `instance.attr = val`""" 185 self[attr] = value 186 187 def __getattr__(self, attr): 188 """get the value of an attribute using dot notation: `instance.attr`""" 189 return self.__getitem__(attr) 190 191 def __copy__(self): 192 cls = self.__class__ 193 if self.get("_spec_property_naming", False): 194 return cls._new_from_openapi_data(**self.__dict__) 195 else: 196 return new_cls.__new__(cls, **self.__dict__) 197 198 def __deepcopy__(self, memo): 199 cls = self.__class__ 200 201 if self.get("_spec_property_naming", False): 202 new_inst = cls._new_from_openapi_data() 203 else: 204 new_inst = cls.__new__(cls) 205 206 for k, v in self.__dict__.items(): 207 setattr(new_inst, k, deepcopy(v, memo)) 208 return new_inst 209 210 211 def __new__(cls, *args, **kwargs): 212 # this function uses the discriminator to 213 # pick a new schema/class to instantiate because a discriminator 214 # propertyName value was passed in 215 216 if len(args) == 1: 217 arg = args[0] 218 if arg is None and is_type_nullable(cls): 219 # The input data is the 'null' value and the type is nullable. 220 return None 221 222 if issubclass(cls, ModelComposed) and allows_single_value_input(cls): 223 model_kwargs = {} 224 oneof_instance = get_oneof_instance(cls, model_kwargs, kwargs, model_arg=arg) 225 return oneof_instance 226 227 228 visited_composed_classes = kwargs.get('_visited_composed_classes', ()) 229 if ( 230 cls.discriminator is None or 231 cls in visited_composed_classes 232 ): 233 # Use case 1: this openapi schema (cls) does not have a discriminator 234 # Use case 2: we have already visited this class before and are sure that we 235 # want to instantiate it this time. We have visited this class deserializing 236 # a payload with a discriminator. During that process we traveled through 237 # this class but did not make an instance of it. Now we are making an 238 # instance of a composed class which contains cls in it, so this time make an instance of cls. 239 # 240 # Here's an example of use case 2: If Animal has a discriminator 241 # petType and we pass in "Dog", and the class Dog 242 # allOf includes Animal, we move through Animal 243 # once using the discriminator, and pick Dog. 244 # Then in the composed schema dog Dog, we will make an instance of the 245 # Animal class (because Dal has allOf: Animal) but this time we won't travel 246 # through Animal's discriminator because we passed in 247 # _visited_composed_classes = (Animal,) 248 249 return super(OpenApiModel, cls).__new__(cls) 250 251 # Get the name and value of the discriminator property. 252 # The discriminator name is obtained from the discriminator meta-data 253 # and the discriminator value is obtained from the input data. 254 discr_propertyname_py = list(cls.discriminator.keys())[0] 255 discr_propertyname_js = cls.attribute_map[discr_propertyname_py] 256 if discr_propertyname_js in kwargs: 257 discr_value = kwargs[discr_propertyname_js] 258 elif discr_propertyname_py in kwargs: 259 discr_value = kwargs[discr_propertyname_py] 260 else: 261 # The input data does not contain the discriminator property. 262 path_to_item = kwargs.get('_path_to_item', ()) 263 raise ApiValueError( 264 "Cannot deserialize input data due to missing discriminator. " 265 "The discriminator property '%s' is missing at path: %s" % 266 (discr_propertyname_js, path_to_item) 267 ) 268 269 # Implementation note: the last argument to get_discriminator_class 270 # is a list of visited classes. get_discriminator_class may recursively 271 # call itself and update the list of visited classes, and the initial 272 # value must be an empty list. Hence not using 'visited_composed_classes' 273 new_cls = get_discriminator_class( 274 cls, discr_propertyname_py, discr_value, []) 275 if new_cls is None: 276 path_to_item = kwargs.get('_path_to_item', ()) 277 disc_prop_value = kwargs.get( 278 discr_propertyname_js, kwargs.get(discr_propertyname_py)) 279 raise ApiValueError( 280 "Cannot deserialize input data due to invalid discriminator " 281 "value. The OpenAPI document has no mapping for discriminator " 282 "property '%s'='%s' at path: %s" % 283 (discr_propertyname_js, disc_prop_value, path_to_item) 284 ) 285 286 if new_cls in visited_composed_classes: 287 # if we are making an instance of a composed schema Descendent 288 # which allOf includes Ancestor, then Ancestor contains 289 # a discriminator that includes Descendent. 290 # So if we make an instance of Descendent, we have to make an 291 # instance of Ancestor to hold the allOf properties. 292 # This code detects that use case and makes the instance of Ancestor 293 # For example: 294 # When making an instance of Dog, _visited_composed_classes = (Dog,) 295 # then we make an instance of Animal to include in dog._composed_instances 296 # so when we are here, cls is Animal 297 # cls.discriminator != None 298 # cls not in _visited_composed_classes 299 # new_cls = Dog 300 # but we know we know that we already have Dog 301 # because it is in visited_composed_classes 302 # so make Animal here 303 return super(OpenApiModel, cls).__new__(cls) 304 305 # Build a list containing all oneOf and anyOf descendants. 306 oneof_anyof_classes = None 307 if cls._composed_schemas is not None: 308 oneof_anyof_classes = ( 309 cls._composed_schemas.get('oneOf', ()) + 310 cls._composed_schemas.get('anyOf', ())) 311 oneof_anyof_child = new_cls in oneof_anyof_classes 312 kwargs['_visited_composed_classes'] = visited_composed_classes + (cls,) 313 314 if cls._composed_schemas.get('allOf') and oneof_anyof_child: 315 # Validate that we can make self because when we make the 316 # new_cls it will not include the allOf validations in self 317 self_inst = super(OpenApiModel, cls).__new__(cls) 318 self_inst.__init__(*args, **kwargs) 319 320 if kwargs.get("_spec_property_naming", False): 321 # when true, implies new is from deserialization 322 new_inst = new_cls._new_from_openapi_data(*args, **kwargs) 323 else: 324 new_inst = new_cls.__new__(new_cls, *args, **kwargs) 325 new_inst.__init__(*args, **kwargs) 326 327 return new_inst 328 329 330 @classmethod 331 @convert_js_args_to_python_args 332 def _new_from_openapi_data(cls, *args, **kwargs): 333 # this function uses the discriminator to 334 # pick a new schema/class to instantiate because a discriminator 335 # propertyName value was passed in 336 337 if len(args) == 1: 338 arg = args[0] 339 if arg is None and is_type_nullable(cls): 340 # The input data is the 'null' value and the type is nullable. 341 return None 342 343 if issubclass(cls, ModelComposed) and allows_single_value_input(cls): 344 model_kwargs = {} 345 oneof_instance = get_oneof_instance(cls, model_kwargs, kwargs, model_arg=arg) 346 return oneof_instance 347 348 349 visited_composed_classes = kwargs.get('_visited_composed_classes', ()) 350 if ( 351 cls.discriminator is None or 352 cls in visited_composed_classes 353 ): 354 # Use case 1: this openapi schema (cls) does not have a discriminator 355 # Use case 2: we have already visited this class before and are sure that we 356 # want to instantiate it this time. We have visited this class deserializing 357 # a payload with a discriminator. During that process we traveled through 358 # this class but did not make an instance of it. Now we are making an 359 # instance of a composed class which contains cls in it, so this time make an instance of cls. 360 # 361 # Here's an example of use case 2: If Animal has a discriminator 362 # petType and we pass in "Dog", and the class Dog 363 # allOf includes Animal, we move through Animal 364 # once using the discriminator, and pick Dog. 365 # Then in the composed schema dog Dog, we will make an instance of the 366 # Animal class (because Dal has allOf: Animal) but this time we won't travel 367 # through Animal's discriminator because we passed in 368 # _visited_composed_classes = (Animal,) 369 370 return cls._from_openapi_data(*args, **kwargs) 371 372 # Get the name and value of the discriminator property. 373 # The discriminator name is obtained from the discriminator meta-data 374 # and the discriminator value is obtained from the input data. 375 discr_propertyname_py = list(cls.discriminator.keys())[0] 376 discr_propertyname_js = cls.attribute_map[discr_propertyname_py] 377 if discr_propertyname_js in kwargs: 378 discr_value = kwargs[discr_propertyname_js] 379 elif discr_propertyname_py in kwargs: 380 discr_value = kwargs[discr_propertyname_py] 381 else: 382 # The input data does not contain the discriminator property. 383 path_to_item = kwargs.get('_path_to_item', ()) 384 raise ApiValueError( 385 "Cannot deserialize input data due to missing discriminator. " 386 "The discriminator property '%s' is missing at path: %s" % 387 (discr_propertyname_js, path_to_item) 388 ) 389 390 # Implementation note: the last argument to get_discriminator_class 391 # is a list of visited classes. get_discriminator_class may recursively 392 # call itself and update the list of visited classes, and the initial 393 # value must be an empty list. Hence not using 'visited_composed_classes' 394 new_cls = get_discriminator_class( 395 cls, discr_propertyname_py, discr_value, []) 396 if new_cls is None: 397 path_to_item = kwargs.get('_path_to_item', ()) 398 disc_prop_value = kwargs.get( 399 discr_propertyname_js, kwargs.get(discr_propertyname_py)) 400 raise ApiValueError( 401 "Cannot deserialize input data due to invalid discriminator " 402 "value. The OpenAPI document has no mapping for discriminator " 403 "property '%s'='%s' at path: %s" % 404 (discr_propertyname_js, disc_prop_value, path_to_item) 405 ) 406 407 if new_cls in visited_composed_classes: 408 # if we are making an instance of a composed schema Descendent 409 # which allOf includes Ancestor, then Ancestor contains 410 # a discriminator that includes Descendent. 411 # So if we make an instance of Descendent, we have to make an 412 # instance of Ancestor to hold the allOf properties. 413 # This code detects that use case and makes the instance of Ancestor 414 # For example: 415 # When making an instance of Dog, _visited_composed_classes = (Dog,) 416 # then we make an instance of Animal to include in dog._composed_instances 417 # so when we are here, cls is Animal 418 # cls.discriminator != None 419 # cls not in _visited_composed_classes 420 # new_cls = Dog 421 # but we know we know that we already have Dog 422 # because it is in visited_composed_classes 423 # so make Animal here 424 return cls._from_openapi_data(*args, **kwargs) 425 426 # Build a list containing all oneOf and anyOf descendants. 427 oneof_anyof_classes = None 428 if cls._composed_schemas is not None: 429 oneof_anyof_classes = ( 430 cls._composed_schemas.get('oneOf', ()) + 431 cls._composed_schemas.get('anyOf', ())) 432 oneof_anyof_child = new_cls in oneof_anyof_classes 433 kwargs['_visited_composed_classes'] = visited_composed_classes + (cls,) 434 435 if cls._composed_schemas.get('allOf') and oneof_anyof_child: 436 # Validate that we can make self because when we make the 437 # new_cls it will not include the allOf validations in self 438 self_inst = cls._from_openapi_data(*args, **kwargs) 439 440 441 new_inst = new_cls._new_from_openapi_data(*args, **kwargs) 442 return new_inst 443 444 445 class ModelSimple(OpenApiModel): 446 """the parent class of models whose type != object in their 447 swagger/openapi""" 448 449 def __setitem__(self, name, value): 450 """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" 451 if name in self.required_properties: 452 self.__dict__[name] = value 453 return 454 455 self.set_attribute(name, value) 456 457 def get(self, name, default=None): 458 """returns the value of an attribute or some default value if the attribute was not set""" 459 if name in self.required_properties: 460 return self.__dict__[name] 461 462 return self.__dict__['_data_store'].get(name, default) 463 464 def __getitem__(self, name): 465 """get the value of an attribute using square-bracket notation: `instance[attr]`""" 466 if name in self: 467 return self.get(name) 468 469 raise ApiAttributeError( 470 "{0} has no attribute '{1}'".format( 471 type(self).__name__, name), 472 [e for e in [self._path_to_item, name] if e] 473 ) 474 475 def __contains__(self, name): 476 """used by `in` operator to check if an attribute value was set in an instance: `'attr' in instance`""" 477 if name in self.required_properties: 478 return name in self.__dict__ 479 480 return name in self.__dict__['_data_store'] 481 482 def to_str(self): 483 """Returns the string representation of the model""" 484 return str(self.value) 485 486 def __eq__(self, other): 487 """Returns true if both objects are equal""" 488 if not isinstance(other, self.__class__): 489 return False 490 491 this_val = self._data_store['value'] 492 that_val = other._data_store['value'] 493 types = set() 494 types.add(this_val.__class__) 495 types.add(that_val.__class__) 496 vals_equal = this_val == that_val 497 return vals_equal 498 499 500 class ModelNormal(OpenApiModel): 501 """the parent class of models whose type == object in their 502 swagger/openapi""" 503 504 def __setitem__(self, name, value): 505 """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" 506 if name in self.required_properties: 507 self.__dict__[name] = value 508 return 509 510 self.set_attribute(name, value) 511 512 def get(self, name, default=None): 513 """returns the value of an attribute or some default value if the attribute was not set""" 514 if name in self.required_properties: 515 return self.__dict__[name] 516 517 return self.__dict__['_data_store'].get(name, default) 518 519 def __getitem__(self, name): 520 """get the value of an attribute using square-bracket notation: `instance[attr]`""" 521 if name in self: 522 return self.get(name) 523 524 raise ApiAttributeError( 525 "{0} has no attribute '{1}'".format( 526 type(self).__name__, name), 527 [e for e in [self._path_to_item, name] if e] 528 ) 529 530 def __contains__(self, name): 531 """used by `in` operator to check if an attribute value was set in an instance: `'attr' in instance`""" 532 if name in self.required_properties: 533 return name in self.__dict__ 534 535 return name in self.__dict__['_data_store'] 536 537 def to_dict(self): 538 """Returns the model properties as a dict""" 539 return model_to_dict(self, serialize=False) 540 541 def to_str(self): 542 """Returns the string representation of the model""" 543 return pprint.pformat(self.to_dict()) 544 545 def __eq__(self, other): 546 """Returns true if both objects are equal""" 547 if not isinstance(other, self.__class__): 548 return False 549 550 if not set(self._data_store.keys()) == set(other._data_store.keys()): 551 return False 552 for _var_name, this_val in self._data_store.items(): 553 that_val = other._data_store[_var_name] 554 types = set() 555 types.add(this_val.__class__) 556 types.add(that_val.__class__) 557 vals_equal = this_val == that_val 558 if not vals_equal: 559 return False 560 return True 561 562 563 class ModelComposed(OpenApiModel): 564 """the parent class of models whose type == object in their 565 swagger/openapi and have oneOf/allOf/anyOf 566 567 When one sets a property we use var_name_to_model_instances to store the value in 568 the correct class instances + run any type checking + validation code. 569 When one gets a property we use var_name_to_model_instances to get the value 570 from the correct class instances. 571 This allows multiple composed schemas to contain the same property with additive 572 constraints on the value. 573 574 _composed_schemas (dict) stores the anyOf/allOf/oneOf classes 575 key (str): allOf/oneOf/anyOf 576 value (list): the classes in the XOf definition. 577 Note: none_type can be included when the openapi document version >= 3.1.0 578 _composed_instances (list): stores a list of instances of the composed schemas 579 defined in _composed_schemas. When properties are accessed in the self instance, 580 they are returned from the self._data_store or the data stores in the instances 581 in self._composed_schemas 582 _var_name_to_model_instances (dict): maps between a variable name on self and 583 the composed instances (self included) which contain that data 584 key (str): property name 585 value (list): list of class instances, self or instances in _composed_instances 586 which contain the value that the key is referring to. 587 """ 588 589 def __setitem__(self, name, value): 590 """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" 591 if name in self.required_properties: 592 self.__dict__[name] = value 593 return 594 595 """ 596 Use cases: 597 1. additional_properties_type is None (additionalProperties == False in spec) 598 Check for property presence in self.openapi_types 599 if not present then throw an error 600 if present set in self, set attribute 601 always set on composed schemas 602 2. additional_properties_type exists 603 set attribute on self 604 always set on composed schemas 605 """ 606 if self.additional_properties_type is None: 607 """ 608 For an attribute to exist on a composed schema it must: 609 - fulfill schema_requirements in the self composed schema not considering oneOf/anyOf/allOf schemas AND 610 - fulfill schema_requirements in each oneOf/anyOf/allOf schemas 611 612 schema_requirements: 613 For an attribute to exist on a schema it must: 614 - be present in properties at the schema OR 615 - have additionalProperties unset (defaults additionalProperties = any type) OR 616 - have additionalProperties set 617 """ 618 if name not in self.openapi_types: 619 raise ApiAttributeError( 620 "{0} has no attribute '{1}'".format( 621 type(self).__name__, name), 622 [e for e in [self._path_to_item, name] if e] 623 ) 624 # attribute must be set on self and composed instances 625 self.set_attribute(name, value) 626 for model_instance in self._composed_instances: 627 setattr(model_instance, name, value) 628 if name not in self._var_name_to_model_instances: 629 # we assigned an additional property 630 self.__dict__['_var_name_to_model_instances'][name] = self._composed_instances + [self] 631 return None 632 633 __unset_attribute_value__ = object() 634 635 def get(self, name, default=None): 636 """returns the value of an attribute or some default value if the attribute was not set""" 637 if name in self.required_properties: 638 return self.__dict__[name] 639 640 # get the attribute from the correct instance 641 model_instances = self._var_name_to_model_instances.get(name) 642 values = [] 643 # A composed model stores self and child (oneof/anyOf/allOf) models under 644 # self._var_name_to_model_instances. 645 # Any property must exist in self and all model instances 646 # The value stored in all model instances must be the same 647 if model_instances: 648 for model_instance in model_instances: 649 if name in model_instance._data_store: 650 v = model_instance._data_store[name] 651 if v not in values: 652 values.append(v) 653 len_values = len(values) 654 if len_values == 0: 655 return default 656 elif len_values == 1: 657 return values[0] 658 elif len_values > 1: 659 raise ApiValueError( 660 "Values stored for property {0} in {1} differ when looking " 661 "at self and self's composed instances. All values must be " 662 "the same".format(name, type(self).__name__), 663 [e for e in [self._path_to_item, name] if e] 664 ) 665 666 def __getitem__(self, name): 667 """get the value of an attribute using square-bracket notation: `instance[attr]`""" 668 value = self.get(name, self.__unset_attribute_value__) 669 if value is self.__unset_attribute_value__: 670 raise ApiAttributeError( 671 "{0} has no attribute '{1}'".format( 672 type(self).__name__, name), 673 [e for e in [self._path_to_item, name] if e] 674 ) 675 return value 676 677 def __contains__(self, name): 678 """used by `in` operator to check if an attribute value was set in an instance: `'attr' in instance`""" 679 680 if name in self.required_properties: 681 return name in self.__dict__ 682 683 model_instances = self._var_name_to_model_instances.get( 684 name, self._additional_properties_model_instances) 685 686 if model_instances: 687 for model_instance in model_instances: 688 if name in model_instance._data_store: 689 return True 690 691 return False 692 693 def to_dict(self): 694 """Returns the model properties as a dict""" 695 return model_to_dict(self, serialize=False) 696 697 def to_str(self): 698 """Returns the string representation of the model""" 699 return pprint.pformat(self.to_dict()) 700 701 def __eq__(self, other): 702 """Returns true if both objects are equal""" 703 if not isinstance(other, self.__class__): 704 return False 705 706 if not set(self._data_store.keys()) == set(other._data_store.keys()): 707 return False 708 for _var_name, this_val in self._data_store.items(): 709 that_val = other._data_store[_var_name] 710 types = set() 711 types.add(this_val.__class__) 712 types.add(that_val.__class__) 713 vals_equal = this_val == that_val 714 if not vals_equal: 715 return False 716 return True 717 718 719 COERCION_INDEX_BY_TYPE = { 720 ModelComposed: 0, 721 ModelNormal: 1, 722 ModelSimple: 2, 723 none_type: 3, # The type of 'None'. 724 list: 4, 725 dict: 5, 726 float: 6, 727 int: 7, 728 bool: 8, 729 datetime: 9, 730 date: 10, 731 str: 11, 732 file_type: 12, # 'file_type' is an alias for the built-in 'file' or 'io.IOBase' type. 733 } 734 735 # these are used to limit what type conversions we try to do 736 # when we have a valid type already and we want to try converting 737 # to another type 738 UPCONVERSION_TYPE_PAIRS = ( 739 (str, datetime), 740 (str, date), 741 (int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float. 742 (list, ModelComposed), 743 (dict, ModelComposed), 744 (str, ModelComposed), 745 (int, ModelComposed), 746 (float, ModelComposed), 747 (list, ModelComposed), 748 (list, ModelNormal), 749 (dict, ModelNormal), 750 (str, ModelSimple), 751 (int, ModelSimple), 752 (float, ModelSimple), 753 (list, ModelSimple), 754 ) 755 756 COERCIBLE_TYPE_PAIRS = { 757 False: ( # client instantiation of a model with client data 758 # (dict, ModelComposed), 759 # (list, ModelComposed), 760 # (dict, ModelNormal), 761 # (list, ModelNormal), 762 # (str, ModelSimple), 763 # (int, ModelSimple), 764 # (float, ModelSimple), 765 # (list, ModelSimple), 766 # (str, int), 767 # (str, float), 768 # (str, datetime), 769 # (str, date), 770 # (int, str), 771 # (float, str), 772 ), 773 True: ( # server -> client data 774 (dict, ModelComposed), 775 (list, ModelComposed), 776 (dict, ModelNormal), 777 (list, ModelNormal), 778 (str, ModelSimple), 779 (int, ModelSimple), 780 (float, ModelSimple), 781 (list, ModelSimple), 782 # (str, int), 783 # (str, float), 784 (str, datetime), 785 (str, date), 786 # (int, str), 787 # (float, str), 788 (str, file_type) 789 ), 790 } 791 792 793 def get_simple_class(input_value): 794 """Returns an input_value's simple class that we will use for type checking 795 Python2: 796 float and int will return int, where int is the python3 int backport 797 str and unicode will return str, where str is the python3 str backport 798 Note: float and int ARE both instances of int backport 799 Note: str_py2 and unicode_py2 are NOT both instances of str backport 800 801 Args: 802 input_value (class/class_instance): the item for which we will return 803 the simple class 804 """ 805 if isinstance(input_value, type): 806 # input_value is a class 807 return input_value 808 elif isinstance(input_value, tuple): 809 return tuple 810 elif isinstance(input_value, list): 811 return list 812 elif isinstance(input_value, dict): 813 return dict 814 elif isinstance(input_value, none_type): 815 return none_type 816 elif isinstance(input_value, file_type): 817 return file_type 818 elif isinstance(input_value, bool): 819 # this must be higher than the int check because 820 # isinstance(True, int) == True 821 return bool 822 elif isinstance(input_value, int): 823 return int 824 elif isinstance(input_value, datetime): 825 # this must be higher than the date check because 826 # isinstance(datetime_instance, date) == True 827 return datetime 828 elif isinstance(input_value, date): 829 return date 830 elif isinstance(input_value, str): 831 return str 832 return type(input_value) 833 834 835 def check_allowed_values(allowed_values, input_variable_path, input_values): 836 """Raises an exception if the input_values are not allowed 837 838 Args: 839 allowed_values (dict): the allowed_values dict 840 input_variable_path (tuple): the path to the input variable 841 input_values (list/str/int/float/date/datetime): the values that we 842 are checking to see if they are in allowed_values 843 """ 844 these_allowed_values = list(allowed_values[input_variable_path].values()) 845 if (isinstance(input_values, list) 846 and not set(input_values).issubset( 847 set(these_allowed_values))): 848 invalid_values = ", ".join( 849 map(str, set(input_values) - set(these_allowed_values))), 850 raise ApiValueError( 851 "Invalid values for `%s` [%s], must be a subset of [%s]" % 852 ( 853 input_variable_path[0], 854 invalid_values, 855 ", ".join(map(str, these_allowed_values)) 856 ) 857 ) 858 elif (isinstance(input_values, dict) 859 and not set( 860 input_values.keys()).issubset(set(these_allowed_values))): 861 invalid_values = ", ".join( 862 map(str, set(input_values.keys()) - set(these_allowed_values))) 863 raise ApiValueError( 864 "Invalid keys in `%s` [%s], must be a subset of [%s]" % 865 ( 866 input_variable_path[0], 867 invalid_values, 868 ", ".join(map(str, these_allowed_values)) 869 ) 870 ) 871 elif (not isinstance(input_values, (list, dict)) 872 and input_values not in these_allowed_values): 873 raise ApiValueError( 874 "Invalid value for `%s` (%s), must be one of %s" % 875 ( 876 input_variable_path[0], 877 input_values, 878 these_allowed_values 879 ) 880 ) 881 882 883 def is_json_validation_enabled(schema_keyword, configuration=None): 884 """Returns true if JSON schema validation is enabled for the specified 885 validation keyword. This can be used to skip JSON schema structural validation 886 as requested in the configuration. 887 888 Args: 889 schema_keyword (string): the name of a JSON schema validation keyword. 890 configuration (Configuration): the configuration class. 891 """ 892 893 return (configuration is None or 894 not hasattr(configuration, '_disabled_client_side_validations') or 895 schema_keyword not in configuration._disabled_client_side_validations) 896 897 898 def check_validations( 899 validations, input_variable_path, input_values, 900 configuration=None): 901 """Raises an exception if the input_values are invalid 902 903 Args: 904 validations (dict): the validation dictionary. 905 input_variable_path (tuple): the path to the input variable. 906 input_values (list/str/int/float/date/datetime): the values that we 907 are checking. 908 configuration (Configuration): the configuration class. 909 """ 910 911 if input_values is None: 912 return 913 914 current_validations = validations[input_variable_path] 915 if (is_json_validation_enabled('multipleOf', configuration) and 916 'multiple_of' in current_validations and 917 isinstance(input_values, (int, float)) and 918 not (float(input_values) / current_validations['multiple_of']).is_integer()): 919 # Note 'multipleOf' will be as good as the floating point arithmetic. 920 raise ApiValueError( 921 "Invalid value for `%s`, value must be a multiple of " 922 "`%s`" % ( 923 input_variable_path[0], 924 current_validations['multiple_of'] 925 ) 926 ) 927 928 if (is_json_validation_enabled('maxLength', configuration) and 929 'max_length' in current_validations and 930 len(input_values) > current_validations['max_length']): 931 raise ApiValueError( 932 "Invalid value for `%s`, length must be less than or equal to " 933 "`%s`" % ( 934 input_variable_path[0], 935 current_validations['max_length'] 936 ) 937 ) 938 939 if (is_json_validation_enabled('minLength', configuration) and 940 'min_length' in current_validations and 941 len(input_values) < current_validations['min_length']): 942 raise ApiValueError( 943 "Invalid value for `%s`, length must be greater than or equal to " 944 "`%s`" % ( 945 input_variable_path[0], 946 current_validations['min_length'] 947 ) 948 ) 949 950 if (is_json_validation_enabled('maxItems', configuration) and 951 'max_items' in current_validations and 952 len(input_values) > current_validations['max_items']): 953 raise ApiValueError( 954 "Invalid value for `%s`, number of items must be less than or " 955 "equal to `%s`" % ( 956 input_variable_path[0], 957 current_validations['max_items'] 958 ) 959 ) 960 961 if (is_json_validation_enabled('minItems', configuration) and 962 'min_items' in current_validations and 963 len(input_values) < current_validations['min_items']): 964 raise ValueError( 965 "Invalid value for `%s`, number of items must be greater than or " 966 "equal to `%s`" % ( 967 input_variable_path[0], 968 current_validations['min_items'] 969 ) 970 ) 971 972 items = ('exclusive_maximum', 'inclusive_maximum', 'exclusive_minimum', 973 'inclusive_minimum') 974 if (any(item in current_validations for item in items)): 975 if isinstance(input_values, list): 976 max_val = max(input_values) 977 min_val = min(input_values) 978 elif isinstance(input_values, dict): 979 max_val = max(input_values.values()) 980 min_val = min(input_values.values()) 981 else: 982 max_val = input_values 983 min_val = input_values 984 985 if (is_json_validation_enabled('exclusiveMaximum', configuration) and 986 'exclusive_maximum' in current_validations and 987 max_val >= current_validations['exclusive_maximum']): 988 raise ApiValueError( 989 "Invalid value for `%s`, must be a value less than `%s`" % ( 990 input_variable_path[0], 991 current_validations['exclusive_maximum'] 992 ) 993 ) 994 995 if (is_json_validation_enabled('maximum', configuration) and 996 'inclusive_maximum' in current_validations and 997 max_val > current_validations['inclusive_maximum']): 998 raise ApiValueError( 999 "Invalid value for `%s`, must be a value less than or equal to " 1000 "`%s`" % ( 1001 input_variable_path[0], 1002 current_validations['inclusive_maximum'] 1003 ) 1004 ) 1005 1006 if (is_json_validation_enabled('exclusiveMinimum', configuration) and 1007 'exclusive_minimum' in current_validations and 1008 min_val <= current_validations['exclusive_minimum']): 1009 raise ApiValueError( 1010 "Invalid value for `%s`, must be a value greater than `%s`" % 1011 ( 1012 input_variable_path[0], 1013 current_validations['exclusive_maximum'] 1014 ) 1015 ) 1016 1017 if (is_json_validation_enabled('minimum', configuration) and 1018 'inclusive_minimum' in current_validations and 1019 min_val < current_validations['inclusive_minimum']): 1020 raise ApiValueError( 1021 "Invalid value for `%s`, must be a value greater than or equal " 1022 "to `%s`" % ( 1023 input_variable_path[0], 1024 current_validations['inclusive_minimum'] 1025 ) 1026 ) 1027 flags = current_validations.get('regex', {}).get('flags', 0) 1028 if (is_json_validation_enabled('pattern', configuration) and 1029 'regex' in current_validations and 1030 not re.search(current_validations['regex']['pattern'], 1031 input_values, flags=flags)): 1032 err_msg = r"Invalid value for `%s`, must match regular expression `%s`" % ( 1033 input_variable_path[0], 1034 current_validations['regex']['pattern'] 1035 ) 1036 if flags != 0: 1037 # Don't print the regex flags if the flags are not 1038 # specified in the OAS document. 1039 err_msg = r"%s with flags=`%s`" % (err_msg, flags) 1040 raise ApiValueError(err_msg) 1041 1042 1043 def order_response_types(required_types): 1044 """Returns the required types sorted in coercion order 1045 1046 Args: 1047 required_types (list/tuple): collection of classes or instance of 1048 list or dict with class information inside it. 1049 1050 Returns: 1051 (list): coercion order sorted collection of classes or instance 1052 of list or dict with class information inside it. 1053 """ 1054 1055 def index_getter(class_or_instance): 1056 if isinstance(class_or_instance, list): 1057 return COERCION_INDEX_BY_TYPE[list] 1058 elif isinstance(class_or_instance, dict): 1059 return COERCION_INDEX_BY_TYPE[dict] 1060 elif (inspect.isclass(class_or_instance) 1061 and issubclass(class_or_instance, ModelComposed)): 1062 return COERCION_INDEX_BY_TYPE[ModelComposed] 1063 elif (inspect.isclass(class_or_instance) 1064 and issubclass(class_or_instance, ModelNormal)): 1065 return COERCION_INDEX_BY_TYPE[ModelNormal] 1066 elif (inspect.isclass(class_or_instance) 1067 and issubclass(class_or_instance, ModelSimple)): 1068 return COERCION_INDEX_BY_TYPE[ModelSimple] 1069 elif class_or_instance in COERCION_INDEX_BY_TYPE: 1070 return COERCION_INDEX_BY_TYPE[class_or_instance] 1071 raise ApiValueError("Unsupported type: %s" % class_or_instance) 1072 1073 sorted_types = sorted( 1074 required_types, 1075 key=lambda class_or_instance: index_getter(class_or_instance) 1076 ) 1077 return sorted_types 1078 1079 1080 def remove_uncoercible(required_types_classes, current_item, spec_property_naming, 1081 must_convert=True): 1082 """Only keeps the type conversions that are possible 1083 1084 Args: 1085 required_types_classes (tuple): tuple of classes that are required 1086 these should be ordered by COERCION_INDEX_BY_TYPE 1087 spec_property_naming (bool): True if the variable names in the input 1088 data are serialized names as specified in the OpenAPI document. 1089 False if the variables names in the input data are python 1090 variable names in PEP-8 snake case. 1091 current_item (any): the current item (input data) to be converted 1092 1093 Keyword Args: 1094 must_convert (bool): if True the item to convert is of the wrong 1095 type and we want a big list of coercibles 1096 if False, we want a limited list of coercibles 1097 1098 Returns: 1099 (list): the remaining coercible required types, classes only 1100 """ 1101 current_type_simple = get_simple_class(current_item) 1102 1103 results_classes = [] 1104 for required_type_class in required_types_classes: 1105 # convert our models to OpenApiModel 1106 required_type_class_simplified = required_type_class 1107 if isinstance(required_type_class_simplified, type): 1108 if issubclass(required_type_class_simplified, ModelComposed): 1109 required_type_class_simplified = ModelComposed 1110 elif issubclass(required_type_class_simplified, ModelNormal): 1111 required_type_class_simplified = ModelNormal 1112 elif issubclass(required_type_class_simplified, ModelSimple): 1113 required_type_class_simplified = ModelSimple 1114 1115 if required_type_class_simplified == current_type_simple: 1116 # don't consider converting to one's own class 1117 continue 1118 1119 class_pair = (current_type_simple, required_type_class_simplified) 1120 if must_convert and class_pair in COERCIBLE_TYPE_PAIRS[spec_property_naming]: 1121 results_classes.append(required_type_class) 1122 elif class_pair in UPCONVERSION_TYPE_PAIRS: 1123 results_classes.append(required_type_class) 1124 return results_classes 1125 1126 def get_discriminated_classes(cls): 1127 """ 1128 Returns all the classes that a discriminator converts to 1129 TODO: lru_cache this 1130 """ 1131 possible_classes = [] 1132 key = list(cls.discriminator.keys())[0] 1133 if is_type_nullable(cls): 1134 possible_classes.append(cls) 1135 for discr_cls in cls.discriminator[key].values(): 1136 if hasattr(discr_cls, 'discriminator') and discr_cls.discriminator is not None: 1137 possible_classes.extend(get_discriminated_classes(discr_cls)) 1138 else: 1139 possible_classes.append(discr_cls) 1140 return possible_classes 1141 1142 1143 def get_possible_classes(cls, from_server_context): 1144 # TODO: lru_cache this 1145 possible_classes = [cls] 1146 if from_server_context: 1147 return possible_classes 1148 if hasattr(cls, 'discriminator') and cls.discriminator is not None: 1149 possible_classes = [] 1150 possible_classes.extend(get_discriminated_classes(cls)) 1151 elif issubclass(cls, ModelComposed): 1152 possible_classes.extend(composed_model_input_classes(cls)) 1153 return possible_classes 1154 1155 1156 def get_required_type_classes(required_types_mixed, spec_property_naming): 1157 """Converts the tuple required_types into a tuple and a dict described 1158 below 1159 1160 Args: 1161 required_types_mixed (tuple/list): will contain either classes or 1162 instance of list or dict 1163 spec_property_naming (bool): if True these values came from the 1164 server, and we use the data types in our endpoints. 1165 If False, we are client side and we need to include 1166 oneOf and discriminator classes inside the data types in our endpoints 1167 1168 Returns: 1169 (valid_classes, dict_valid_class_to_child_types_mixed): 1170 valid_classes (tuple): the valid classes that the current item 1171 should be 1172 dict_valid_class_to_child_types_mixed (dict): 1173 valid_class (class): this is the key 1174 child_types_mixed (list/dict/tuple): describes the valid child 1175 types 1176 """ 1177 valid_classes = [] 1178 child_req_types_by_current_type = {} 1179 for required_type in required_types_mixed: 1180 if isinstance(required_type, list): 1181 valid_classes.append(list) 1182 child_req_types_by_current_type[list] = required_type 1183 elif isinstance(required_type, tuple): 1184 valid_classes.append(tuple) 1185 child_req_types_by_current_type[tuple] = required_type 1186 elif isinstance(required_type, dict): 1187 valid_classes.append(dict) 1188 child_req_types_by_current_type[dict] = required_type[str] 1189 else: 1190 valid_classes.extend(get_possible_classes(required_type, spec_property_naming)) 1191 return tuple(valid_classes), child_req_types_by_current_type 1192 1193 1194 def change_keys_js_to_python(input_dict, model_class): 1195 """ 1196 Converts from javascript_key keys in the input_dict to python_keys in 1197 the output dict using the mapping in model_class. 1198 If the input_dict contains a key which does not declared in the model_class, 1199 the key is added to the output dict as is. The assumption is the model_class 1200 may have undeclared properties (additionalProperties attribute in the OAS 1201 document). 1202 """ 1203 1204 if getattr(model_class, 'attribute_map', None) is None: 1205 return input_dict 1206 output_dict = {} 1207 reversed_attr_map = {value: key for key, value in 1208 model_class.attribute_map.items()} 1209 for javascript_key, value in input_dict.items(): 1210 python_key = reversed_attr_map.get(javascript_key) 1211 if python_key is None: 1212 # if the key is unknown, it is in error or it is an 1213 # additionalProperties variable 1214 python_key = javascript_key 1215 output_dict[python_key] = value 1216 return output_dict 1217 1218 1219 def get_type_error(var_value, path_to_item, valid_classes, key_type=False): 1220 error_msg = type_error_message( 1221 var_name=path_to_item[-1], 1222 var_value=var_value, 1223 valid_classes=valid_classes, 1224 key_type=key_type 1225 ) 1226 return ApiTypeError( 1227 error_msg, 1228 path_to_item=path_to_item, 1229 valid_classes=valid_classes, 1230 key_type=key_type 1231 ) 1232 1233 1234 def deserialize_primitive(data, klass, path_to_item): 1235 """Deserializes string to primitive type. 1236 1237 :param data: str/int/float 1238 :param klass: str/class the class to convert to 1239 1240 :return: int, float, str, bool, date, datetime 1241 """ 1242 additional_message = "" 1243 try: 1244 if klass in {datetime, date}: 1245 additional_message = ( 1246 "If you need your parameter to have a fallback " 1247 "string value, please set its type as `type: {}` in your " 1248 "spec. That allows the value to be any type. " 1249 ) 1250 if klass == datetime: 1251 if len(data) < 8: 1252 raise ValueError("This is not a datetime") 1253 # The string should be in iso8601 datetime format. 1254 parsed_datetime = parse(data) 1255 date_only = ( 1256 parsed_datetime.hour == 0 and 1257 parsed_datetime.minute == 0 and 1258 parsed_datetime.second == 0 and 1259 parsed_datetime.tzinfo is None and 1260 8 <= len(data) <= 10 1261 ) 1262 if date_only: 1263 raise ValueError("This is a date, not a datetime") 1264 return parsed_datetime 1265 elif klass == date: 1266 if len(data) < 8: 1267 raise ValueError("This is not a date") 1268 return parse(data).date() 1269 else: 1270 converted_value = klass(data) 1271 if isinstance(data, str) and klass == float: 1272 if str(converted_value) != data: 1273 # '7' -> 7.0 -> '7.0' != '7' 1274 raise ValueError('This is not a float') 1275 return converted_value 1276 except (OverflowError, ValueError) as ex: 1277 # parse can raise OverflowError 1278 raise ApiValueError( 1279 "{0}Failed to parse {1} as {2}".format( 1280 additional_message, repr(data), klass.__name__ 1281 ), 1282 path_to_item=path_to_item 1283 ) from ex 1284 1285 1286 def get_discriminator_class(model_class, 1287 discr_name, 1288 discr_value, cls_visited): 1289 """Returns the child class specified by the discriminator. 1290 1291 Args: 1292 model_class (OpenApiModel): the model class. 1293 discr_name (string): the name of the discriminator property. 1294 discr_value (any): the discriminator value. 1295 cls_visited (list): list of model classes that have been visited. 1296 Used to determine the discriminator class without 1297 visiting circular references indefinitely. 1298 1299 Returns: 1300 used_model_class (class/None): the chosen child class that will be used 1301 to deserialize the data, for example dog.Dog. 1302 If a class is not found, None is returned. 1303 """ 1304 1305 if model_class in cls_visited: 1306 # The class has already been visited and no suitable class was found. 1307 return None 1308 cls_visited.append(model_class) 1309 used_model_class = None 1310 if discr_name in model_class.discriminator: 1311 class_name_to_discr_class = model_class.discriminator[discr_name] 1312 used_model_class = class_name_to_discr_class.get(discr_value) 1313 if used_model_class is None: 1314 # We didn't find a discriminated class in class_name_to_discr_class. 1315 # So look in the ancestor or descendant discriminators 1316 # The discriminator mapping may exist in a descendant (anyOf, oneOf) 1317 # or ancestor (allOf). 1318 # Ancestor example: in the GrandparentAnimal -> ParentPet -> ChildCat 1319 # hierarchy, the discriminator mappings may be defined at any level 1320 # in the hierarchy. 1321 # Descendant example: mammal -> whale/zebra/Pig -> BasquePig/DanishPig 1322 # if we try to make BasquePig from mammal, we need to travel through 1323 # the oneOf descendant discriminators to find BasquePig 1324 descendant_classes = model_class._composed_schemas.get('oneOf', ()) + \ 1325 model_class._composed_schemas.get('anyOf', ()) 1326 ancestor_classes = model_class._composed_schemas.get('allOf', ()) 1327 possible_classes = descendant_classes + ancestor_classes 1328 for cls in possible_classes: 1329 # Check if the schema has inherited discriminators. 1330 if hasattr(cls, 'discriminator') and cls.discriminator is not None: 1331 used_model_class = get_discriminator_class( 1332 cls, discr_name, discr_value, cls_visited) 1333 if used_model_class is not None: 1334 return used_model_class 1335 return used_model_class 1336 1337 1338 def deserialize_model(model_data, model_class, path_to_item, check_type, 1339 configuration, spec_property_naming): 1340 """Deserializes model_data to model instance. 1341 1342 Args: 1343 model_data (int/str/float/bool/none_type/list/dict): data to instantiate the model 1344 model_class (OpenApiModel): the model class 1345 path_to_item (list): path to the model in the received data 1346 check_type (bool): whether to check the data tupe for the values in 1347 the model 1348 configuration (Configuration): the instance to use to convert files 1349 spec_property_naming (bool): True if the variable names in the input 1350 data are serialized names as specified in the OpenAPI document. 1351 False if the variables names in the input data are python 1352 variable names in PEP-8 snake case. 1353 1354 Returns: 1355 model instance 1356 1357 Raise: 1358 ApiTypeError 1359 ApiValueError 1360 ApiKeyError 1361 """ 1362 1363 kw_args = dict(_check_type=check_type, 1364 _path_to_item=path_to_item, 1365 _configuration=configuration, 1366 _spec_property_naming=spec_property_naming) 1367 1368 if issubclass(model_class, ModelSimple): 1369 return model_class._new_from_openapi_data(model_data, **kw_args) 1370 elif isinstance(model_data, list): 1371 return model_class._new_from_openapi_data(*model_data, **kw_args) 1372 if isinstance(model_data, dict): 1373 kw_args.update(model_data) 1374 return model_class._new_from_openapi_data(**kw_args) 1375 elif isinstance(model_data, PRIMITIVE_TYPES): 1376 return model_class._new_from_openapi_data(model_data, **kw_args) 1377 1378 1379 def deserialize_file(response_data, configuration, content_disposition=None): 1380 """Deserializes body to file 1381 1382 Saves response body into a file in a temporary folder, 1383 using the filename from the `Content-Disposition` header if provided. 1384 1385 Args: 1386 param response_data (str): the file data to write 1387 configuration (Configuration): the instance to use to convert files 1388 1389 Keyword Args: 1390 content_disposition (str): the value of the Content-Disposition 1391 header 1392 1393 Returns: 1394 (file_type): the deserialized file which is open 1395 The user is responsible for closing and reading the file 1396 """ 1397 fd, path = tempfile.mkstemp(dir=configuration.temp_folder_path) 1398 os.close(fd) 1399 os.remove(path) 1400 1401 if content_disposition: 1402 filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', 1403 content_disposition).group(1) 1404 path = os.path.join(os.path.dirname(path), filename) 1405 1406 with open(path, "wb") as f: 1407 if isinstance(response_data, str): 1408 # change str to bytes so we can write it 1409 response_data = response_data.encode('utf-8') 1410 f.write(response_data) 1411 1412 f = open(path, "rb") 1413 return f 1414 1415 1416 def attempt_convert_item(input_value, valid_classes, path_to_item, 1417 configuration, spec_property_naming, key_type=False, 1418 must_convert=False, check_type=True): 1419 """ 1420 Args: 1421 input_value (any): the data to convert 1422 valid_classes (any): the classes that are valid 1423 path_to_item (list): the path to the item to convert 1424 configuration (Configuration): the instance to use to convert files 1425 spec_property_naming (bool): True if the variable names in the input 1426 data are serialized names as specified in the OpenAPI document. 1427 False if the variables names in the input data are python 1428 variable names in PEP-8 snake case. 1429 key_type (bool): if True we need to convert a key type (not supported) 1430 must_convert (bool): if True we must convert 1431 check_type (bool): if True we check the type or the returned data in 1432 ModelComposed/ModelNormal/ModelSimple instances 1433 1434 Returns: 1435 instance (any) the fixed item 1436 1437 Raises: 1438 ApiTypeError 1439 ApiValueError 1440 ApiKeyError 1441 """ 1442 valid_classes_ordered = order_response_types(valid_classes) 1443 valid_classes_coercible = remove_uncoercible( 1444 valid_classes_ordered, input_value, spec_property_naming) 1445 if not valid_classes_coercible or key_type: 1446 # we do not handle keytype errors, json will take care 1447 # of this for us 1448 if configuration is None or not configuration.discard_unknown_keys: 1449 raise get_type_error(input_value, path_to_item, valid_classes, 1450 key_type=key_type) 1451 for valid_class in valid_classes_coercible: 1452 try: 1453 if issubclass(valid_class, OpenApiModel): 1454 return deserialize_model(input_value, valid_class, 1455 path_to_item, check_type, 1456 configuration, spec_property_naming) 1457 elif valid_class == file_type: 1458 return deserialize_file(input_value, configuration) 1459 return deserialize_primitive(input_value, valid_class, 1460 path_to_item) 1461 except (ApiTypeError, ApiValueError, ApiKeyError) as conversion_exc: 1462 if must_convert: 1463 raise conversion_exc 1464 # if we have conversion errors when must_convert == False 1465 # we ignore the exception and move on to the next class 1466 continue 1467 # we were unable to convert, must_convert == False 1468 return input_value 1469 1470 1471 def is_type_nullable(input_type): 1472 """ 1473 Returns true if None is an allowed value for the specified input_type. 1474 1475 A type is nullable if at least one of the following conditions is true: 1476 1. The OAS 'nullable' attribute has been specified, 1477 1. The type is the 'null' type, 1478 1. The type is a anyOf/oneOf composed schema, and a child schema is 1479 the 'null' type. 1480 Args: 1481 input_type (type): the class of the input_value that we are 1482 checking 1483 Returns: 1484 bool 1485 """ 1486 if input_type is none_type: 1487 return True 1488 if issubclass(input_type, OpenApiModel) and input_type._nullable: 1489 return True 1490 if issubclass(input_type, ModelComposed): 1491 # If oneOf/anyOf, check if the 'null' type is one of the allowed types. 1492 for t in input_type._composed_schemas.get('oneOf', ()): 1493 if is_type_nullable(t): return True 1494 for t in input_type._composed_schemas.get('anyOf', ()): 1495 if is_type_nullable(t): return True 1496 return False 1497 1498 1499 def is_valid_type(input_class_simple, valid_classes): 1500 """ 1501 Args: 1502 input_class_simple (class): the class of the input_value that we are 1503 checking 1504 valid_classes (tuple): the valid classes that the current item 1505 should be 1506 Returns: 1507 bool 1508 """ 1509 if issubclass(input_class_simple, OpenApiModel) and \ 1510 valid_classes == (bool, date, datetime, dict, float, int, list, str, none_type,): 1511 return True 1512 valid_type = input_class_simple in valid_classes 1513 if not valid_type and ( 1514 issubclass(input_class_simple, OpenApiModel) or 1515 input_class_simple is none_type): 1516 for valid_class in valid_classes: 1517 if input_class_simple is none_type and is_type_nullable(valid_class): 1518 # Schema is oneOf/anyOf and the 'null' type is one of the allowed types. 1519 return True 1520 if not (issubclass(valid_class, OpenApiModel) and valid_class.discriminator): 1521 continue 1522 discr_propertyname_py = list(valid_class.discriminator.keys())[0] 1523 discriminator_classes = ( 1524 valid_class.discriminator[discr_propertyname_py].values() 1525 ) 1526 valid_type = is_valid_type(input_class_simple, discriminator_classes) 1527 if valid_type: 1528 return True 1529 return valid_type 1530 1531 1532 def validate_and_convert_types(input_value, required_types_mixed, path_to_item, 1533 spec_property_naming, _check_type, configuration=None): 1534 """Raises a TypeError is there is a problem, otherwise returns value 1535 1536 Args: 1537 input_value (any): the data to validate/convert 1538 required_types_mixed (list/dict/tuple): A list of 1539 valid classes, or a list tuples of valid classes, or a dict where 1540 the value is a tuple of value classes 1541 path_to_item: (list) the path to the data being validated 1542 this stores a list of keys or indices to get to the data being 1543 validated 1544 spec_property_naming (bool): True if the variable names in the input 1545 data are serialized names as specified in the OpenAPI document. 1546 False if the variables names in the input data are python 1547 variable names in PEP-8 snake case. 1548 _check_type: (boolean) if true, type will be checked and conversion 1549 will be attempted. 1550 configuration: (Configuration): the configuration class to use 1551 when converting file_type items. 1552 If passed, conversion will be attempted when possible 1553 If not passed, no conversions will be attempted and 1554 exceptions will be raised 1555 1556 Returns: 1557 the correctly typed value 1558 1559 Raises: 1560 ApiTypeError 1561 """ 1562 results = get_required_type_classes(required_types_mixed, spec_property_naming) 1563 valid_classes, child_req_types_by_current_type = results 1564 1565 input_class_simple = get_simple_class(input_value) 1566 valid_type = is_valid_type(input_class_simple, valid_classes) 1567 if not valid_type: 1568 if configuration: 1569 # if input_value is not valid_type try to convert it 1570 converted_instance = attempt_convert_item( 1571 input_value, 1572 valid_classes, 1573 path_to_item, 1574 configuration, 1575 spec_property_naming, 1576 key_type=False, 1577 must_convert=True, 1578 check_type=_check_type 1579 ) 1580 return converted_instance 1581 else: 1582 raise get_type_error(input_value, path_to_item, valid_classes, 1583 key_type=False) 1584 1585 # input_value's type is in valid_classes 1586 if len(valid_classes) > 1 and configuration: 1587 # there are valid classes which are not the current class 1588 valid_classes_coercible = remove_uncoercible( 1589 valid_classes, input_value, spec_property_naming, must_convert=False) 1590 if valid_classes_coercible: 1591 converted_instance = attempt_convert_item( 1592 input_value, 1593 valid_classes_coercible, 1594 path_to_item, 1595 configuration, 1596 spec_property_naming, 1597 key_type=False, 1598 must_convert=False, 1599 check_type=_check_type 1600 ) 1601 return converted_instance 1602 1603 if child_req_types_by_current_type == {}: 1604 # all types are of the required types and there are no more inner 1605 # variables left to look at 1606 return input_value 1607 inner_required_types = child_req_types_by_current_type.get( 1608 type(input_value) 1609 ) 1610 if inner_required_types is None: 1611 # for this type, there are not more inner variables left to look at 1612 return input_value 1613 if isinstance(input_value, list): 1614 if input_value == []: 1615 # allow an empty list 1616 return input_value 1617 for index, inner_value in enumerate(input_value): 1618 inner_path = list(path_to_item) 1619 inner_path.append(index) 1620 input_value[index] = validate_and_convert_types( 1621 inner_value, 1622 inner_required_types, 1623 inner_path, 1624 spec_property_naming, 1625 _check_type, 1626 configuration=configuration 1627 ) 1628 elif isinstance(input_value, dict): 1629 if input_value == {}: 1630 # allow an empty dict 1631 return input_value 1632 for inner_key, inner_val in input_value.items(): 1633 inner_path = list(path_to_item) 1634 inner_path.append(inner_key) 1635 if get_simple_class(inner_key) != str: 1636 raise get_type_error(inner_key, inner_path, valid_classes, 1637 key_type=True) 1638 input_value[inner_key] = validate_and_convert_types( 1639 inner_val, 1640 inner_required_types, 1641 inner_path, 1642 spec_property_naming, 1643 _check_type, 1644 configuration=configuration 1645 ) 1646 return input_value 1647 1648 1649 def model_to_dict(model_instance, serialize=True): 1650 """Returns the model properties as a dict 1651 1652 Args: 1653 model_instance (one of your model instances): the model instance that 1654 will be converted to a dict. 1655 1656 Keyword Args: 1657 serialize (bool): if True, the keys in the dict will be values from 1658 attribute_map 1659 """ 1660 result = {} 1661 1662 model_instances = [model_instance] 1663 if model_instance._composed_schemas: 1664 model_instances.extend(model_instance._composed_instances) 1665 seen_json_attribute_names = set() 1666 used_fallback_python_attribute_names = set() 1667 py_to_json_map = {} 1668 for model_instance in model_instances: 1669 for attr, value in model_instance._data_store.items(): 1670 if serialize: 1671 # we use get here because additional property key names do not 1672 # exist in attribute_map 1673 try: 1674 attr = model_instance.attribute_map[attr] 1675 py_to_json_map.update(model_instance.attribute_map) 1676 seen_json_attribute_names.add(attr) 1677 except KeyError: 1678 used_fallback_python_attribute_names.add(attr) 1679 if isinstance(value, list): 1680 if not value: 1681 # empty list or None 1682 result[attr] = value 1683 else: 1684 res = [] 1685 for v in value: 1686 if isinstance(v, PRIMITIVE_TYPES) or v is None: 1687 res.append(v) 1688 elif isinstance(v, ModelSimple): 1689 res.append(v.value) 1690 else: 1691 res.append(model_to_dict(v, serialize=serialize)) 1692 result[attr] = res 1693 elif isinstance(value, dict): 1694 result[attr] = dict(map( 1695 lambda item: (item[0], 1696 model_to_dict(item[1], serialize=serialize)) 1697 if hasattr(item[1], '_data_store') else item, 1698 value.items() 1699 )) 1700 elif isinstance(value, ModelSimple): 1701 result[attr] = value.value 1702 elif hasattr(value, '_data_store'): 1703 result[attr] = model_to_dict(value, serialize=serialize) 1704 else: 1705 result[attr] = value 1706 if serialize: 1707 for python_key in used_fallback_python_attribute_names: 1708 json_key = py_to_json_map.get(python_key) 1709 if json_key is None: 1710 continue 1711 if python_key == json_key: 1712 continue 1713 json_key_assigned_no_need_for_python_key = json_key in seen_json_attribute_names 1714 if json_key_assigned_no_need_for_python_key: 1715 del result[python_key] 1716 1717 return result 1718 1719 1720 def type_error_message(var_value=None, var_name=None, valid_classes=None, 1721 key_type=None): 1722 """ 1723 Keyword Args: 1724 var_value (any): the variable which has the type_error 1725 var_name (str): the name of the variable which has the typ error 1726 valid_classes (tuple): the accepted classes for current_item's 1727 value 1728 key_type (bool): False if our value is a value in a dict 1729 True if it is a key in a dict 1730 False if our item is an item in a list 1731 """ 1732 key_or_value = 'value' 1733 if key_type: 1734 key_or_value = 'key' 1735 valid_classes_phrase = get_valid_classes_phrase(valid_classes) 1736 msg = ( 1737 "Invalid type for variable '{0}'. Required {1} type {2} and " 1738 "passed type was {3}".format( 1739 var_name, 1740 key_or_value, 1741 valid_classes_phrase, 1742 type(var_value).__name__, 1743 ) 1744 ) 1745 return msg 1746 1747 1748 def get_valid_classes_phrase(input_classes): 1749 """Returns a string phrase describing what types are allowed 1750 """ 1751 all_classes = list(input_classes) 1752 all_classes = sorted(all_classes, key=lambda cls: cls.__name__) 1753 all_class_names = [cls.__name__ for cls in all_classes] 1754 if len(all_class_names) == 1: 1755 return 'is {0}'.format(all_class_names[0]) 1756 return "is one of [{0}]".format(", ".join(all_class_names)) 1757 1758 1759 def get_allof_instances(self, model_args, constant_args): 1760 """ 1761 Args: 1762 self: the class we are handling 1763 model_args (dict): var_name to var_value 1764 used to make instances 1765 constant_args (dict): 1766 metadata arguments: 1767 _check_type 1768 _path_to_item 1769 _spec_property_naming 1770 _configuration 1771 _visited_composed_classes 1772 1773 Returns 1774 composed_instances (list) 1775 """ 1776 composed_instances = [] 1777 for allof_class in self._composed_schemas['allOf']: 1778 1779 try: 1780 if constant_args.get('_spec_property_naming'): 1781 allof_instance = allof_class._from_openapi_data(**model_args, **constant_args) 1782 else: 1783 allof_instance = allof_class(**model_args, **constant_args) 1784 composed_instances.append(allof_instance) 1785 except Exception as ex: 1786 raise ApiValueError( 1787 "Invalid inputs given to generate an instance of '%s'. The " 1788 "input data was invalid for the allOf schema '%s' in the composed " 1789 "schema '%s'. Error=%s" % ( 1790 allof_class.__name__, 1791 allof_class.__name__, 1792 self.__class__.__name__, 1793 str(ex) 1794 ) 1795 ) from ex 1796 return composed_instances 1797 1798 1799 def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None): 1800 """ 1801 Find the oneOf schema that matches the input data (e.g. payload). 1802 If exactly one schema matches the input data, an instance of that schema 1803 is returned. 1804 If zero or more than one schema match the input data, an exception is raised. 1805 In OAS 3.x, the payload MUST, by validation, match exactly one of the 1806 schemas described by oneOf. 1807 1808 Args: 1809 cls: the class we are handling 1810 model_kwargs (dict): var_name to var_value 1811 The input data, e.g. the payload that must match a oneOf schema 1812 in the OpenAPI document. 1813 constant_kwargs (dict): var_name to var_value 1814 args that every model requires, including configuration, server 1815 and path to item. 1816 1817 Kwargs: 1818 model_arg: (int, float, bool, str, date, datetime, ModelSimple, None): 1819 the value to assign to a primitive class or ModelSimple class 1820 Notes: 1821 - this is only passed in when oneOf includes types which are not object 1822 - None is used to suppress handling of model_arg, nullable models are handled in __new__ 1823 1824 Returns 1825 oneof_instance (instance) 1826 """ 1827 if len(cls._composed_schemas['oneOf']) == 0: 1828 return None 1829 1830 oneof_instances = [] 1831 # Iterate over each oneOf schema and determine if the input data 1832 # matches the oneOf schemas. 1833 for oneof_class in cls._composed_schemas['oneOf']: 1834 # The composed oneOf schema allows the 'null' type and the input data 1835 # is the null value. This is a OAS >= 3.1 feature. 1836 if oneof_class is none_type: 1837 # skip none_types because we are deserializing dict data. 1838 # none_type deserialization is handled in the __new__ method 1839 continue 1840 1841 single_value_input = allows_single_value_input(oneof_class) 1842 1843 try: 1844 if not single_value_input: 1845 if constant_kwargs.get('_spec_property_naming'): 1846 oneof_instance = oneof_class._from_openapi_data(**model_kwargs, **constant_kwargs) 1847 else: 1848 oneof_instance = oneof_class(**model_kwargs, **constant_kwargs) 1849 else: 1850 if issubclass(oneof_class, ModelSimple): 1851 if constant_kwargs.get('_spec_property_naming'): 1852 oneof_instance = oneof_class._from_openapi_data(model_arg, **constant_kwargs) 1853 else: 1854 oneof_instance = oneof_class(model_arg, **constant_kwargs) 1855 elif oneof_class in PRIMITIVE_TYPES: 1856 oneof_instance = validate_and_convert_types( 1857 model_arg, 1858 (oneof_class,), 1859 constant_kwargs['_path_to_item'], 1860 constant_kwargs['_spec_property_naming'], 1861 constant_kwargs['_check_type'], 1862 configuration=constant_kwargs['_configuration'] 1863 ) 1864 oneof_instances.append(oneof_instance) 1865 except Exception: 1866 pass 1867 if len(oneof_instances) == 0: 1868 raise ApiValueError( 1869 "Invalid inputs given to generate an instance of %s. None " 1870 "of the oneOf schemas matched the input data." % 1871 cls.__name__ 1872 ) 1873 elif len(oneof_instances) > 1: 1874 raise ApiValueError( 1875 "Invalid inputs given to generate an instance of %s. Multiple " 1876 "oneOf schemas matched the inputs, but a max of one is allowed." % 1877 cls.__name__ 1878 ) 1879 return oneof_instances[0] 1880 1881 1882 def get_anyof_instances(self, model_args, constant_args): 1883 """ 1884 Args: 1885 self: the class we are handling 1886 model_args (dict): var_name to var_value 1887 The input data, e.g. the payload that must match at least one 1888 anyOf child schema in the OpenAPI document. 1889 constant_args (dict): var_name to var_value 1890 args that every model requires, including configuration, server 1891 and path to item. 1892 1893 Returns 1894 anyof_instances (list) 1895 """ 1896 anyof_instances = [] 1897 if len(self._composed_schemas['anyOf']) == 0: 1898 return anyof_instances 1899 1900 for anyof_class in self._composed_schemas['anyOf']: 1901 # The composed oneOf schema allows the 'null' type and the input data 1902 # is the null value. This is a OAS >= 3.1 feature. 1903 if anyof_class is none_type: 1904 # skip none_types because we are deserializing dict data. 1905 # none_type deserialization is handled in the __new__ method 1906 continue 1907 1908 try: 1909 if constant_args.get('_spec_property_naming'): 1910 anyof_instance = anyof_class._from_openapi_data(**model_args, **constant_args) 1911 else: 1912 anyof_instance = anyof_class(**model_args, **constant_args) 1913 anyof_instances.append(anyof_instance) 1914 except Exception: 1915 pass 1916 if len(anyof_instances) == 0: 1917 raise ApiValueError( 1918 "Invalid inputs given to generate an instance of %s. None of the " 1919 "anyOf schemas matched the inputs." % 1920 self.__class__.__name__ 1921 ) 1922 return anyof_instances 1923 1924 1925 def get_discarded_args(self, composed_instances, model_args): 1926 """ 1927 Gathers the args that were discarded by configuration.discard_unknown_keys 1928 """ 1929 model_arg_keys = model_args.keys() 1930 discarded_args = set() 1931 # arguments passed to self were already converted to python names 1932 # before __init__ was called 1933 for instance in composed_instances: 1934 if instance.__class__ in self._composed_schemas['allOf']: 1935 try: 1936 keys = instance.to_dict().keys() 1937 discarded_keys = model_args - keys 1938 discarded_args.update(discarded_keys) 1939 except Exception: 1940 # allOf integer schema will throw exception 1941 pass 1942 else: 1943 try: 1944 all_keys = set(model_to_dict(instance, serialize=False).keys()) 1945 js_keys = model_to_dict(instance, serialize=True).keys() 1946 all_keys.update(js_keys) 1947 discarded_keys = model_arg_keys - all_keys 1948 discarded_args.update(discarded_keys) 1949 except Exception: 1950 # allOf integer schema will throw exception 1951 pass 1952 return discarded_args 1953 1954 1955 def validate_get_composed_info(constant_args, model_args, self): 1956 """ 1957 For composed schemas, generate schema instances for 1958 all schemas in the oneOf/anyOf/allOf definition. If additional 1959 properties are allowed, also assign those properties on 1960 all matched schemas that contain additionalProperties. 1961 Openapi schemas are python classes. 1962 1963 Exceptions are raised if: 1964 - 0 or > 1 oneOf schema matches the model_args input data 1965 - no anyOf schema matches the model_args input data 1966 - any of the allOf schemas do not match the model_args input data 1967 1968 Args: 1969 constant_args (dict): these are the args that every model requires 1970 model_args (dict): these are the required and optional spec args that 1971 were passed in to make this model 1972 self (class): the class that we are instantiating 1973 This class contains self._composed_schemas 1974 1975 Returns: 1976 composed_info (list): length three 1977 composed_instances (list): the composed instances which are not 1978 self 1979 var_name_to_model_instances (dict): a dict going from var_name 1980 to the model_instance which holds that var_name 1981 the model_instance may be self or an instance of one of the 1982 classes in self.composed_instances() 1983 additional_properties_model_instances (list): a list of the 1984 model instances which have the property 1985 additional_properties_type. This list can include self 1986 """ 1987 # create composed_instances 1988 composed_instances = [] 1989 allof_instances = get_allof_instances(self, model_args, constant_args) 1990 composed_instances.extend(allof_instances) 1991 oneof_instance = get_oneof_instance(self.__class__, model_args, constant_args) 1992 if oneof_instance is not None: 1993 composed_instances.append(oneof_instance) 1994 anyof_instances = get_anyof_instances(self, model_args, constant_args) 1995 composed_instances.extend(anyof_instances) 1996 """ 1997 set additional_properties_model_instances 1998 additional properties must be evaluated at the schema level 1999 so self's additional properties are most important 2000 If self is a composed schema with: 2001 - no properties defined in self 2002 - additionalProperties: False 2003 Then for object payloads every property is an additional property 2004 and they are not allowed, so only empty dict is allowed 2005 2006 Properties must be set on all matching schemas 2007 so when a property is assigned toa composed instance, it must be set on all 2008 composed instances regardless of additionalProperties presence 2009 keeping it to prevent breaking changes in v5.0.1 2010 TODO remove cls._additional_properties_model_instances in 6.0.0 2011 """ 2012 additional_properties_model_instances = [] 2013 if self.additional_properties_type is not None: 2014 additional_properties_model_instances = [self] 2015 2016 """ 2017 no need to set properties on self in here, they will be set in __init__ 2018 By here all composed schema oneOf/anyOf/allOf instances have their properties set using 2019 model_args 2020 """ 2021 discarded_args = get_discarded_args(self, composed_instances, model_args) 2022 2023 # map variable names to composed_instances 2024 var_name_to_model_instances = {} 2025 for prop_name in model_args: 2026 if prop_name not in discarded_args: 2027 var_name_to_model_instances[prop_name] = [self] + composed_instances 2028 2029 return [ 2030 composed_instances, 2031 var_name_to_model_instances, 2032 additional_properties_model_instances, 2033 discarded_args 2034 ]