github.com/phrase/openapi@v0.0.0-20240514140800-49e8a106740e/openapi-generator/templates/python/python-experimental/model_utils.mustache (about)

     1  # coding: utf-8
     2  
     3  {{>partial_header}}
     4  
     5  from datetime import date, datetime  # noqa: F401
     6  import inspect
     7  import os
     8  import pprint
     9  import re
    10  import tempfile
    11  
    12  from dateutil.parser import parse
    13  import six
    14  
    15  from {{packageName}}.exceptions import (
    16      ApiKeyError,
    17      ApiTypeError,
    18      ApiValueError,
    19  )
    20  
    21  none_type = type(None)
    22  if six.PY3:
    23      import io
    24      file_type = io.IOBase
    25      # these are needed for when other modules import str and int from here
    26      str = str
    27      int = int
    28  else:
    29      file_type = file  # noqa: F821
    30      str_py2 = str
    31      unicode_py2 = unicode  # noqa: F821
    32      long_py2 = long  # noqa: F821
    33      int_py2 = int
    34      # this requires that the future library is installed
    35      from builtins import int, str
    36  
    37  
    38  class OpenApiModel(object):
    39      """The base class for all OpenAPIModels"""
    40  
    41  {{> python-experimental/model_templates/method_set_attribute }}
    42  
    43  {{> python-experimental/model_templates/methods_shared }}
    44  
    45  
    46  class ModelSimple(OpenApiModel):
    47      """the parent class of models whose type != object in their
    48      swagger/openapi"""
    49  
    50  {{> python-experimental/model_templates/methods_setattr_getattr_normal }}
    51  
    52  {{> python-experimental/model_templates/methods_tostr_eq_simple }}
    53  
    54  
    55  class ModelNormal(OpenApiModel):
    56      """the parent class of models whose type == object in their
    57      swagger/openapi"""
    58  
    59  {{> python-experimental/model_templates/methods_setattr_getattr_normal }}
    60  
    61  {{> python-experimental/model_templates/methods_todict_tostr_eq_shared}}
    62  
    63  
    64  class ModelComposed(OpenApiModel):
    65      """the parent class of models whose type == object in their
    66      swagger/openapi and have oneOf/allOf/anyOf"""
    67  
    68  {{> python-experimental/model_templates/methods_setattr_getattr_composed }}
    69  
    70  {{> python-experimental/model_templates/methods_todict_tostr_eq_shared}}
    71  
    72  
    73  COERCION_INDEX_BY_TYPE = {
    74      ModelComposed: 0,
    75      ModelNormal: 1,
    76      ModelSimple: 2,
    77      none_type: 3,    # The type of 'None'.
    78      list: 4,
    79      dict: 5,
    80      float: 6,
    81      int: 7,
    82      bool: 8,
    83      datetime: 9,
    84      date: 10,
    85      str: 11,
    86      file_type: 12,   # 'file_type' is an alias for the built-in 'file' or 'io.IOBase' type.
    87  }
    88  
    89  # these are used to limit what type conversions we try to do
    90  # when we have a valid type already and we want to try converting
    91  # to another type
    92  UPCONVERSION_TYPE_PAIRS = (
    93      (str, datetime),
    94      (str, date),
    95      (list, ModelComposed),
    96      (dict, ModelComposed),
    97      (list, ModelNormal),
    98      (dict, ModelNormal),
    99      (str, ModelSimple),
   100      (int, ModelSimple),
   101      (float, ModelSimple),
   102      (list, ModelSimple),
   103  )
   104  
   105  COERCIBLE_TYPE_PAIRS = {
   106      False: (  # client instantiation of a model with client data
   107          # (dict, ModelComposed),
   108          # (list, ModelComposed),
   109          # (dict, ModelNormal),
   110          # (list, ModelNormal),
   111          # (str, ModelSimple),
   112          # (int, ModelSimple),
   113          # (float, ModelSimple),
   114          # (list, ModelSimple),
   115          # (str, int),
   116          # (str, float),
   117          # (str, datetime),
   118          # (str, date),
   119          # (int, str),
   120          # (float, str),
   121      ),
   122      True: (  # server -> client data
   123          (dict, ModelComposed),
   124          (list, ModelComposed),
   125          (dict, ModelNormal),
   126          (list, ModelNormal),
   127          (str, ModelSimple),
   128          (int, ModelSimple),
   129          (float, ModelSimple),
   130          (list, ModelSimple),
   131          # (str, int),
   132          # (str, float),
   133          (str, datetime),
   134          (str, date),
   135          # (int, str),
   136          # (float, str),
   137          (str, file_type)
   138      ),
   139  }
   140  
   141  
   142  def get_simple_class(input_value):
   143      """Returns an input_value's simple class that we will use for type checking
   144      Python2:
   145      float and int will return int, where int is the python3 int backport
   146      str and unicode will return str, where str is the python3 str backport
   147      Note: float and int ARE both instances of int backport
   148      Note: str_py2 and unicode_py2 are NOT both instances of str backport
   149  
   150      Args:
   151          input_value (class/class_instance): the item for which we will return
   152                                              the simple class
   153      """
   154      if isinstance(input_value, type):
   155          # input_value is a class
   156          return input_value
   157      elif isinstance(input_value, tuple):
   158          return tuple
   159      elif isinstance(input_value, list):
   160          return list
   161      elif isinstance(input_value, dict):
   162          return dict
   163      elif isinstance(input_value, none_type):
   164          return none_type
   165      elif isinstance(input_value, file_type):
   166          return file_type
   167      elif isinstance(input_value, bool):
   168          # this must be higher than the int check because
   169          # isinstance(True, int) == True
   170          return bool
   171      elif isinstance(input_value, int):
   172          # for python2 input_value==long_instance -> return int
   173          # where int is the python3 int backport
   174          return int
   175      elif isinstance(input_value, datetime):
   176          # this must be higher than the date check because
   177          # isinstance(datetime_instance, date) == True
   178          return datetime
   179      elif isinstance(input_value, date):
   180          return date
   181      elif (six.PY2 and isinstance(input_value, (str_py2, unicode_py2, str)) or
   182              isinstance(input_value, str)):
   183          return str
   184      return type(input_value)
   185  
   186  
   187  def check_allowed_values(allowed_values, input_variable_path, input_values):
   188      """Raises an exception if the input_values are not allowed
   189  
   190      Args:
   191          allowed_values (dict): the allowed_values dict
   192          input_variable_path (tuple): the path to the input variable
   193          input_values (list/str/int/float/date/datetime): the values that we
   194              are checking to see if they are in allowed_values
   195      """
   196      these_allowed_values = list(allowed_values[input_variable_path].values())
   197      if (isinstance(input_values, list)
   198              and not set(input_values).issubset(
   199                  set(these_allowed_values))):
   200          invalid_values = ", ".join(
   201              map(str, set(input_values) - set(these_allowed_values))),
   202          raise ApiValueError(
   203              "Invalid values for `%s` [%s], must be a subset of [%s]" %
   204              (
   205                  input_variable_path[0],
   206                  invalid_values,
   207                  ", ".join(map(str, these_allowed_values))
   208              )
   209          )
   210      elif (isinstance(input_values, dict)
   211              and not set(
   212                  input_values.keys()).issubset(set(these_allowed_values))):
   213          invalid_values = ", ".join(
   214              map(str, set(input_values.keys()) - set(these_allowed_values)))
   215          raise ApiValueError(
   216              "Invalid keys in `%s` [%s], must be a subset of [%s]" %
   217              (
   218                  input_variable_path[0],
   219                  invalid_values,
   220                  ", ".join(map(str, these_allowed_values))
   221              )
   222          )
   223      elif (not isinstance(input_values, (list, dict))
   224              and input_values not in these_allowed_values):
   225          raise ApiValueError(
   226              "Invalid value for `%s` (%s), must be one of %s" %
   227              (
   228                  input_variable_path[0],
   229                  input_values,
   230                  these_allowed_values
   231              )
   232          )
   233  
   234  
   235  def check_validations(validations, input_variable_path, input_values):
   236      """Raises an exception if the input_values are invalid
   237  
   238      Args:
   239          validations (dict): the validation dictionary
   240          input_variable_path (tuple): the path to the input variable
   241          input_values (list/str/int/float/date/datetime): the values that we
   242              are checking
   243      """
   244      current_validations = validations[input_variable_path]
   245      if ('max_length' in current_validations and
   246              len(input_values) > current_validations['max_length']):
   247          raise ApiValueError(
   248              "Invalid value for `%s`, length must be less than or equal to "
   249              "`%s`" % (
   250                  input_variable_path[0],
   251                  current_validations['max_length']
   252              )
   253          )
   254  
   255      if ('min_length' in current_validations and
   256              len(input_values) < current_validations['min_length']):
   257          raise ApiValueError(
   258              "Invalid value for `%s`, length must be greater than or equal to "
   259              "`%s`" % (
   260                  input_variable_path[0],
   261                  current_validations['min_length']
   262              )
   263          )
   264  
   265      if ('max_items' in current_validations and
   266              len(input_values) > current_validations['max_items']):
   267          raise ApiValueError(
   268              "Invalid value for `%s`, number of items must be less than or "
   269              "equal to `%s`" % (
   270                  input_variable_path[0],
   271                  current_validations['max_items']
   272              )
   273          )
   274  
   275      if ('min_items' in current_validations and
   276              len(input_values) < current_validations['min_items']):
   277          raise ValueError(
   278              "Invalid value for `%s`, number of items must be greater than or "
   279              "equal to `%s`" % (
   280                  input_variable_path[0],
   281                  current_validations['min_items']
   282              )
   283          )
   284  
   285      items = ('exclusive_maximum', 'inclusive_maximum', 'exclusive_minimum',
   286               'inclusive_minimum')
   287      if (any(item in current_validations for item in items)):
   288          if isinstance(input_values, list):
   289              max_val = max(input_values)
   290              min_val = min(input_values)
   291          elif isinstance(input_values, dict):
   292              max_val = max(input_values.values())
   293              min_val = min(input_values.values())
   294          else:
   295              max_val = input_values
   296              min_val = input_values
   297  
   298      if ('exclusive_maximum' in current_validations and
   299              max_val >= current_validations['exclusive_maximum']):
   300          raise ApiValueError(
   301              "Invalid value for `%s`, must be a value less than `%s`" % (
   302                  input_variable_path[0],
   303                  current_validations['exclusive_maximum']
   304              )
   305          )
   306  
   307      if ('inclusive_maximum' in current_validations and
   308              max_val > current_validations['inclusive_maximum']):
   309          raise ApiValueError(
   310              "Invalid value for `%s`, must be a value less than or equal to "
   311              "`%s`" % (
   312                  input_variable_path[0],
   313                  current_validations['inclusive_maximum']
   314              )
   315          )
   316  
   317      if ('exclusive_minimum' in current_validations and
   318              min_val <= current_validations['exclusive_minimum']):
   319          raise ApiValueError(
   320              "Invalid value for `%s`, must be a value greater than `%s`" %
   321              (
   322                  input_variable_path[0],
   323                  current_validations['exclusive_maximum']
   324              )
   325          )
   326  
   327      if ('inclusive_minimum' in current_validations and
   328              min_val < current_validations['inclusive_minimum']):
   329          raise ApiValueError(
   330              "Invalid value for `%s`, must be a value greater than or equal "
   331              "to `%s`" % (
   332                  input_variable_path[0],
   333                  current_validations['inclusive_minimum']
   334              )
   335          )
   336      flags = current_validations.get('regex', {}).get('flags', 0)
   337      if ('regex' in current_validations and
   338              not re.search(current_validations['regex']['pattern'],
   339                            input_values, flags=flags)):
   340          raise ApiValueError(
   341              r"Invalid value for `%s`, must be a follow pattern or equal to "
   342              r"`%s` with flags=`%s`" % (
   343                  input_variable_path[0],
   344                  current_validations['regex']['pattern'],
   345                  flags
   346              )
   347            )
   348  
   349  
   350  def order_response_types(required_types):
   351      """Returns the required types sorted in coercion order
   352  
   353      Args:
   354          required_types (list/tuple): collection of classes or instance of
   355              list or dict with class information inside it.
   356  
   357      Returns:
   358          (list): coercion order sorted collection of classes or instance
   359              of list or dict with class information inside it.
   360      """
   361  
   362      def index_getter(class_or_instance):
   363          if isinstance(class_or_instance, list):
   364              return COERCION_INDEX_BY_TYPE[list]
   365          elif isinstance(class_or_instance, dict):
   366              return COERCION_INDEX_BY_TYPE[dict]
   367          elif (inspect.isclass(class_or_instance)
   368                  and issubclass(class_or_instance, ModelComposed)):
   369              return COERCION_INDEX_BY_TYPE[ModelComposed]
   370          elif (inspect.isclass(class_or_instance)
   371                  and issubclass(class_or_instance, ModelNormal)):
   372              return COERCION_INDEX_BY_TYPE[ModelNormal]
   373          elif (inspect.isclass(class_or_instance)
   374                  and issubclass(class_or_instance, ModelSimple)):
   375              return COERCION_INDEX_BY_TYPE[ModelSimple]
   376          elif class_or_instance in COERCION_INDEX_BY_TYPE:
   377              return COERCION_INDEX_BY_TYPE[class_or_instance]
   378          raise ApiValueError("Unsupported type: %s" % class_or_instance)
   379  
   380      sorted_types = sorted(
   381          required_types,
   382          key=lambda class_or_instance: index_getter(class_or_instance)
   383      )
   384      return sorted_types
   385  
   386  
   387  def remove_uncoercible(required_types_classes, current_item, from_server,
   388                         must_convert=True):
   389      """Only keeps the type conversions that are possible
   390  
   391      Args:
   392          required_types_classes (tuple): tuple of classes that are required
   393                            these should be ordered by COERCION_INDEX_BY_TYPE
   394          from_server (bool): a boolean of whether the data is from the server
   395                            if false, the data is from the client
   396          current_item (any): the current item (input data) to be converted
   397  
   398      Keyword Args:
   399          must_convert (bool): if True the item to convert is of the wrong
   400                            type and we want a big list of coercibles
   401                            if False, we want a limited list of coercibles
   402  
   403      Returns:
   404          (list): the remaining coercible required types, classes only
   405      """
   406      current_type_simple = get_simple_class(current_item)
   407  
   408      results_classes = []
   409      for required_type_class in required_types_classes:
   410          # convert our models to OpenApiModel
   411          required_type_class_simplified = required_type_class
   412          if isinstance(required_type_class_simplified, type):
   413              if issubclass(required_type_class_simplified, ModelComposed):
   414                  required_type_class_simplified = ModelComposed
   415              elif issubclass(required_type_class_simplified, ModelNormal):
   416                  required_type_class_simplified = ModelNormal
   417              elif issubclass(required_type_class_simplified, ModelSimple):
   418                  required_type_class_simplified = ModelSimple
   419  
   420          if required_type_class_simplified == current_type_simple:
   421              # don't consider converting to one's own class
   422              continue
   423  
   424          class_pair = (current_type_simple, required_type_class_simplified)
   425          if must_convert and class_pair in COERCIBLE_TYPE_PAIRS[from_server]:
   426              results_classes.append(required_type_class)
   427          elif class_pair in UPCONVERSION_TYPE_PAIRS:
   428              results_classes.append(required_type_class)
   429      return results_classes
   430  
   431  
   432  def get_required_type_classes(required_types_mixed):
   433      """Converts the tuple required_types into a tuple and a dict described
   434      below
   435  
   436      Args:
   437          required_types_mixed (tuple/list): will contain either classes or
   438              instance of list or dict
   439  
   440      Returns:
   441          (valid_classes, dict_valid_class_to_child_types_mixed):
   442              valid_classes (tuple): the valid classes that the current item
   443                                     should be
   444              dict_valid_class_to_child_types_mixed (doct):
   445                  valid_class (class): this is the key
   446                  child_types_mixed (list/dict/tuple): describes the valid child
   447                      types
   448      """
   449      valid_classes = []
   450      child_req_types_by_current_type = {}
   451      for required_type in required_types_mixed:
   452          if isinstance(required_type, list):
   453              valid_classes.append(list)
   454              child_req_types_by_current_type[list] = required_type
   455          elif isinstance(required_type, tuple):
   456              valid_classes.append(tuple)
   457              child_req_types_by_current_type[tuple] = required_type
   458          elif isinstance(required_type, dict):
   459              valid_classes.append(dict)
   460              child_req_types_by_current_type[dict] = required_type[str]
   461          else:
   462              valid_classes.append(required_type)
   463      return tuple(valid_classes), child_req_types_by_current_type
   464  
   465  
   466  def change_keys_js_to_python(input_dict, model_class):
   467      """
   468      Converts from javascript_key keys in the input_dict to python_keys in
   469      the output dict using the mapping in model_class.
   470      If the input_dict contains a key which does not declared in the model_class,
   471      the key is added to the output dict as is. The assumption is the model_class
   472      may have undeclared properties (additionalProperties attribute in the OAS
   473      document).
   474      """
   475  
   476      output_dict = {}
   477      reversed_attr_map = {value: key for key, value in
   478                           six.iteritems(model_class.attribute_map)}
   479      for javascript_key, value in six.iteritems(input_dict):
   480          python_key = reversed_attr_map.get(javascript_key)
   481          if python_key is None:
   482              # if the key is unknown, it is in error or it is an
   483              # additionalProperties variable
   484              python_key = javascript_key
   485          output_dict[python_key] = value
   486      return output_dict
   487  
   488  
   489  def get_type_error(var_value, path_to_item, valid_classes, key_type=False):
   490      error_msg = type_error_message(
   491          var_name=path_to_item[-1],
   492          var_value=var_value,
   493          valid_classes=valid_classes,
   494          key_type=key_type
   495      )
   496      return ApiTypeError(
   497          error_msg,
   498          path_to_item=path_to_item,
   499          valid_classes=valid_classes,
   500          key_type=key_type
   501      )
   502  
   503  
   504  def deserialize_primitive(data, klass, path_to_item):
   505      """Deserializes string to primitive type.
   506  
   507      :param data: str/int/float
   508      :param klass: str/class the class to convert to
   509  
   510      :return: int, float, str, bool, date, datetime
   511      """
   512      additional_message = ""
   513      try:
   514          if klass in {datetime, date}:
   515              additional_message = (
   516                  "If you need your parameter to have a fallback "
   517                  "string value, please set its type as `type: {}` in your "
   518                  "spec. That allows the value to be any type. "
   519              )
   520              if klass == datetime:
   521                  if len(data) < 8:
   522                      raise ValueError("This is not a datetime")
   523                  # The string should be in iso8601 datetime format.
   524                  parsed_datetime = parse(data)
   525                  date_only = (
   526                      parsed_datetime.hour == 0 and
   527                      parsed_datetime.minute == 0 and
   528                      parsed_datetime.second == 0 and
   529                      parsed_datetime.tzinfo is None and
   530                      8 <= len(data) <= 10
   531                  )
   532                  if date_only:
   533                      raise ValueError("This is a date, not a datetime")
   534                  return parsed_datetime
   535              elif klass == date:
   536                  if len(data) < 8:
   537                      raise ValueError("This is not a date")
   538                  return parse(data).date()
   539          else:
   540              converted_value = klass(data)
   541              if isinstance(data, str) and klass == float:
   542                  if str(converted_value) != data:
   543                      # '7' -> 7.0 -> '7.0' != '7'
   544                      raise ValueError('This is not a float')
   545              return converted_value
   546      except (OverflowError, ValueError):
   547          # parse can raise OverflowError
   548          raise ApiValueError(
   549              "{0}Failed to parse {1} as {2}".format(
   550                  additional_message, repr(data), get_py3_class_name(klass)
   551              ),
   552              path_to_item=path_to_item
   553          )
   554  
   555  
   556  def fix_model_input_data(model_data, model_class):
   557      # this is only called on classes where the input data is a dict
   558      fixed_model_data = change_keys_js_to_python(
   559          model_data,
   560          model_class
   561      )
   562      if model_class._composed_schemas() is not None:
   563          for allof_class in model_class._composed_schemas()['allOf']:
   564              fixed_model_data = change_keys_js_to_python(
   565                  fixed_model_data,
   566                  allof_class
   567              )
   568      return fixed_model_data
   569  
   570  
   571  def deserialize_model(model_data, model_class, path_to_item, check_type,
   572                        configuration, from_server):
   573      """Deserializes model_data to model instance.
   574  
   575      Args:
   576          model_data (list/dict): data to instantiate the model
   577          model_class (OpenApiModel): the model class
   578          path_to_item (list): path to the model in the received data
   579          check_type (bool): whether to check the data tupe for the values in
   580              the model
   581          configuration (Configuration): the instance to use to convert files
   582          from_server (bool): True if the data is from the server
   583              False if the data is from the client
   584  
   585      Returns:
   586          model instance
   587  
   588      Raise:
   589          ApiTypeError
   590          ApiValueError
   591          ApiKeyError
   592      """
   593  
   594      kw_args = dict(_check_type=check_type,
   595                     _path_to_item=path_to_item,
   596                     _configuration=configuration,
   597                     _from_server=from_server)
   598  
   599      used_model_class = model_class
   600      if model_class.discriminator() is not None:
   601          used_model_class = model_class.get_discriminator_class(
   602              from_server, model_data)
   603  
   604      if issubclass(used_model_class, ModelSimple):
   605          instance = used_model_class(value=model_data, **kw_args)
   606          return instance
   607      if isinstance(model_data, list):
   608          instance = used_model_class(*model_data, **kw_args)
   609      if isinstance(model_data, dict):
   610          fixed_model_data = change_keys_js_to_python(
   611              model_data,
   612              used_model_class
   613          )
   614          kw_args.update(fixed_model_data)
   615          instance = used_model_class(**kw_args)
   616      return instance
   617  
   618  
   619  def deserialize_file(response_data, configuration, content_disposition=None):
   620      """Deserializes body to file
   621  
   622      Saves response body into a file in a temporary folder,
   623      using the filename from the `Content-Disposition` header if provided.
   624  
   625      Args:
   626          param response_data (str):  the file data to write
   627          configuration (Configuration): the instance to use to convert files
   628  
   629      Keyword Args:
   630          content_disposition (str):  the value of the Content-Disposition
   631              header
   632  
   633      Returns:
   634          (file_type): the deserialized file which is open
   635              The user is responsible for closing and reading the file
   636      """
   637      fd, path = tempfile.mkstemp(dir=configuration.temp_folder_path)
   638      os.close(fd)
   639      os.remove(path)
   640  
   641      if content_disposition:
   642          filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?',
   643                               content_disposition).group(1)
   644          path = os.path.join(os.path.dirname(path), filename)
   645  
   646      with open(path, "wb") as f:
   647          if six.PY3 and isinstance(response_data, str):
   648              # in python3 change str to bytes so we can write it
   649              response_data = response_data.encode('utf-8')
   650          f.write(response_data)
   651  
   652      f = open(path, "rb")
   653      return f
   654  
   655  
   656  def attempt_convert_item(input_value, valid_classes, path_to_item,
   657                           configuration, from_server, key_type=False,
   658                           must_convert=False, check_type=True):
   659      """
   660      Args:
   661          input_value (any): the data to convert
   662          valid_classes (any): the classes that are valid
   663          path_to_item (list): the path to the item to convert
   664          configuration (Configuration): the instance to use to convert files
   665          from_server (bool): True if data is from the server, False is data is
   666              from the client
   667          key_type (bool): if True we need to convert a key type (not supported)
   668          must_convert (bool): if True we must convert
   669          check_type (bool): if True we check the type or the returned data in
   670              ModelComposed/ModelNormal/ModelSimple instances
   671  
   672      Returns:
   673          instance (any) the fixed item
   674  
   675      Raises:
   676          ApiTypeError
   677          ApiValueError
   678          ApiKeyError
   679      """
   680      valid_classes_ordered = order_response_types(valid_classes)
   681      valid_classes_coercible = remove_uncoercible(
   682          valid_classes_ordered, input_value, from_server)
   683      if not valid_classes_coercible or key_type:
   684          # we do not handle keytype errors, json will take care
   685          # of this for us
   686          if configuration is None or not configuration.discard_unknown_keys:
   687              raise get_type_error(input_value, path_to_item, valid_classes,
   688                                   key_type=key_type)
   689      for valid_class in valid_classes_coercible:
   690          try:
   691              if issubclass(valid_class, OpenApiModel):
   692                  return deserialize_model(input_value, valid_class,
   693                                           path_to_item, check_type,
   694                                           configuration, from_server)
   695              elif valid_class == file_type:
   696                  return deserialize_file(input_value, configuration)
   697              return deserialize_primitive(input_value, valid_class,
   698                                           path_to_item)
   699          except (ApiTypeError, ApiValueError, ApiKeyError) as conversion_exc:
   700              if must_convert:
   701                  raise conversion_exc
   702              # if we have conversion errors when must_convert == False
   703              # we ignore the exception and move on to the next class
   704              continue
   705      # we were unable to convert, must_convert == False
   706      return input_value
   707  
   708  
   709  def validate_and_convert_types(input_value, required_types_mixed, path_to_item,
   710                                 from_server, _check_type, configuration=None):
   711      """Raises a TypeError is there is a problem, otherwise returns value
   712  
   713      Args:
   714          input_value (any): the data to validate/convert
   715          required_types_mixed (list/dict/tuple): A list of
   716              valid classes, or a list tuples of valid classes, or a dict where
   717              the value is a tuple of value classes
   718          path_to_item: (list) the path to the data being validated
   719              this stores a list of keys or indices to get to the data being
   720              validated
   721          from_server (bool): True if data is from the server
   722              False if data is from the client
   723          _check_type: (boolean) if true, type will be checked and conversion
   724              will be attempted.
   725          configuration: (Configuration): the configuration class to use
   726              when converting file_type items.
   727              If passed, conversion will be attempted when possible
   728              If not passed, no conversions will be attempted and
   729              exceptions will be raised
   730  
   731      Returns:
   732          the correctly typed value
   733  
   734      Raises:
   735          ApiTypeError
   736      """
   737      results = get_required_type_classes(required_types_mixed)
   738      valid_classes, child_req_types_by_current_type = results
   739  
   740      input_class_simple = get_simple_class(input_value)
   741      valid_type = input_class_simple in set(valid_classes)
   742      if not valid_type:
   743          if configuration:
   744              # if input_value is not valid_type try to convert it
   745              converted_instance = attempt_convert_item(
   746                  input_value,
   747                  valid_classes,
   748                  path_to_item,
   749                  configuration,
   750                  from_server,
   751                  key_type=False,
   752                  must_convert=True
   753              )
   754              return converted_instance
   755          else:
   756              raise get_type_error(input_value, path_to_item, valid_classes,
   757                                   key_type=False)
   758  
   759      # input_value's type is in valid_classes
   760      if len(valid_classes) > 1 and configuration:
   761          # there are valid classes which are not the current class
   762          valid_classes_coercible = remove_uncoercible(
   763              valid_classes, input_value, from_server, must_convert=False)
   764          if valid_classes_coercible:
   765              converted_instance = attempt_convert_item(
   766                  input_value,
   767                  valid_classes_coercible,
   768                  path_to_item,
   769                  configuration,
   770                  from_server,
   771                  key_type=False,
   772                  must_convert=False
   773              )
   774              return converted_instance
   775  
   776      if child_req_types_by_current_type == {}:
   777          # all types are of the required types and there are no more inner
   778          # variables left to look at
   779          return input_value
   780      inner_required_types = child_req_types_by_current_type.get(
   781          type(input_value)
   782      )
   783      if inner_required_types is None:
   784          # for this type, there are not more inner variables left to look at
   785          return input_value
   786      if isinstance(input_value, list):
   787          if input_value == []:
   788              # allow an empty list
   789              return input_value
   790          for index, inner_value in enumerate(input_value):
   791              inner_path = list(path_to_item)
   792              inner_path.append(index)
   793              input_value[index] = validate_and_convert_types(
   794                  inner_value,
   795                  inner_required_types,
   796                  inner_path,
   797                  from_server,
   798                  _check_type,
   799                  configuration=configuration
   800              )
   801      elif isinstance(input_value, dict):
   802          if input_value == {}:
   803              # allow an empty dict
   804              return input_value
   805          for inner_key, inner_val in six.iteritems(input_value):
   806              inner_path = list(path_to_item)
   807              inner_path.append(inner_key)
   808              if get_simple_class(inner_key) != str:
   809                  raise get_type_error(inner_key, inner_path, valid_classes,
   810                                       key_type=True)
   811              input_value[inner_key] = validate_and_convert_types(
   812                  inner_val,
   813                  inner_required_types,
   814                  inner_path,
   815                  from_server,
   816                  _check_type,
   817                  configuration=configuration
   818              )
   819      return input_value
   820  
   821  
   822  def model_to_dict(model_instance, serialize=True):
   823      """Returns the model properties as a dict
   824  
   825      Args:
   826          model_instance (one of your model instances): the model instance that
   827              will be converted to a dict.
   828  
   829      Keyword Args:
   830          serialize (bool): if True, the keys in the dict will be values from
   831              attribute_map
   832      """
   833      result = {}
   834  
   835      model_instances = [model_instance]
   836      if model_instance._composed_schemas() is not None:
   837          model_instances.extend(model_instance._composed_instances)
   838      for model_instance in model_instances:
   839          for attr, value in six.iteritems(model_instance._data_store):
   840              if serialize:
   841                  # we use get here because additional property key names do not
   842                  # exist in attribute_map
   843                  attr = model_instance.attribute_map.get(attr, attr)
   844              if isinstance(value, list):
   845                  result[attr] = list(map(
   846                      lambda x: model_to_dict(x, serialize=serialize)
   847                      if hasattr(x, '_data_store') else x, value
   848                  ))
   849              elif isinstance(value, dict):
   850                  result[attr] = dict(map(
   851                      lambda item: (item[0],
   852                                    model_to_dict(item[1], serialize=serialize))
   853                      if hasattr(item[1], '_data_store') else item,
   854                      value.items()
   855                  ))
   856              elif hasattr(value, '_data_store'):
   857                  result[attr] = model_to_dict(value, serialize=serialize)
   858              else:
   859                  result[attr] = value
   860  
   861      return result
   862  
   863  
   864  def type_error_message(var_value=None, var_name=None, valid_classes=None,
   865                         key_type=None):
   866      """
   867      Keyword Args:
   868          var_value (any): the variable which has the type_error
   869          var_name (str): the name of the variable which has the typ error
   870          valid_classes (tuple): the accepted classes for current_item's
   871                                    value
   872          key_type (bool): False if our value is a value in a dict
   873                           True if it is a key in a dict
   874                           False if our item is an item in a list
   875      """
   876      key_or_value = 'value'
   877      if key_type:
   878          key_or_value = 'key'
   879      valid_classes_phrase = get_valid_classes_phrase(valid_classes)
   880      msg = (
   881          "Invalid type for variable '{0}'. Required {1} type {2} and "
   882          "passed type was {3}".format(
   883              var_name,
   884              key_or_value,
   885              valid_classes_phrase,
   886              type(var_value).__name__,
   887          )
   888      )
   889      return msg
   890  
   891  
   892  def get_valid_classes_phrase(input_classes):
   893      """Returns a string phrase describing what types are allowed
   894      Note: Adds the extra valid classes in python2
   895      """
   896      all_classes = list(input_classes)
   897      if six.PY2 and str in input_classes:
   898          all_classes.extend([str_py2, unicode_py2])
   899      if six.PY2 and int in input_classes:
   900          all_classes.extend([int_py2, long_py2])
   901      all_classes = sorted(all_classes, key=lambda cls: cls.__name__)
   902      all_class_names = [cls.__name__ for cls in all_classes]
   903      if len(all_class_names) == 1:
   904          return 'is {0}'.format(all_class_names[0])
   905      return "is one of [{0}]".format(", ".join(all_class_names))
   906  
   907  
   908  def get_py3_class_name(input_class):
   909      if six.PY2:
   910          if input_class == str:
   911              return 'str'
   912          elif input_class == int:
   913              return 'int'
   914      return input_class.__name__
   915  
   916  
   917  def get_allof_instances(self, model_args, constant_args):
   918      """
   919      Args:
   920          self: the class we are handling
   921          model_args (dict): var_name to var_value
   922              used to make instances
   923          constant_args (dict): var_name to var_value
   924              used to make instances
   925  
   926      Returns
   927          composed_instances (list)
   928      """
   929      composed_instances = []
   930      for allof_class in self._composed_schemas()['allOf']:
   931  
   932          # transform js keys to python keys in fixed_model_args
   933          fixed_model_args = change_keys_js_to_python(
   934              model_args, allof_class)
   935  
   936          # extract a dict of only required keys from fixed_model_args
   937          kwargs = {}
   938          var_names = set(allof_class.openapi_types().keys())
   939          for var_name in var_names:
   940              if var_name in fixed_model_args:
   941                  kwargs[var_name] = fixed_model_args[var_name]
   942  
   943          # and use it to make the instance
   944          kwargs.update(constant_args)
   945          try:
   946              allof_instance = allof_class(**kwargs)
   947              composed_instances.append(allof_instance)
   948          except Exception as ex:
   949              raise ApiValueError(
   950                  "Invalid inputs given to generate an instance of '%s'. The "
   951                  "input data was invalid for the allOf schema '%s' in the composed "
   952                  "schema '%s'. Error=%s" % (
   953                      allof_class.__class__.__name__,
   954                      allof_class.__class__.__name__,
   955                      self.__class__.__name__,
   956                      str(ex)
   957                  )
   958              )
   959      return composed_instances
   960  
   961  
   962  def get_oneof_instance(self, model_args, constant_args):
   963      """
   964      Find the oneOf schema that matches the input data (e.g. payload).
   965      If exactly one schema matches the input data, an instance of that schema
   966      is returned.
   967      If zero or more than one schema match the input data, an exception is raised.
   968      In OAS 3.x, the payload MUST, by validation, match exactly one of the
   969      schemas described by oneOf.
   970      Args:
   971          self: the class we are handling
   972          model_args (dict): var_name to var_value
   973              The input data, e.g. the payload that must match a oneOf schema
   974              in the OpenAPI document.
   975          constant_args (dict): var_name to var_value
   976              args that every model requires, including configuration, server
   977              and path to item.
   978  
   979      Returns
   980          oneof_instance (instance/None)
   981      """
   982      if len(self._composed_schemas()['oneOf']) == 0:
   983          return None
   984  
   985      oneof_instances = []
   986      # Iterate over each oneOf schema and determine if the input data
   987      # matches the oneOf schemas.
   988      for oneof_class in self._composed_schemas()['oneOf']:
   989          # transform js keys from input data to python keys in fixed_model_args
   990          fixed_model_args = change_keys_js_to_python(
   991              model_args, oneof_class)
   992  
   993          # Extract a dict with the properties that are declared in the oneOf schema.
   994          # Undeclared properties (e.g. properties that are allowed because of the
   995          # additionalProperties attribute in the OAS document) are not added to
   996          # the dict.
   997          kwargs = {}
   998          var_names = set(oneof_class.openapi_types().keys())
   999          for var_name in var_names:
  1000              if var_name in fixed_model_args:
  1001                  kwargs[var_name] = fixed_model_args[var_name]
  1002  
  1003          # do not try to make a model with no input args
  1004          if len(kwargs) == 0:
  1005              continue
  1006  
  1007          # and use it to make the instance
  1008          kwargs.update(constant_args)
  1009          try:
  1010              oneof_instance = oneof_class(**kwargs)
  1011              oneof_instances.append(oneof_instance)
  1012          except Exception:
  1013              pass
  1014      if len(oneof_instances) == 0:
  1015          raise ApiValueError(
  1016              "Invalid inputs given to generate an instance of %s. None "
  1017              "of the oneOf schemas matched the input data." %
  1018              self.__class__.__name__
  1019          )
  1020      elif len(oneof_instances) > 1:
  1021          raise ApiValueError(
  1022              "Invalid inputs given to generate an instance of %s. Multiple "
  1023              "oneOf schemas matched the inputs, but a max of one is allowed." %
  1024              self.__class__.__name__
  1025          )
  1026      return oneof_instances[0]
  1027  
  1028  
  1029  def get_anyof_instances(self, model_args, constant_args):
  1030      """
  1031      Args:
  1032          self: the class we are handling
  1033          model_args (dict): var_name to var_value
  1034              used to make instances
  1035          constant_args (dict): var_name to var_value
  1036              used to make instances
  1037  
  1038      Returns
  1039          anyof_instances (list)
  1040      """
  1041      anyof_instances = []
  1042      if len(self._composed_schemas()['anyOf']) == 0:
  1043          return anyof_instances
  1044  
  1045      for anyof_class in self._composed_schemas()['anyOf']:
  1046          # transform js keys to python keys in fixed_model_args
  1047          fixed_model_args = change_keys_js_to_python(model_args, anyof_class)
  1048  
  1049          # extract a dict of only required keys from these_model_vars
  1050          kwargs = {}
  1051          var_names = set(anyof_class.openapi_types().keys())
  1052          for var_name in var_names:
  1053              if var_name in fixed_model_args:
  1054                  kwargs[var_name] = fixed_model_args[var_name]
  1055  
  1056          # do not try to make a model with no input args
  1057          if len(kwargs) == 0:
  1058              continue
  1059  
  1060          # and use it to make the instance
  1061          kwargs.update(constant_args)
  1062          try:
  1063              anyof_instance = anyof_class(**kwargs)
  1064              anyof_instances.append(anyof_instance)
  1065          except Exception:
  1066              pass
  1067      if len(anyof_instances) == 0:
  1068          raise ApiValueError(
  1069              "Invalid inputs given to generate an instance of %s. None of the "
  1070              "anyOf schemas matched the inputs." %
  1071              self.__class__.__name__
  1072          )
  1073      return anyof_instances
  1074  
  1075  
  1076  def get_additional_properties_model_instances(
  1077          composed_instances, self):
  1078      additional_properties_model_instances = []
  1079      all_instances = [self]
  1080      all_instances.extend(composed_instances)
  1081      for instance in all_instances:
  1082          if instance.additional_properties_type is not None:
  1083              additional_properties_model_instances.append(instance)
  1084      return additional_properties_model_instances
  1085  
  1086  
  1087  def get_var_name_to_model_instances(self, composed_instances):
  1088      var_name_to_model_instances = {}
  1089      all_instances = [self]
  1090      all_instances.extend(composed_instances)
  1091      for instance in all_instances:
  1092          for var_name in instance.openapi_types():
  1093              if var_name not in var_name_to_model_instances:
  1094                  var_name_to_model_instances[var_name] = [instance]
  1095              else:
  1096                  var_name_to_model_instances[var_name].append(instance)
  1097      return var_name_to_model_instances
  1098  
  1099  
  1100  def get_unused_args(self, composed_instances, model_args):
  1101      unused_args = dict(model_args)
  1102      # arguments apssed to self were already converted to python names
  1103      # before __init__ was called
  1104      for var_name_py in self.attribute_map:
  1105          if var_name_py in unused_args:
  1106              del unused_args[var_name_py]
  1107      for instance in composed_instances:
  1108          if instance.__class__ in self._composed_schemas()['allOf']:
  1109              for var_name_py in instance.attribute_map:
  1110                  if var_name_py in unused_args:
  1111                      del unused_args[var_name_py]
  1112          else:
  1113              for var_name_js in instance.attribute_map.values():
  1114                  if var_name_js in unused_args:
  1115                      del unused_args[var_name_js]
  1116      return unused_args
  1117  
  1118  
  1119  def validate_get_composed_info(constant_args, model_args, self):
  1120      """
  1121      For composed schemas, generate schema instances for
  1122      all schemas in the oneOf/anyOf/allOf definition. If additional
  1123      properties are allowed, also assign those properties on
  1124      all matched schemas that contain additionalProperties.
  1125      Openapi schemas are python classes.
  1126  
  1127      Exceptions are raised if:
  1128      - no oneOf schema matches the model_args input data
  1129      - > 1 oneOf schema matches the model_args input data
  1130      - > 1 oneOf schema matches the model_args input data
  1131      - no anyOf schema matches the model_args input data
  1132      - any of the allOf schemas do not match the model_args input data
  1133  
  1134      Args:
  1135          constant_args (dict): these are the args that every model requires
  1136          model_args (dict): these are the required and optional spec args that
  1137              were passed in to make this model
  1138          self (class): the class that we are instantiating
  1139              This class contains self._composed_schemas()
  1140  
  1141      Returns:
  1142          composed_info (list): length three
  1143              composed_instances (list): the composed instances which are not
  1144                  self
  1145              var_name_to_model_instances (dict): a dict going from var_name
  1146                  to the model_instance which holds that var_name
  1147                  the model_instance may be self or an instance of one of the
  1148                  classes in self.composed_instances()
  1149              additional_properties_model_instances (list): a list of the
  1150                  model instances which have the property
  1151                  additional_properties_type. This list can include self
  1152      """
  1153      # create composed_instances
  1154      composed_instances = []
  1155      allof_instances = get_allof_instances(self, model_args, constant_args)
  1156      composed_instances.extend(allof_instances)
  1157      oneof_instance = get_oneof_instance(self, model_args, constant_args)
  1158      if oneof_instance is not None:
  1159          composed_instances.append(oneof_instance)
  1160      anyof_instances = get_anyof_instances(self, model_args, constant_args)
  1161      composed_instances.extend(anyof_instances)
  1162  
  1163      # map variable names to composed_instances
  1164      var_name_to_model_instances = get_var_name_to_model_instances(
  1165          self, composed_instances)
  1166  
  1167      # set additional_properties_model_instances
  1168      additional_properties_model_instances = (
  1169          get_additional_properties_model_instances(composed_instances, self)
  1170      )
  1171  
  1172      # set any remaining values
  1173      unused_args = get_unused_args(self, composed_instances, model_args)
  1174      if len(unused_args) > 0 and \
  1175              len(additional_properties_model_instances) == 0 and \
  1176              (self._configuration is None or
  1177                  not self._configuration.discard_unknown_keys):
  1178          raise ApiValueError(
  1179              "Invalid input arguments input when making an instance of "
  1180              "class %s. Not all inputs were used. The unused input data "
  1181              "is %s" % (self.__class__.__name__, unused_args)
  1182          )
  1183  
  1184      # no need to add additional_properties to var_name_to_model_instances here
  1185      # because additional_properties_model_instances will direct us to that
  1186      # instance when we use getattr or setattr
  1187      # and we update var_name_to_model_instances in setattr
  1188  
  1189      return [
  1190        composed_instances,
  1191        var_name_to_model_instances,
  1192        additional_properties_model_instances,
  1193        unused_args
  1194      ]