All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openapitools.codegen.languages.PythonClientExperimentalCodegen Maven / Gradle / Ivy

/*
 * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.openapitools.codegen.languages;

import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.examples.ExampleGenerator;
import org.openapitools.codegen.meta.features.*;
import org.openapitools.codegen.utils.ModelUtils;
import org.openapitools.codegen.utils.ProcessUtils;
import org.openapitools.codegen.meta.GeneratorMetadata;
import org.openapitools.codegen.meta.Stability;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.io.File;
import java.util.*;
import java.util.regex.Pattern;

import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.underscore;

public class PythonClientExperimentalCodegen extends PythonClientCodegen {
    private static final Logger LOGGER = LoggerFactory.getLogger(PythonClientExperimentalCodegen.class);

    public PythonClientExperimentalCodegen() {
        super();

        modifyFeatureSet(features -> features
                .includeDocumentationFeatures(DocumentationFeature.Readme)
                .wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON, WireFormatFeature.XML, WireFormatFeature.Custom))
                .securityFeatures(EnumSet.of(
                        SecurityFeature.BasicAuth,
                        SecurityFeature.BearerToken,
                        SecurityFeature.ApiKey,
                        SecurityFeature.OAuth2_Implicit
                ))
                .excludeGlobalFeatures(
                        GlobalFeature.XMLStructureDefinitions,
                        GlobalFeature.Callbacks,
                        GlobalFeature.LinkObjects,
                        GlobalFeature.ParameterStyling
                )
                .excludeSchemaSupportFeatures(
                        SchemaSupportFeature.Polymorphism
                )
                .excludeParameterFeatures(
                        ParameterFeature.Cookie
                )
         );

        // this may set datatype right for additional properties
        instantiationTypes.put("map", "dict");

        languageSpecificPrimitives.add("file_type");
        languageSpecificPrimitives.add("none_type");

        apiTemplateFiles.remove("api.mustache");
        apiTemplateFiles.put("python-experimental/api.mustache", ".py");

        apiDocTemplateFiles.remove("api_doc.mustache");
        apiDocTemplateFiles.put("python-experimental/api_doc.mustache", ".md");

        modelDocTemplateFiles.remove("model_doc.mustache");
        modelDocTemplateFiles.put("python-experimental/model_doc.mustache", ".md");

        modelTemplateFiles.remove("model.mustache");
        modelTemplateFiles.put("python-experimental/model.mustache", ".py");

        modelTestTemplateFiles.remove("model_test.mustache", ".py");
        modelTestTemplateFiles.put("python-experimental/model_test.mustache", ".py");

        // this generator does not use SORT_PARAMS_BY_REQUIRED_FLAG
        // this generator uses the following order for endpoint paramters and model properties
        // required params/props with no enum of length one
        // required params/props with enum of length one (which is used to set a default value as a python named arg value)
        // optional params/props with **kwargs in python
        cliOptions.remove(4);

        generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
                .stability(Stability.EXPERIMENTAL)
                .build();
    }

    @Override
    public void processOpts() {
        super.processOpts();

        supportingFiles.remove(new SupportingFile("api_client.mustache", packagePath(), "api_client.py"));
        supportingFiles.add(new SupportingFile("python-experimental/api_client.mustache", packagePath(), "api_client.py"));

        supportingFiles.add(new SupportingFile("python-experimental/model_utils.mustache", packagePath(), "model_utils.py"));

        supportingFiles.remove(new SupportingFile("__init__model.mustache", packagePath() + File.separatorChar + "models", "__init__.py"));
        supportingFiles.add(new SupportingFile("python-experimental/__init__model.mustache", packagePath() + File.separatorChar + "models", "__init__.py"));

        supportingFiles.remove(new SupportingFile("__init__package.mustache", packagePath(), "__init__.py"));
        supportingFiles.add(new SupportingFile("python-experimental/__init__package.mustache", packagePath(), "__init__.py"));


        // Generate the 'signing.py' module, but only if the 'HTTP signature' security scheme is specified in the OAS.
        Map securitySchemeMap = openAPI != null ?
           (openAPI.getComponents() != null ? openAPI.getComponents().getSecuritySchemes() : null) : null;
        List authMethods = fromSecurity(securitySchemeMap);
        if (ProcessUtils.hasHttpSignatureMethods(authMethods)) {
            supportingFiles.add(new SupportingFile("python-experimental/signing.mustache", packagePath(), "signing.py"));
        }

        Boolean generateSourceCodeOnly = false;
        if (additionalProperties.containsKey(CodegenConstants.SOURCECODEONLY_GENERATION)) {
            generateSourceCodeOnly = Boolean.valueOf(additionalProperties.get(CodegenConstants.SOURCECODEONLY_GENERATION).toString());
        }

        // remove what PythonClientCodegen did
        String readmePath = "README.md";
        String readmeTemplate = "README.mustache";
        if (generateSourceCodeOnly) {
            readmePath = packagePath() + "_" + readmePath;
            readmeTemplate = "README_onlypackage.mustache";
        }
        supportingFiles.remove(new SupportingFile(readmeTemplate, "", readmePath));
        // add the correct readme
        readmeTemplate = "python-experimental/README.mustache";
        if (generateSourceCodeOnly) {
            readmeTemplate = "python-experimental/README_onlypackage.mustache";
        }
        supportingFiles.add(new SupportingFile(readmeTemplate, "", readmePath));

        if (!generateSourceCodeOnly) {
          supportingFiles.remove(new SupportingFile("setup.mustache", "", "setup.py"));
          supportingFiles.add(new SupportingFile("python-experimental/setup.mustache", "", "setup.py"));
          supportingFiles.remove(new SupportingFile("requirements.mustache", "", "requirements.txt"));
          supportingFiles.add(new SupportingFile("python-experimental/requirements.mustache", "", "requirements.txt"));
          supportingFiles.remove(new SupportingFile("test-requirements.mustache", "", "test-requirements.txt"));
          supportingFiles.add(new SupportingFile("python-experimental/test-requirements.mustache", "", "test-requirements.txt"));
        }

        // default this to true so the python ModelSimple models will be generated
        ModelUtils.setGenerateAliasAsModel(true);
        LOGGER.info(CodegenConstants.GENERATE_ALIAS_AS_MODEL + " is hard coded to true in this generator. Alias models will only be generated if they contain validations or enums");
    }

    /**
     * Configures a friendly name for the generator.  This will be used by the
     * generator to select the library with the -g flag.
     *
     * @return the friendly name for the generator
     */
    @Override
    public String getName() {
        return "python-experimental";
    }

    public String dateToString(Schema p, Date date, DateFormat dateFormatter, DateFormat dateTimeFormatter) {
        // converts a date into a date or date-time python string
        if (!(ModelUtils.isDateSchema(p) || ModelUtils.isDateTimeSchema(p))) {
            throw new RuntimeException("passed schema must be of type Date or DateTime");
        }
        if (ModelUtils.isDateSchema(p)) {
            return "dateutil_parser('" + dateFormatter.format(date) + "').date()";
        }
        return "dateutil_parser('" + dateTimeFormatter.format(date) + "')";
    }

    /**
     * Return the default value of the property
     *
     * @param p OpenAPI property object
     * @return string presentation of the default value of the property
     */
    @Override
    public String toDefaultValue(Schema p) {
        // if a variable has no default set and only has one allowed value
        // using enum of length == 1 we use that value. Server/client usage:
        // python servers: should only use default values for optional params
        // python clients: should only use default values for required params
        Object defaultObject = null;
        Boolean enumLengthOne = (p.getEnum() != null && p.getEnum().size() == 1);
        if (p.getDefault() != null) {
            defaultObject = p.getDefault();
        } else if (enumLengthOne) {
            defaultObject = p.getEnum().get(0);
        }

        // convert datetime and date enums if they exist
        DateFormat iso8601Date = new SimpleDateFormat("yyyy-MM-dd", Locale.ROOT);
        DateFormat iso8601DateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ROOT);
        TimeZone utc = TimeZone.getTimeZone("UTC");
        iso8601Date.setTimeZone(utc);
        iso8601DateTime.setTimeZone(utc);

        if (ModelUtils.isDateSchema(p) || ModelUtils.isDateTimeSchema(p)) {
            List currentEnum = p.getEnum();
            List fixedEnum = new ArrayList();
            String fixedValue = null;
            Date date = null;
            if (currentEnum != null && !currentEnum.isEmpty()) {
                for (Object enumItem : currentEnum) {
                    date = (Date) enumItem;
                    fixedValue = dateToString(p, date, iso8601Date, iso8601DateTime);
                    fixedEnum.add(fixedValue);
                }
                p.setEnum(fixedEnum);
            }

            // convert the example if it exists
            Object currentExample = p.getExample();
            if (currentExample != null) {
                date = (Date) currentExample;
                fixedValue = dateToString(p, date, iso8601Date, iso8601DateTime);
                fixedEnum.add(fixedValue);
                p.setExample(fixedValue);
            }

            // fix defaultObject
            if (defaultObject != null) {
                date = (Date) defaultObject;
                fixedValue = dateToString(p, date, iso8601Date, iso8601DateTime);
                p.setDefault(fixedValue);
                defaultObject = fixedValue;
            }
        }

        if (defaultObject == null) {
            return null;
        }

        String defaultValue = null;
        if (ModelUtils.isStringSchema(p)) {
            defaultValue = defaultObject.toString();
            if (ModelUtils.isDateSchema(p) || ModelUtils.isDateTimeSchema(p)) {
                return defaultValue;
            }

            if (!ModelUtils.isByteArraySchema(p) && !ModelUtils.isBinarySchema(p) && !ModelUtils.isFileSchema(p) && !ModelUtils.isUUIDSchema(p) && !ModelUtils.isEmailSchema(p) && !ModelUtils.isDateTimeSchema(p) && !ModelUtils.isDateSchema(p)) {
                if (Pattern.compile("\r\n|\r|\n").matcher((String) defaultValue).find()) {
                    defaultValue = "'''" + defaultValue + "'''";
                } else {
                    defaultValue = "'" + defaultValue + "'";
                }
            }
            return defaultValue;
        } else if (ModelUtils.isIntegerSchema(p) || ModelUtils.isNumberSchema(p) || ModelUtils.isBooleanSchema(p)) {
            defaultValue = String.valueOf(defaultObject);
            if (ModelUtils.isBooleanSchema(p)) {
                if (Boolean.valueOf(defaultValue) == false) {
                    return "False";
                } else {
                    return "True";
                }
            }
            return defaultValue;
        } else {
            return defaultObject.toString();
        }
    }

    @Override
    public String toModelImport(String name) {
        // name looks like cat.Cat
        String moduleName = name.split("\\.")[0];
        // https://exceptionshub.com/circular-or-cyclic-imports-in-python.html
        return "from " + modelPackage() + " import "+ moduleName;
    }

    private String robustImport(String name) {
        // name looks like cat.Cat
        String moduleName = name.split("\\.")[0];
        // https://exceptionshub.com/circular-or-cyclic-imports-in-python.html
        String modelImport = "try:\n    from " + modelPackage() +
          " import " + moduleName+ "\nexcept ImportError:\n    " +
          moduleName + " = sys.modules[\n        '" + modelPackage() + "." + moduleName + "']";
        return modelImport;
    }

    private String getPythonClassName(String name) {
        // name looks like cat.Cat or Cat
        String[] pieces = name.split("\\.");
        if (pieces.length == 1) {
            return pieces[0];
        }
        return pieces[1];
    }

    private void fixOperationImports(Set imports) {
        if (imports.size() == 0) {
            return;
        }
        String[] modelNames = imports.toArray(new String[0]);
        imports.clear();
        // loops through imports and converts them all from 'module.Class' to 'from  models.module import module'
        for (String modelName : modelNames) {
            // if a modelName lacks the module (Pet) then we convert it to pet.Pet
            if (modelName.indexOf(".") == -1) {
                modelName = toModelName(modelName);
            }
            imports.add(toModelImport(modelName));
        }
    }

    @Override
    @SuppressWarnings("static-method")
    public Map postProcessOperationsWithModels(Map objs, List allModels) {
        HashMap val = (HashMap)objs.get("operations");
        ArrayList operations = (ArrayList) val.get("operation");
        ArrayList> imports = (ArrayList>)objs.get("imports");
        imports.clear();
        for (CodegenOperation operation : operations) {
            fixOperationImports(operation.imports);
            for (String thisImport : operation.imports) {
                HashMap higherImport = new HashMap();
                higherImport.put("import", thisImport);
                if (!imports.contains(higherImport)) {
                    imports.add(higherImport);
                }
            }
        }
        return objs;
    }

    private void fixModelImports(Set imports) {
        // loops through imports and converts them all from 'module.Class' to 'import models.module as module'
        if (imports.size() == 0) {
            return;
        }
        String[] modelNames = imports.toArray(new String[0]);
        imports.clear();
        for (String modelName : modelNames) {
            imports.add(robustImport(modelName));
        }
    }

    // override with any special post-processing for all models
    @SuppressWarnings({"static-method", "unchecked"})
    public Map postProcessAllModels(Map objs) {
        // loop through all models and delete ones where type!=object and the model has no validations and enums
        // we will remove them because they are not needed
        Map modelSchemasToRemove = new HashMap();
        for (Map.Entry entry : objs.entrySet()) {
            Map inner = (Map) entry.getValue();
            List> models = (List>) inner.get("models");
            for (Map mo : models) {
                CodegenModel cm = (CodegenModel) mo.get("model");

                // make sure discrimonator models are included in imports
                CodegenDiscriminator discriminator = cm.discriminator;
                if (discriminator != null) {
                    Set mappedModels = discriminator.getMappedModels();
                    for (CodegenDiscriminator.MappedModel mappedModel : mappedModels) {
                        String otherModelName = mappedModel.getModelName();
                        cm.imports.add(otherModelName);
                    }
                }

                // add imports for anyOf, allOf, oneOf
                ArrayList> composedSchemaSets = new ArrayList>();
                composedSchemaSets.add(cm.allOf);
                composedSchemaSets.add(cm.anyOf);
                composedSchemaSets.add(cm.oneOf);
                for (Set importSet : composedSchemaSets) {
                    for (String otherModelName : importSet) {
                        cm.imports.add(otherModelName);
                    }
                }

                // fix the imports that each model has, change them to absolute
                fixModelImports(cm.imports);

                Schema modelSchema = ModelUtils.getSchema(this.openAPI, cm.name);
                CodegenProperty modelProperty = fromProperty("value", modelSchema);
                if (cm.isEnum || cm.isAlias) {
                    if (!modelProperty.isEnum && !modelProperty.hasValidation) {
                        // remove these models because they are aliases and do not have any enums or validations
                        modelSchemasToRemove.put(cm.name, modelSchema);
                    }
                } else if (cm.isArrayModel && !modelProperty.isEnum && !modelProperty.hasValidation) {
                    // remove any ArrayModels which lack validation and enums
                    modelSchemasToRemove.put(cm.name, modelSchema);
                }
            }
        }

        // Remove modelSchemasToRemove models from objs
        for (String modelName : modelSchemasToRemove.keySet()) {
            objs.remove(modelName);
        }
        return objs;
    }

    /**
     * Convert OAS Property object to Codegen Property object
     *
     * @param name name of the property
     * @param p    OAS property object
     * @return Codegen Property object
     */
    @Override
    public CodegenProperty fromProperty(String name, Schema p) {
        // we have a custom version of this function to always set allowableValues.enumVars on all enum variables
        CodegenProperty result = super.fromProperty(name, p);
        if (result.isEnum) {
            updateCodegenPropertyEnum(result);
        }
        return result;
    }

    /**
     * Update codegen property's enum by adding "enumVars" (with name and value)
     *
     * @param var list of CodegenProperty
     */
    @Override
    public void updateCodegenPropertyEnum(CodegenProperty var) {
        // we have a custom version of this method to omit overwriting the defaultValue
        Map allowableValues = var.allowableValues;

        // handle array
        if (var.mostInnerItems != null) {
            allowableValues = var.mostInnerItems.allowableValues;
        }

        if (allowableValues == null) {
            return;
        }

        List values = (List) allowableValues.get("values");
        if (values == null) {
            return;
        }

        String varDataType = var.mostInnerItems != null ? var.mostInnerItems.dataType : var.dataType;
        Optional referencedSchema = ModelUtils.getSchemas(openAPI).entrySet().stream()
                .filter(entry -> Objects.equals(varDataType, toModelName(entry.getKey())))
                .map(Map.Entry::getValue)
                .findFirst();
        String dataType = (referencedSchema.isPresent()) ? getTypeDeclaration(referencedSchema.get()) : varDataType;

        // put "enumVars" map into `allowableValues", including `name` and `value`
        List> enumVars = buildEnumVars(values, dataType);

        // if "x-enum-varnames" or "x-enum-descriptions" defined, update varnames
        Map extensions = var.mostInnerItems != null ? var.mostInnerItems.getVendorExtensions() : var.getVendorExtensions();
        if (referencedSchema.isPresent()) {
            extensions = referencedSchema.get().getExtensions();
        }
        updateEnumVarsWithExtensions(enumVars, extensions);
        allowableValues.put("enumVars", enumVars);
        // overwriting defaultValue omitted from here
    }

    @Override
    public CodegenParameter fromRequestBody(RequestBody body, Set imports, String bodyParameterName) {
        CodegenParameter result = super.fromRequestBody(body, imports, bodyParameterName);
        // if we generated a model with a non-object type because it has validations or enums,
        // make sure that the datatype of that body parameter refers to our model class
        Content content = body.getContent();
        Set keySet = content.keySet();
        Object[] keyArray = (Object[]) keySet.toArray();
        MediaType mediaType = content.get(keyArray[0]);
        Schema schema = mediaType.getSchema();
        String ref = schema.get$ref();
        if (ref == null) {
            return result;
        }
        String modelName = ModelUtils.getSimpleRef(ref);
        // the result lacks validation info so we need to make a CodegenProperty from the schema to check
        // if we have validation and enum info exists
        Schema realSchema = ModelUtils.getSchema(this.openAPI, modelName);
        CodegenProperty modelProp = fromProperty("body", realSchema);
        if (modelProp.isPrimitiveType && (modelProp.hasValidation || modelProp.isEnum)) {
            String simpleDataType = result.dataType;
            result.dataType = toModelName(modelName);
            result.baseType = getPythonClassName(result.dataType);
            imports.remove(modelName);
            imports.add(result.dataType);
            // set the example value
            if (modelProp.isEnum) {
                String value = modelProp._enum.get(0).toString();
                result.example = this.packageName + "." + result.baseType + "(" + toEnumValue(value, simpleDataType) + ")";
            } else {
                result.example = this.packageName + "." + result.baseType + "(" + result.example + ")";
            }
        } else if (!result.isPrimitiveType) {
            // fix the baseType for the api docs so the .md link to the class's documentation file is correct
            result.baseType = getPythonClassName(result.baseType);
        }
        return result;
    }

    /**
     * Convert OAS Response object to Codegen Response object
     *
     * @param responseCode HTTP response code
     * @param response     OAS Response object
     * @return Codegen Response object
     */
    @Override
    public CodegenResponse fromResponse(String responseCode, ApiResponse response) {
        // if a response points at a model whose type != object and it has validations and/or enums, then we will
        // generate the model, and response.baseType must be the name
        // of the model. Point responses at models if the model is python class type ModelSimple
        // When we serialize/deserialize ModelSimple models, validations and enums will be checked.
        Schema responseSchema;
        if (this.openAPI != null && this.openAPI.getComponents() != null) {
            responseSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getSchemaFromResponse(response), importMapping);
        } else { // no model/alias defined
            responseSchema = ModelUtils.getSchemaFromResponse(response);
        }

        String newBaseType = null;
        if (responseSchema != null) {
            CodegenProperty cp = fromProperty("response", responseSchema);
            if (cp.complexType != null) {
                String modelName = getPythonClassName(cp.complexType);
                Schema modelSchema = ModelUtils.getSchema(this.openAPI, modelName);
                if (modelSchema != null && !"object".equals(modelSchema.getType())) {
                    CodegenProperty modelProp = fromProperty("response", modelSchema);
                    if (modelProp.isEnum == true || modelProp.hasValidation == true) {
                        // this model has validations and/or enums so we will generate it
                        newBaseType = modelName;
                    }
                }
            } else {
                if (cp.isEnum == true || cp.hasValidation == true) {
                    // this model has validations and/or enums so we will generate it
                    Schema sc = ModelUtils.getSchemaFromResponse(response);
                    newBaseType = ModelUtils.getSimpleRef(sc.get$ref());
                }
            }
        }

        CodegenResponse result = super.fromResponse(responseCode, response);
        if (newBaseType != null) {
            result.dataType = toModelName(newBaseType);
            // baseType is used to set the link to the model .md documentation
            result.baseType = getPythonClassName(newBaseType);
        } else if (!result.primitiveType) {
            // fix the baseType for the api docs so the .md link to the class's documentation file is correct
            result.baseType = getPythonClassName(result.baseType);
        }

        return result;
    }

    /**
     * Set op's returnBaseType, returnType, examples etc.
     *
     * @param operation      endpoint Operation
     * @param schemas        a map of the schemas in the openapi spec
     * @param op             endpoint CodegenOperation
     * @param methodResponse the default ApiResponse for the endpoint
     */
    @Override
    public void handleMethodResponse(Operation operation,
                                     Map schemas,
                                     CodegenOperation op,
                                     ApiResponse methodResponse) {
        handleMethodResponse(operation, schemas, op, methodResponse, Collections.emptyMap());
    }

    /**
     * Set op's returnBaseType, returnType, examples etc.
     *
     * @param operation      endpoint Operation
     * @param schemas        a map of the schemas in the openapi spec
     * @param op             endpoint CodegenOperation
     * @param methodResponse the default ApiResponse for the endpoint
     * @param importMappings mappings of external types to be omitted by unaliasing
     */
    @Override
    protected void handleMethodResponse(Operation operation,
                                        Map schemas,
                                        CodegenOperation op,
                                        ApiResponse methodResponse,
                                        Map importMappings) {
        // we have a custom version of this method to handle endpoints that return models where
        // type != object the model has validations and/or enums
        // we do this by invoking our custom fromResponse method to create defaultResponse
        // which we then use to set op.returnType and op.returnBaseType
        CodegenResponse defaultResponse = fromResponse("defaultResponse", methodResponse);
        Schema responseSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getSchemaFromResponse(methodResponse), importMappings);

        if (responseSchema != null) {
            op.returnBaseType = defaultResponse.baseType;

            // generate examples
            String exampleStatusCode = "200";
            for (String key : operation.getResponses().keySet()) {
                if (operation.getResponses().get(key) == methodResponse && !key.equals("default")) {
                    exampleStatusCode = key;
                }
            }
            op.examples = new ExampleGenerator(schemas, this.openAPI).generateFromResponseSchema(exampleStatusCode, responseSchema, getProducesInfo(this.openAPI, operation));
            op.defaultResponse = toDefaultValue(responseSchema);
            op.returnType = defaultResponse.dataType;
            op.hasReference = schemas.containsKey(op.returnBaseType);

            // lookup discriminator
            Schema schema = schemas.get(op.returnBaseType);
            if (schema != null) {
                CodegenModel cmod = fromModel(op.returnBaseType, schema);
                op.discriminator = cmod.discriminator;
            }

            if (defaultResponse.isListContainer) {
                op.isListContainer = true;
            } else if (defaultResponse.isMapContainer) {
                op.isMapContainer = true;
            } else {
                op.returnSimpleType = true;
            }
            if (languageSpecificPrimitives().contains(op.returnBaseType) || op.returnBaseType == null) {
                op.returnTypeIsPrimitive = true;
            }
        }
        addHeaders(methodResponse, op.responseHeaders);
    }


    /**
     * Return the sanitized variable name for enum
     *
     * @param value    enum variable name
     * @param datatype data type
     * @return the sanitized variable name for enum
     */
    public String toEnumVarName(String value, String datatype) {
        // our enum var names are keys in a python dict, so change spaces to underscores
        if (value.length() == 0) {
            return "EMPTY";
        }

        String var = value.replaceAll("\\s+", "_").toUpperCase(Locale.ROOT);
        return var;
    }

    /**
     * Return the enum value in the language specified format
     * e.g. status becomes "status"
     *
     * @param value    enum variable name
     * @param datatype data type
     * @return the sanitized value for enum
     */
    public String toEnumValue(String value, String datatype) {
        if (datatype.equals("int") || datatype.equals("float")) {
            return value;
        } else {
            return "\"" + escapeText(value) + "\"";
        }
    }

    @Override
    public void postProcessModelProperty(CodegenModel model, CodegenProperty p) {
        postProcessPattern(p.pattern, p.vendorExtensions);
        // set property.complexType so the model docs will link to the ClassName.md
        if (p.complexType != null && !languageSpecificPrimitives.contains(p.complexType)) {
            p.complexType = getPythonClassName(p.complexType);
        } else if (p.isListContainer && p.mostInnerItems.complexType != null && !languageSpecificPrimitives.contains(p.mostInnerItems.complexType)) {
            // fix ListContainers
            p.complexType = getPythonClassName(p.mostInnerItems.complexType);
        }
        // if a model has a property that is of type self, remove the module name from the dataType
        if (p.complexType != null && p.dataType.contains(model.classname)) {
            String classNameNoModule = getPythonClassName(model.classname);
            p.dataType = p.dataType.replace(model.classname, classNameNoModule);
        }
    }

    @Override
    public void postProcessParameter(CodegenParameter p) {
        postProcessPattern(p.pattern, p.vendorExtensions);
        // set baseType to null so the api docs will not point to a model for languageSpecificPrimitives
        if (p.baseType != null && languageSpecificPrimitives.contains(p.baseType)){
            p.baseType = null;
        } else if (p.isListContainer && p.mostInnerItems.complexType != null && !languageSpecificPrimitives.contains(p.mostInnerItems.complexType)) {
            // fix ListContainers
            p.baseType = getPythonClassName(p.mostInnerItems.complexType);
        }
    }

    private void addNullDefaultToOneOfAnyOfReqProps(Schema schema, CodegenModel result){
        // for composed schema models, if the required properties are only from oneOf or anyOf models
        // give them a nulltype.Null so the user can omit including them in python
        ComposedSchema cs = (ComposedSchema) schema;

        // these are the properties that are from properties in self cs or cs allOf
        Map selfProperties = new LinkedHashMap();
        List selfRequired = new ArrayList();

        // these are the properties that are from properties in cs oneOf or cs anyOf
        Map otherProperties = new LinkedHashMap();
        List otherRequired = new ArrayList();

        List oneOfanyOfSchemas = new ArrayList<>();
        List oneOf = cs.getOneOf();
        if (oneOf != null) {
            oneOfanyOfSchemas.addAll(oneOf);
        }
        List anyOf = cs.getAnyOf();
        if (anyOf != null) {
            oneOfanyOfSchemas.addAll(anyOf);
        }
        for (Schema sc: oneOfanyOfSchemas) {
            Schema refSchema = ModelUtils.getReferencedSchema(this.openAPI, sc);
            addProperties(otherProperties, otherRequired, refSchema);
        }
        Set otherRequiredSet = new HashSet(otherRequired);

        List allOf = cs.getAllOf();
        if ((schema.getProperties() != null && !schema.getProperties().isEmpty()) || allOf != null) {
            // NOTE: this function also adds the allOf propesrties inside schema
            addProperties(selfProperties, selfRequired, schema);
        }
        if (result.discriminator != null) {
            selfRequired.add(result.discriminator.getPropertyBaseName());
        }
        Set selfRequiredSet = new HashSet(selfRequired);

        List reqVars = result.getRequiredVars();
        if (reqVars != null) {
            for (CodegenProperty cp: reqVars) {
                String propName = cp.baseName;
                if (otherRequiredSet.contains(propName) && !selfRequiredSet.contains(propName)) {
                    // if var is in otherRequiredSet and is not in selfRequiredSet and is in result.requiredVars
                    // then set it to nullable because the user doesn't have to give a value for it
                    cp.setDefaultValue("nulltype.Null");
                }
            }
        }
    }

    /**
     * Convert OAS Model object to Codegen Model object
     *
     * @param name   the name of the model
     * @param schema OAS Model object
     * @return Codegen Model object
     */
    @Override
    public CodegenModel fromModel(String name, Schema schema) {
        // we have a custom version of this function so we can produce
        // models for components whose type != object and which have validations and enums
        // this ensures that endpoint (operation) responses with validations and enums
        // will generate models, and when those endpoint responses are received in python
        // the response is cast as a model, and the model will validate the response using the enums and validations
        Map propertyToModelName = new HashMap();
        Map propertiesMap = schema.getProperties();
        if (propertiesMap != null) {
            for (Map.Entry entry : propertiesMap.entrySet()) {
                String schemaPropertyName = entry.getKey();
                String pythonPropertyName = toVarName(schemaPropertyName);
                Schema propertySchema = entry.getValue();
                String ref = propertySchema.get$ref();
                if (ref == null) {
                    continue;
                }
                Schema refSchema = ModelUtils.getReferencedSchema(this.openAPI, propertySchema);
                String refType = refSchema.getType();
                if (refType == null || refType.equals("object")) {
                    continue;
                }
                CodegenProperty modelProperty = fromProperty("_fake_name", refSchema);
                if (modelProperty.isEnum == false && modelProperty.hasValidation == false) {
                    continue;
                }
                String modelName = ModelUtils.getSimpleRef(ref);
                propertyToModelName.put(pythonPropertyName, toModelName(modelName));
            }
        }
        CodegenModel result = super.fromModel(name, schema);
        // use this to store the model name like Cat
        // we can't use result.name because that is used to lookup models in the spec
        // we can't use result.classname because that stores cat.Cat
        // we can't use result.classVarName because that stores the variable for making example instances
        result.unescapedDescription = simpleModelName(name);

        // make non-object type models have one property so we can use it to store enums and validations
        if (result.isAlias || result.isEnum) {
            Schema modelSchema = ModelUtils.getSchema(this.openAPI, result.name);
            CodegenProperty modelProperty = fromProperty("value", modelSchema);
            if (modelProperty.isEnum == true || modelProperty.hasValidation == true) {
                // these models are non-object models with enums and/or validations
                // add a single property to the model so we can have a way to access validations
                result.isAlias = true;
                modelProperty.required = true;
                List theProperties = Arrays.asList(modelProperty);
                result.setAllVars(theProperties);
                result.setVars(theProperties);
                result.setRequiredVars(theProperties);
                // post process model properties
                if (result.vars != null) {
                    for (CodegenProperty prop : result.vars) {
                        postProcessModelProperty(result, prop);
                    }
                }

            }
        }

        // set regex values, before it was only done on model.vars
        // fix all property references to non-object models, make those properties non-primitive and
        // set their dataType and complexType to the model name, so documentation will refer to the correct model
        ArrayList> listOfLists = new ArrayList>();
        listOfLists.add(result.vars);
        listOfLists.add(result.allVars);
        listOfLists.add(result.requiredVars);
        listOfLists.add(result.optionalVars);
        listOfLists.add(result.readOnlyVars);
        listOfLists.add(result.readWriteVars);
        for (List cpList : listOfLists) {
            for (CodegenProperty cp : cpList) {
                // set regex values, before it was only done on model.vars
                postProcessModelProperty(result, cp);
                // fix references to non-object models
                if (!propertyToModelName.containsKey(cp.name)) {
                    continue;
                }
                cp.isPrimitiveType = false;
                String modelName = propertyToModelName.get(cp.name);
                cp.complexType = getPythonClassName(modelName);
                cp.dataType = modelName;
                cp.isEnum = false;
                cp.hasValidation = false;
                result.imports.add(modelName);
            }
        }
        // if a class has a property of type self, remove the self import from imports
        if (result.imports.contains(result.classname)) {
            result.imports.remove(result.classname);
        }

        if (result.requiredVars.size() > 0 && (result.oneOf.size() > 0 || result.anyOf.size() > 0)) {
            addNullDefaultToOneOfAnyOfReqProps(schema, result);
        }

        return result;
    }

    /**
     * Output the type declaration of the property
     *
     * @param schema property schema
     * @return a string presentation of the property type
     */
    public String getSimpleTypeDeclaration(Schema schema) {
        String oasType = getSchemaType(schema);
        if (typeMapping.containsKey(oasType)) {
            return typeMapping.get(oasType);
        }
        return oasType;
    }

    /**
     * Return a string representation of the Python types for the specified schema.
     * Primitive types in the OAS specification are implemented in Python using the corresponding
     * Python primitive types.
     * Composed types (e.g. allAll, oneOf, anyOf) are represented in Python using list of types. 
     * 
     * @param p The OAS schema.
     * @param prefix prepended to the returned value.
     * @param suffix appended to the returned value.
     * @return a string representation of the Python types
     */
    public String getTypeString(Schema p, String prefix, String suffix) {
        // this is used to set dataType, which defines a python tuple of classes
        String fullSuffix = suffix;
        if (")".equals(suffix)) {
            fullSuffix = "," + suffix;
        }
        // Resolve $ref because ModelUtils.isXYZ methods do not automatically resolve references.
        if (ModelUtils.isNullable(ModelUtils.getReferencedSchema(this.openAPI, p))) {
            fullSuffix = ", none_type" + suffix;
        }
        if (ModelUtils.isFreeFormObject(p) && ModelUtils.getAdditionalProperties(p) == null) {
            return prefix + "bool, date, datetime, dict, float, int, list, str" + fullSuffix;
        }
        if ((ModelUtils.isMapSchema(p) || "object".equals(p.getType())) && ModelUtils.getAdditionalProperties(p) != null) {
            Schema inner = ModelUtils.getAdditionalProperties(p);
            return prefix + "{str: " + getTypeString(inner, "(", ")") + "}" + fullSuffix;
        } else if (ModelUtils.isArraySchema(p)) {
            ArraySchema ap = (ArraySchema) p;
            Schema inner = ap.getItems();
            return prefix + "[" + getTypeString(inner, "", "") + "]" + fullSuffix;
        }
        if (ModelUtils.isFileSchema(p)) {
            return prefix + "file_type" + fullSuffix;
        }
        String baseType = getSimpleTypeDeclaration(p);
        return prefix + baseType + fullSuffix;
    }

    /**
     * Output the type declaration of a given name
     *
     * @param p property schema
     * @return a string presentation of the type
     */
    @Override
    public String getTypeDeclaration(Schema p) {
        // this is used to set dataType, which defines a python tuple of classes
        // in Python we will wrap this in () to make it a tuple but here we
        // will omit the parens so the generated documentaion will not include
        // them
        return getTypeString(p, "", "");
    }

    @Override
    public String toInstantiationType(Schema property) {
        if (ModelUtils.isArraySchema(property) || ModelUtils.isMapSchema(property) || property.getAdditionalProperties() != null) {
            return getSchemaType(property);
        }
        return super.toInstantiationType(property);
    }

    @Override
    protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) {
        Schema addProps = ModelUtils.getAdditionalProperties(schema);
        if (addProps != null && addProps.get$ref() == null) {
            // if AdditionalProperties exists and is an inline definition, get its datatype and store it in m.parent
            String typeString = getTypeDeclaration(addProps);
            codegenModel.additionalPropertiesType = typeString;
        } else {
            addParentContainer(codegenModel, codegenModel.name, schema);
        }
    }

    @Override
    public void setParameterExampleValue(CodegenParameter p) {
        // we have a custom version of this function so we can set the file
        // type example value
        String example;

        if (p.defaultValue == null) {
            example = p.example;
        } else {
            p.example = p.defaultValue;
            return;
        }

        String type = p.baseType;
        if (type == null) {
            type = p.dataType;
        }

        if ("String".equalsIgnoreCase(type) || "str".equalsIgnoreCase(type)) {
            if (example == null) {
                example = p.paramName + "_example";
            }
            example = "'" + escapeText(example) + "'";
        } else if ("Integer".equals(type) || "int".equals(type)) {
            if (example == null) {
                example = "56";
            }
        } else if ("Float".equalsIgnoreCase(type) || "Double".equalsIgnoreCase(type)) {
            if (example == null) {
                example = "3.4";
            }
        } else if ("BOOLEAN".equalsIgnoreCase(type) || "bool".equalsIgnoreCase(type)) {
            if (example == null) {
                example = "True";
            }
        } else if ("file".equalsIgnoreCase(type)) {
            if (example == null) {
                example = "/path/to/file";
            }
            example = "open('"+example+"', 'rb')";
        } else if ("Date".equalsIgnoreCase(type)) {
            if (example == null) {
                example = "2013-10-20";
            }
            example = "'" + escapeText(example) + "'";
        } else if ("DateTime".equalsIgnoreCase(type)) {
            if (example == null) {
                example = "2013-10-20T19:20:30+01:00";
            }
            example = "'" + escapeText(example) + "'";
        } else if (!languageSpecificPrimitives.contains(type)) {
            // type is a model class, e.g. user.User
            example = this.packageName + "." + getPythonClassName(type) + "()";
        } else {
            LOGGER.warn("Type " + type + " not handled properly in setParameterExampleValue");
        }

        if (example == null) {
            example = "None";
        } else if (Boolean.TRUE.equals(p.isListContainer)) {
            example = "[" + example + "]";
        } else if (Boolean.TRUE.equals(p.isMapContainer)) {
            example = "{'key': " + example + "}";
        }

        p.example = example;
    }

    private String simpleModelName(String name) {
        // this returns a model name like Cat
        String modelName = sanitizeName(name);
        // remove dollar sign
        modelName = modelName.replaceAll("$", "");

        // model name cannot use reserved keyword, e.g. return
        if (isReservedWord(modelName)) {
            LOGGER.warn(modelName + " (reserved word) cannot be used as model name. Renamed to " + camelize("model_" + modelName));
            modelName = "model_" + modelName; // e.g. return => ModelReturn (after camelize)
        }

        // model name starts with number
        if (modelName.matches("^\\d.*")) {
            LOGGER.warn(modelName + " (model name starts with number) cannot be used as model name. Renamed to " + camelize("model_" + modelName));
            modelName = "model_" + modelName; // e.g. 200Response => Model200Response (after camelize)
        }

        if (!StringUtils.isEmpty(modelNamePrefix)) {
            modelName = modelNamePrefix + "_" + modelName;
        }

        if (!StringUtils.isEmpty(modelNameSuffix)) {
            modelName = modelName + "_" + modelNameSuffix;
        }

        // camelize the model name
        // phone_number => PhoneNumber
        return camelize(modelName);
    }


    @Override
    public String toModelFilename(String name) {
        // underscore the model file name
        // PhoneNumber => phone_number
        return underscore(dropDots(simpleModelName(name)));
    }

    @Override
    public String toModelName(String name) {
        // we have a custom version of this function so we can support circular references in python 2 and 3
        return toModelFilename(name)+"."+simpleModelName(name);
    }

    @Override
    public String toModelDocFilename(String name) {
        // this is used to generate the model's .md documentation file
        return simpleModelName(name);
    }
}