org.openapitools.codegen.languages.AbstractPythonCodegen 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 static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.escape;
import static org.openapitools.codegen.utils.StringUtils.underscore;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import lombok.Setter;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.CodegenConfig;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenMediaType;
import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenParameter;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.CodegenResponse;
import org.openapitools.codegen.DefaultCodegen;
import org.openapitools.codegen.GeneratorLanguage;
import org.openapitools.codegen.IJsonSchemaValidationProperties;
import org.openapitools.codegen.meta.features.SecurityFeature;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.ModelsMap;
import org.openapitools.codegen.model.OperationMap;
import org.openapitools.codegen.model.OperationsMap;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.curiousoddman.rgxgen.RgxGen;
import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
public abstract class AbstractPythonCodegen extends DefaultCodegen implements CodegenConfig {
private final Logger LOGGER = LoggerFactory.getLogger(AbstractPythonCodegen.class);
public static final String MAP_NUMBER_TO = "mapNumberTo";
protected String packageName = "openapi_client";
@Setter protected String packageVersion = "1.0.0";
@Setter protected String projectName; // for setup.py, e.g. petstore-api
protected boolean hasModelsToImport = Boolean.FALSE;
protected String mapNumberTo = "Union[StrictFloat, StrictInt]";
protected Map regexModifiers;
private Map schemaKeyToModelNameCache = new HashMap<>();
// map of set (model imports)
private HashMap> circularImports = new HashMap<>();
// map of codegen models
private HashMap codegenModelMap = new HashMap<>();
public AbstractPythonCodegen() {
super();
modifyFeatureSet(features -> features.securityFeatures(EnumSet.of(
SecurityFeature.BasicAuth,
SecurityFeature.BearerToken,
SecurityFeature.ApiKey,
SecurityFeature.OAuth2_Implicit
)));
// from https://docs.python.org/3/reference/lexical_analysis.html#keywords
setReservedWordsLowerCase(
Arrays.asList(
// pydantic
"field",
// local variable name used in API methods (endpoints)
"all_params", "resource_path", "path_params", "query_params",
"header_params", "form_params", "local_var_files", "body_params", "auth_settings",
// @property
"property",
// typing keywords
"schema", "base64", "json",
"date", "float",
// python reserved words
"and", "del", "from", "not", "while", "as", "elif", "global", "or", "with",
"assert", "else", "if", "pass", "yield", "break", "except", "import",
"print", "class", "exec", "in", "raise", "continue", "finally", "is",
"return", "def", "for", "lambda", "try", "self", "nonlocal", "None", "True",
"False", "async", "await"));
languageSpecificPrimitives.clear();
languageSpecificPrimitives.add("int");
languageSpecificPrimitives.add("float");
languageSpecificPrimitives.add("list");
languageSpecificPrimitives.add("dict");
languageSpecificPrimitives.add("List");
languageSpecificPrimitives.add("Dict");
languageSpecificPrimitives.add("bool");
languageSpecificPrimitives.add("str");
languageSpecificPrimitives.add("datetime");
languageSpecificPrimitives.add("date");
languageSpecificPrimitives.add("object");
// TODO file and binary is mapped as `file`
languageSpecificPrimitives.add("file");
languageSpecificPrimitives.add("bytes");
typeMapping.clear();
typeMapping.put("integer", "int");
typeMapping.put("float", "float");
typeMapping.put("number", "float");
typeMapping.put("long", "int");
typeMapping.put("double", "float");
typeMapping.put("array", "list");
typeMapping.put("set", "list");
typeMapping.put("map", "dict");
typeMapping.put("boolean", "bool");
typeMapping.put("string", "str");
typeMapping.put("date", "date");
typeMapping.put("DateTime", "datetime");
typeMapping.put("object", "object");
typeMapping.put("AnyType", "object");
typeMapping.put("file", "file");
// TODO binary should be mapped to byte array
// mapped to String as a workaround
typeMapping.put("binary", "str");
typeMapping.put("ByteArray", "str");
// map uuid to string for the time being
typeMapping.put("UUID", "str");
typeMapping.put("URI", "str");
typeMapping.put("null", "none_type");
regexModifiers = new HashMap();
regexModifiers.put('i', "IGNORECASE");
regexModifiers.put('l', "LOCALE");
regexModifiers.put('m', "MULTILINE");
regexModifiers.put('s', "DOTALL");
regexModifiers.put('u', "UNICODE");
regexModifiers.put('x', "VERBOSE");
}
@Override
public void processOpts() {
super.processOpts();
if (StringUtils.isEmpty(System.getenv("PYTHON_POST_PROCESS_FILE"))) {
LOGGER.info("Environment variable PYTHON_POST_PROCESS_FILE not defined so the Python code may not be properly formatted. To define it, try 'export PYTHON_POST_PROCESS_FILE=\"/usr/local/bin/yapf -i\"' (Linux/Mac)");
LOGGER.info("NOTE: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI).");
} else if (!this.isEnablePostProcessFile()) {
LOGGER.info("Warning: Environment variable 'PYTHON_POST_PROCESS_FILE' is set but file post-processing is not enabled. To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI).");
}
}
@Override
public String escapeReservedWord(String name) {
if (this.reservedWordsMappings().containsKey(name)) {
return this.reservedWordsMappings().get(name);
}
return "_" + name;
}
/**
* 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 the Schema is a $ref, get the default value from the reference.
String ref = ModelUtils.getSimpleRef(p.get$ref());
if (ref != null) {
Schema referencedSchema = ModelUtils.getSchemas(this.openAPI).get(ref);
if (referencedSchema != null) {
String defaultValue = toDefaultValue(referencedSchema);
if (defaultValue != null) {
return defaultValue;
}
// No default was found for the $ref, so see if one has been defined locally.
}
}
if (ModelUtils.isBooleanSchema(p)) {
if (p.getDefault() != null) {
if (!Boolean.valueOf(p.getDefault().toString()))
return "False";
else
return "True";
}
} else if (ModelUtils.isDateSchema(p)) {
// TODO
} else if (ModelUtils.isDateTimeSchema(p)) {
// TODO
} else if (ModelUtils.isNumberSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
}
} else if (ModelUtils.isIntegerSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
}
} else if (ModelUtils.isEnumSchema(p)) {
// Enum must come before string to use enum reference!
if (p.getDefault() != null) {
String defaultValue = String.valueOf(p.getDefault());
if (defaultValue != null) {
return defaultValue;
}
}
} else if (ModelUtils.isStringSchema(p)) {
if (p.getDefault() != null) {
String defaultValue = String.valueOf(p.getDefault());
if (defaultValue != null) {
defaultValue = defaultValue.replace("\\", "\\\\")
.replace("'", "\'");
if (Pattern.compile("\r\n|\r|\n").matcher(defaultValue).find()) {
return "'''" + defaultValue + "'''";
} else {
return "'" + defaultValue + "'";
}
}
}
} else if (ModelUtils.isArraySchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
} else {
return null;
}
}
return null;
}
@Override
public String toVarName(String name) {
// obtain the name from nameMapping directly if provided
if (nameMapping.containsKey(name)) {
return nameMapping.get(name);
}
// sanitize name
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
// remove dollar sign
name = name.replace("$", "");
// if it's all upper case, convert to lower case
if (name.matches("^[A-Z_]*$")) {
name = name.toLowerCase(Locale.ROOT);
}
// underscore the variable name
// petId => pet_id
name = underscore(name);
// remove leading underscore
name = name.replaceAll("^_*", "");
// for reserved word or word starting with number, append _
if (isReservedWord(name) || name.matches("^\\d.*")) {
name = escapeReservedWord(name);
}
return name;
}
@Override
public String toRegularExpression(String pattern) {
return addRegularExpressionDelimiter(pattern);
}
@Override
public String toParamName(String name) {
// obtain the name from parameterNameMapping directly if provided
if (parameterNameMapping.containsKey(name)) {
return parameterNameMapping.get(name);
}
// to avoid conflicts with 'callback' parameter for async call
if ("callback".equals(name)) {
return "param_callback";
}
// should be the same as variable name
return toVarName(name);
}
@Override
public String toOperationId(String operationId) {
// throw exception if method name is empty (should not occur as an auto-generated method name will be used)
if (StringUtils.isEmpty(operationId)) {
throw new RuntimeException("Empty method name (operationId) not allowed");
}
// method name cannot use reserved keyword, e.g. return
if (isReservedWord(operationId)) {
LOGGER.warn("{} (reserved word) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId)));
operationId = "call_" + operationId;
}
// operationId starts with a number
if (operationId.matches("^\\d.*")) {
LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId)));
operationId = "call_" + operationId;
}
return underscore(sanitizeName(operationId));
}
@Override
public String escapeQuotationMark(String input) {
// remove ' to avoid code injection
return input.replace("'", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
// remove multiline comment
return input.replace("'''", "'_'_'");
}
@Override
public void postProcessFile(File file, String fileType) {
super.postProcessFile(file, fileType);
if (file == null) {
return;
}
String pythonPostProcessFile = System.getenv("PYTHON_POST_PROCESS_FILE");
if (StringUtils.isEmpty(pythonPostProcessFile)) {
return; // skip if PYTHON_POST_PROCESS_FILE env variable is not defined
}
// only process files with py extension
if ("py".equals(FilenameUtils.getExtension(file.toString()))) {
this.executePostProcessor(new String[] {pythonPostProcessFile, file.toString()});
}
}
@Override
public String toExampleValue(Schema schema) {
return toExampleValueRecursive(schema, new ArrayList<>(), 5);
}
private String toExampleValueRecursive(Schema schema, List includedSchemas, int indentation) {
boolean cycleFound = includedSchemas.stream().filter(s -> schema.equals(s)).count() > 1;
if (cycleFound) {
return "";
}
String indentationString = "";
for (int i = 0; i < indentation; i++) indentationString += " ";
String example = null;
if (schema.getExample() != null) {
example = schema.getExample().toString();
}
if (ModelUtils.isNullType(schema) && null != example) {
// The 'null' type is allowed in OAS 3.1 and above. It is not supported by OAS 3.0.x,
// though this tooling supports it.
return "None";
}
// correct "true"s into "True"s, since super.toExampleValue uses "toString()" on Java booleans
if (ModelUtils.isBooleanSchema(schema) && null != example) {
if ("false".equalsIgnoreCase(example)) example = "False";
else example = "True";
}
// correct "'"s into "'"s after toString()
if (ModelUtils.isStringSchema(schema) && schema.getDefault() != null && !ModelUtils.isDateSchema(schema) && !ModelUtils.isDateTimeSchema(schema)) {
example = String.valueOf(schema.getDefault());
}
if (StringUtils.isNotBlank(example) && !"null".equals(example)) {
if (ModelUtils.isStringSchema(schema)) {
example = "'" + example + "'";
}
return example;
}
if (schema.getEnum() != null && !schema.getEnum().isEmpty()) {
// Enum case:
example = schema.getEnum().get(0).toString();
if (ModelUtils.isStringSchema(schema)) {
example = "'" + escapeTextInSingleQuotes(example) + "'";
}
if (null == example)
LOGGER.warn("Empty enum. Cannot built an example!");
return example;
} else if (null != schema.get$ref()) {
// $ref case:
Map allDefinitions = ModelUtils.getSchemas(this.openAPI);
String ref = ModelUtils.getSimpleRef(schema.get$ref());
if (allDefinitions != null) {
Schema refSchema = allDefinitions.get(ref);
if (null == refSchema) {
return "None";
} else {
String refTitle = refSchema.getTitle();
if (StringUtils.isBlank(refTitle) || "null".equals(refTitle)) {
refSchema.setTitle(ref);
}
if (StringUtils.isNotBlank(schema.getTitle()) && !"null".equals(schema.getTitle())) {
includedSchemas.add(schema);
}
return toExampleValueRecursive(refSchema, includedSchemas, indentation);
}
} else {
LOGGER.warn("allDefinitions not defined in toExampleValue!\n");
}
}
if (ModelUtils.isDateSchema(schema)) {
example = "datetime.datetime.strptime('1975-12-30', '%Y-%m-%d').date()";
return example;
} else if (ModelUtils.isDateTimeSchema(schema)) {
example = "datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f')";
return example;
} else if (ModelUtils.isBinarySchema(schema)) {
example = "bytes(b'blah')";
return example;
} else if (ModelUtils.isByteArraySchema(schema)) {
example = "YQ==";
} else if (ModelUtils.isStringSchema(schema)) {
// a BigDecimal:
if ("Number".equalsIgnoreCase(schema.getFormat())) {
return "1";
}
if (StringUtils.isNotBlank(schema.getPattern())) {
String pattern = schema.getPattern();
RgxGen rgxGen = new RgxGen(patternCorrection(pattern));
// this seed makes it so if we have [a-z] we pick a
Random random = new Random(18);
String sample = rgxGen.generate(random);
// omit leading / and trailing /, omit trailing /i
Pattern valueExtractor = Pattern.compile("^/\\^?(.+?)\\$?/.?$");
Matcher m = valueExtractor.matcher(sample);
if (m.find()) {
example = m.group(m.groupCount());
} else {
example = sample;
}
}
if (example == null) {
example = "";
}
int len = 0;
if (null != schema.getMinLength()) {
len = schema.getMinLength().intValue();
if (len < 1) {
example = "";
} else {
for (int i = 0; i < len; i++) example += i;
}
}
} else if (ModelUtils.isIntegerSchema(schema)) {
if (schema.getMinimum() != null)
example = schema.getMinimum().toString();
else
example = "56";
} else if (ModelUtils.isNumberSchema(schema)) {
if (schema.getMinimum() != null)
example = schema.getMinimum().toString();
else
example = "1.337";
} else if (ModelUtils.isBooleanSchema(schema)) {
example = "True";
} else if (ModelUtils.isArraySchema(schema)) {
if (StringUtils.isNotBlank(schema.getTitle()) && !"null".equals(schema.getTitle())) {
includedSchemas.add(schema);
}
example = "[\n" + indentationString + toExampleValueRecursive(ModelUtils.getSchemaItems(schema), includedSchemas, indentation + 1) + "\n" + indentationString + "]";
} else if (ModelUtils.isMapSchema(schema)) {
if (StringUtils.isNotBlank(schema.getTitle()) && !"null".equals(schema.getTitle())) {
includedSchemas.add(schema);
}
Object additionalObject = schema.getAdditionalProperties();
if (additionalObject instanceof Schema) {
Schema additional = (Schema) additionalObject;
String theKey = "'key'";
if (additional.getEnum() != null && !additional.getEnum().isEmpty()) {
theKey = additional.getEnum().get(0).toString();
if (ModelUtils.isStringSchema(additional)) {
theKey = "'" + escapeTextInSingleQuotes(theKey) + "'";
}
}
example = "{\n" + indentationString + theKey + " : " + toExampleValueRecursive(additional, includedSchemas, indentation + 1) + "\n" + indentationString + "}";
} else {
example = "{ }";
}
} else if (ModelUtils.isObjectSchema(schema)) {
if (StringUtils.isBlank(schema.getTitle())) {
example = "None";
return example;
}
// I remove any property that is a discriminator, since it is not well supported by the python generator
String toExclude = null;
if (schema.getDiscriminator() != null) {
toExclude = schema.getDiscriminator().getPropertyName();
}
example = packageName + ".models." + underscore(schema.getTitle()) + "." + schema.getTitle() + "(";
// if required only:
// List reqs = schema.getRequired();
// if required and optionals
List reqs = new ArrayList<>();
if (schema.getProperties() != null && !schema.getProperties().isEmpty()) {
for (Object toAdd : schema.getProperties().keySet()) {
reqs.add((String) toAdd);
}
Map properties = schema.getProperties();
Set propkeys = null;
if (properties != null) propkeys = properties.keySet();
if (toExclude != null && reqs.contains(toExclude)) {
reqs.remove(toExclude);
}
for (String toRemove : includedSchemas.stream().map(Schema::getTitle).collect(Collectors.toList())) {
if (reqs.contains(toRemove)) {
reqs.remove(toRemove);
}
}
if (StringUtils.isNotBlank(schema.getTitle()) && !"null".equals(schema.getTitle())) {
includedSchemas.add(schema);
}
if (null != schema.getRequired()) for (Object toAdd : schema.getRequired()) {
reqs.add((String) toAdd);
}
if (null != propkeys) for (String propname : propkeys) {
Schema schema2 = properties.get(propname);
if (reqs.contains(propname)) {
String refTitle = schema2.getTitle();
if (StringUtils.isBlank(refTitle) || "null".equals(refTitle)) {
schema2.setTitle(propname);
}
example += "\n" + indentationString + underscore(propname) + " = " +
toExampleValueRecursive(schema2, includedSchemas, indentation + 1) + ", ";
}
}
}
example += ")";
} else {
LOGGER.debug("Type {} not handled properly in toExampleValue", schema.getType());
}
if (ModelUtils.isStringSchema(schema)) {
example = "'" + escapeTextInSingleQuotes(example) + "'";
}
return example;
}
@Override
public void setParameterExampleValue(CodegenParameter p) {
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 = "'" + escapeTextInSingleQuotes(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 = "'" + escapeTextInSingleQuotes(example) + "'";
} else if ("Date".equalsIgnoreCase(type)) {
if (example == null) {
example = "2013-10-20";
}
example = "'" + escapeTextInSingleQuotes(example) + "'";
} else if ("DateTime".equalsIgnoreCase(type)) {
if (example == null) {
example = "2013-10-20T19:20:30+01:00";
}
example = "'" + escapeTextInSingleQuotes(example) + "'";
} else if (!languageSpecificPrimitives.contains(type)) {
// type is a model class, e.g. User
example = this.packageName + "." + type + "()";
} else {
LOGGER.debug("Type {} not handled properly in setParameterExampleValue", type);
}
if (example == null) {
example = "None";
} else if (Boolean.TRUE.equals(p.isArray)) {
example = "[" + example + "]";
} else if (Boolean.TRUE.equals(p.isMap)) {
example = "{'key': " + example + "}";
}
p.example = example;
}
@Override
public void setParameterExampleValue(CodegenParameter codegenParameter, Parameter parameter) {
Schema schema = parameter.getSchema();
if (parameter.getExample() != null) {
codegenParameter.example = parameter.getExample().toString();
} else if (parameter.getExamples() != null && !parameter.getExamples().isEmpty()) {
Example example = parameter.getExamples().values().iterator().next();
if (example.getValue() != null) {
codegenParameter.example = example.getValue().toString();
}
} else if (schema != null && schema.getExample() != null) {
codegenParameter.example = schema.getExample().toString();
}
setParameterExampleValue(codegenParameter);
}
@Override
public String sanitizeTag(String tag) {
return sanitizeName(tag);
}
public String patternCorrection(String pattern) {
// Java does not recognize starting and ending forward slashes and mode modifiers
// It considers them as characters with no special meaning and tries to find them in the match string
boolean checkEnding = pattern.endsWith("/i") || pattern.endsWith("/g") || pattern.endsWith("/m");
if (checkEnding) pattern = pattern.substring(0, pattern.length() - 2);
if (pattern.endsWith("/")) pattern = pattern.substring(0, pattern.length() - 1);
if (pattern.startsWith("/")) pattern = pattern.substring(1);
return pattern;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
additionalProperties.put(CodegenConstants.PACKAGE_NAME, this.packageName);
}
@Override
public String getTypeDeclaration(Schema p) {
p = ModelUtils.unaliasSchema(openAPI, p);
if (ModelUtils.isArraySchema(p)) {
Schema inner = ModelUtils.getSchemaItems(p);
return getSchemaType(p) + "[" + getCollectionItemTypeDeclaration(inner) + "]";
} else if (ModelUtils.isMapSchema(p)) {
Schema inner = ModelUtils.getAdditionalProperties(p);
return getSchemaType(p) + "[str, " + getCollectionItemTypeDeclaration(inner) + "]";
}
String openAPIType = getSchemaType(p);
if (typeMapping.containsKey(openAPIType)) {
return typeMapping.get(openAPIType);
}
if (languageSpecificPrimitives.contains(openAPIType)) {
return openAPIType;
}
return toModelName(openAPIType);
}
private String getCollectionItemTypeDeclaration(Schema inner) {
String innerDeclaration = getTypeDeclaration(inner);
if (Boolean.TRUE.equals(inner.getNullable())) {
innerDeclaration = "Optional[" + innerDeclaration + "]";
}
return innerDeclaration;
}
@Override
public String getSchemaType(Schema p) {
String openAPIType = super.getSchemaType(p);
String type;
if (openAPIType == null) {
LOGGER.error("OpenAPI Type for {} is null. Default to UNKNOWN_OPENAPI_TYPE instead.", p.getName());
openAPIType = "UNKNOWN_OPENAPI_TYPE";
}
if (typeMapping.containsKey(openAPIType)) {
type = typeMapping.get(openAPIType);
if (type != null) {
return type;
}
} else {
type = openAPIType;
}
return toModelName(type);
}
@Override
public String toModelName(String name) {
// obtain the name from modelNameMapping directly if provided
if (modelNameMapping.containsKey(name)) {
return modelNameMapping.get(name);
}
// check if schema-mapping has a different model for this class, so we can use it
// instead of the auto-generated one.
if (schemaMapping.containsKey(name)) {
return schemaMapping.get(name);
}
// memoization
String origName = name;
if (schemaKeyToModelNameCache.containsKey(origName)) {
return schemaKeyToModelNameCache.get(origName);
}
String sanitizedName = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
// remove dollar sign
sanitizedName = sanitizedName.replace("$", "");
// remove whitespace
sanitizedName = sanitizedName.replaceAll("\\s+", "");
String nameWithPrefixSuffix = sanitizedName;
if (!StringUtils.isEmpty(modelNamePrefix)) {
// add '_' so that model name can be camelized correctly
nameWithPrefixSuffix = modelNamePrefix + "_" + nameWithPrefixSuffix;
}
if (!StringUtils.isEmpty(modelNameSuffix)) {
// add '_' so that model name can be camelized correctly
nameWithPrefixSuffix = nameWithPrefixSuffix + "_" + modelNameSuffix;
}
// camelize the model name
// phone_number => PhoneNumber
String camelizedName = camelize(nameWithPrefixSuffix);
// model name cannot use reserved keyword, e.g. return
if (isReservedWord(camelizedName)) {
String modelName = "Model" + camelizedName; // e.g. return => ModelReturn (after camelize)
LOGGER.warn("{} (reserved word) cannot be used as model name. Renamed to {}", camelizedName, modelName);
schemaKeyToModelNameCache.put(origName, modelName);
return modelName;
}
// model name starts with number
if (camelizedName.matches("^\\d.*")) {
String modelName = "Model" + camelizedName; // e.g. return => ModelReturn (after camelize)
LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", camelizedName, modelName);
schemaKeyToModelNameCache.put(origName, modelName);
return modelName;
}
schemaKeyToModelNameCache.put(origName, camelizedName);
return camelizedName;
}
@Override
public String toModelFilename(String name) {
// underscore the model file name
// PhoneNumber => phone_number
return underscore(dropDots(toModelName(name)));
}
@Override
public String toModelTestFilename(String name) {
return "test_" + toModelFilename(name);
}
@Override
public String toApiFilename(String name) {
// e.g. PhoneNumberApi.py => phone_number_api.py
return underscore(toApiName(name));
}
@Override
public String toApiTestFilename(String name) {
return "test_" + toApiFilename(name);
}
@Override
public String toApiName(String name) {
return super.toApiName(name);
}
@Override
public String toApiVarName(String name) {
return underscore(toApiName(name));
}
protected static String dropDots(String str) {
return str.replaceAll("\\.", "_");
}
@Override
public GeneratorLanguage generatorLanguage() {
return GeneratorLanguage.PYTHON;
}
@Override
public Map postProcessAllModels(Map objs) {
final Map processed = super.postProcessAllModels(objs);
for (Map.Entry entry : objs.entrySet()) {
// create hash map of codegen model
CodegenModel cm = ModelUtils.getModelByName(entry.getKey(), objs);
codegenModelMap.put(cm.classname, ModelUtils.getModelByName(entry.getKey(), objs));
}
// create circular import
for (String m : codegenModelMap.keySet()) {
createImportMapOfSet(m, codegenModelMap);
}
for (Map.Entry entry : processed.entrySet()) {
entry.setValue(postProcessModelsMap(entry.getValue()));
}
return processed;
}
private ModelsMap postProcessModelsMap(ModelsMap objs) {
// process enum in models
objs = postProcessModelsEnum(objs);
// TODO: migrate almost (all?) everything to the `PythonImports` class.
TreeSet modelImports = new TreeSet<>();
TreeSet postponedModelImports = new TreeSet<>();
for (ModelMap m : objs.getModels()) {
TreeSet exampleImports = new TreeSet<>();
TreeSet postponedExampleImports = new TreeSet<>();
List readOnlyFields = new ArrayList<>();
hasModelsToImport = false;
int property_count = 1;
PythonImports moduleImports = new PythonImports();
CodegenModel model = m.getModel();
PydanticType pydantic = new PydanticType(
modelImports,
exampleImports,
postponedModelImports,
postponedExampleImports,
moduleImports,
model.classname
);
// handle null type in oneOf
if (model.getComposedSchemas() != null && model.getComposedSchemas().getOneOf() != null
&& !model.getComposedSchemas().getOneOf().isEmpty()) {
int index = 0;
List oneOfs = model.getComposedSchemas().getOneOf();
for (CodegenProperty oneOf : oneOfs) {
if ("none_type".equals(oneOf.dataType)) {
oneOfs.remove(index);
break; // return earlier assuming there's only 1 null type defined
}
index++;
}
}
List codegenProperties = null;
if (!model.oneOf.isEmpty()) { // oneOfValidationError
codegenProperties = model.getComposedSchemas().getOneOf();
moduleImports.add("typing", "Any");
moduleImports.add("typing", "List");
moduleImports.add("pydantic", "Field");
moduleImports.add("pydantic", "StrictStr");
moduleImports.add("pydantic", "ValidationError");
moduleImports.add("pydantic", "field_validator");
} else if (!model.anyOf.isEmpty()) { // anyOF
codegenProperties = model.getComposedSchemas().getAnyOf();
moduleImports.add("pydantic", "Field");
moduleImports.add("pydantic", "StrictStr");
moduleImports.add("pydantic", "ValidationError");
moduleImports.add("pydantic", "field_validator");
} else { // typical model
codegenProperties = model.vars;
// if super class
if (model.getDiscriminator() != null && model.getDiscriminator().getMappedModels() != null) {
moduleImports.add("typing", "Union");
moduleImports.add("importlib", "import_module");
}
}
if (!model.allOf.isEmpty()) { // allOf
for (CodegenProperty cp : model.allVars) {
if (!cp.isPrimitiveType || cp.isModel) {
if (cp.isArray || cp.isMap){ // if array or map
modelImports.add(cp.items.dataType);
}else{ // if model
modelImports.add(cp.getDataType());
}
}
}
}
// if model_generic.mustache is used
if (model.oneOf.isEmpty() && model.anyOf.isEmpty() && !model.isEnum) {
moduleImports.add("typing", "ClassVar");
moduleImports.add("typing", "Dict");
moduleImports.add("typing", "Any");
if(this.disallowAdditionalPropertiesIfNotPresent || model.isAdditionalPropertiesTrue) {
moduleImports.add("typing", "List");
}
}
// if pydantic model
if (!model.isEnum) {
moduleImports.add("pydantic", "ConfigDict");
}
//loop through properties/schemas to set up typing, pydantic
for (CodegenProperty cp : codegenProperties) {
// is readOnly?
if (cp.isReadOnly) {
readOnlyFields.add(cp.name);
}
String typing = pydantic.generatePythonType(cp);
cp.vendorExtensions.put("x-py-typing", typing);
// setup x-py-name for each oneOf/anyOf schema
if (!model.oneOf.isEmpty()) { // oneOf
cp.vendorExtensions.put("x-py-name", String.format(Locale.ROOT, "oneof_schema_%d_validator", property_count++));
} else if (!model.anyOf.isEmpty()) { // anyOf
cp.vendorExtensions.put("x-py-name", String.format(Locale.ROOT, "anyof_schema_%d_validator", property_count++));
}
}
// add parent model to import
if (!StringUtils.isEmpty(model.parent)) {
modelImports.add(model.parent);
} else if (!model.isEnum) {
moduleImports.add("pydantic", "BaseModel");
}
// set enum type in extensions and update `name` in enumVars
if (model.isEnum) {
for (Map enumVars : (List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy