org.openapitools.codegen.languages.PythonAbstractConnexionServerCodegen 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 com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.PathItem.HttpMethod;
import io.swagger.v3.oas.models.media.ArraySchema;
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.security.SecurityScheme;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.meta.features.DocumentationFeature;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.*;
import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.underscore;
public class PythonAbstractConnexionServerCodegen extends DefaultCodegen implements CodegenConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(PythonAbstractConnexionServerCodegen.class);
public static final String CONTROLLER_PACKAGE = "controllerPackage";
public static final String DEFAULT_CONTROLLER = "defaultController";
public static final String SUPPORT_PYTHON2 = "supportPython2";
// nose is a python testing framework, we use pytest if USE_NOSE is unset
public static final String USE_NOSE = "useNose";
static final String MEDIA_TYPE = "mediaType";
protected int serverPort = 8080;
protected String packageName;
protected String packageVersion;
protected String controllerPackage;
protected String defaultController;
protected Map regexModifiers;
protected boolean fixBodyName;
protected boolean useNose = Boolean.FALSE;
public PythonAbstractConnexionServerCodegen(String templateDirectory, boolean fixBodyNameValue) {
super();
modifyFeatureSet(features -> features.includeDocumentationFeatures(DocumentationFeature.Readme));
fixBodyName = fixBodyNameValue;
modelPackage = "models";
testPackage = "test";
languageSpecificPrimitives.clear();
languageSpecificPrimitives.add("int");
languageSpecificPrimitives.add("float");
languageSpecificPrimitives.add("List");
languageSpecificPrimitives.add("Dict");
languageSpecificPrimitives.add("bool");
languageSpecificPrimitives.add("str");
languageSpecificPrimitives.add("datetime");
languageSpecificPrimitives.add("date");
languageSpecificPrimitives.add("file");
languageSpecificPrimitives.add("object");
languageSpecificPrimitives.add("byte");
languageSpecificPrimitives.add("bytearray");
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("map", "Dict");
typeMapping.put("boolean", "bool");
typeMapping.put("string", "str");
typeMapping.put("date", "date");
typeMapping.put("DateTime", "datetime");
typeMapping.put("object", "object");
typeMapping.put("file", "file");
typeMapping.put("UUID", "str");
typeMapping.put("URI", "str");
typeMapping.put("byte", "bytearray");
typeMapping.put("ByteArray", "bytearray");
// from https://docs.python.org/3/reference/lexical_analysis.html#keywords
setReservedWordsLowerCase(
Arrays.asList(
// @property
"property",
// 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", "None", "True", "False", "nonlocal"));
// set the output folder here
outputFolder = "generated-code/connexion";
apiTemplateFiles.put("controller.mustache", ".py");
modelTemplateFiles.put("model.mustache", ".py");
apiTestTemplateFiles().put("controller_test.mustache", ".py");
/*
* Template Location. This is the location which templates will be read from. The generator
* will use the resource stream to attempt to read the templates.
*/
embeddedTemplateDir = templateDir = templateDirectory;
/*
* Additional Properties. These values can be passed to the templates and
* are available in models, apis, and supporting files
*/
additionalProperties.put("serverPort", serverPort);
/*
* Supporting Files. You can write single files for the generator with the
* entire object tree available. If the input file has a suffix of `.mustache
* it will be processed by the template engine. Otherwise, it will be copied
*/
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
supportingFiles.add(new SupportingFile("test-requirements.mustache", "", "test-requirements.txt"));
supportingFiles.add(new SupportingFile("requirements.mustache", "", "requirements.txt"));
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");
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "python package name (convention: snake_case).")
.defaultValue("openapi_server"));
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_VERSION, "python package version.")
.defaultValue("1.0.0"));
cliOptions.add(new CliOption(CONTROLLER_PACKAGE, "controller package").
defaultValue("controllers"));
cliOptions.add(new CliOption(DEFAULT_CONTROLLER, "default controller").
defaultValue("default_controller"));
cliOptions.add(new CliOption(SUPPORT_PYTHON2, "support python2").
defaultValue("false"));
cliOptions.add(new CliOption("serverPort", "TCP port to listen to in app.run").
defaultValue("8080"));
cliOptions.add(CliOption.newBoolean(USE_NOSE, "use the nose test framework").
defaultValue(Boolean.FALSE.toString()));
}
protected void addSupportingFiles() {
}
@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).");
}
//apiTemplateFiles.clear();
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME));
} else {
setPackageName("openapi_server");
additionalProperties.put(CodegenConstants.PACKAGE_NAME, this.packageName);
}
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) {
setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION));
} else {
setPackageVersion("1.0.0");
additionalProperties.put(CodegenConstants.PACKAGE_VERSION, this.packageVersion);
}
if (additionalProperties.containsKey(CONTROLLER_PACKAGE)) {
this.controllerPackage = additionalProperties.get(CONTROLLER_PACKAGE).toString();
} else {
this.controllerPackage = "controllers";
additionalProperties.put(CONTROLLER_PACKAGE, this.controllerPackage);
}
if (additionalProperties.containsKey(DEFAULT_CONTROLLER)) {
this.defaultController = additionalProperties.get(DEFAULT_CONTROLLER).toString();
} else {
this.defaultController = "default_controller";
additionalProperties.put(DEFAULT_CONTROLLER, this.defaultController);
}
if (Boolean.TRUE.equals(additionalProperties.get(SUPPORT_PYTHON2))) {
additionalProperties.put(SUPPORT_PYTHON2, Boolean.TRUE);
typeMapping.put("long", "long");
}
if (additionalProperties.containsKey(USE_NOSE)) {
setUseNose((String) additionalProperties.get(USE_NOSE));
}
supportingFiles.add(new SupportingFile("__main__.mustache", packagePath(), "__main__.py"));
supportingFiles.add(new SupportingFile("util.mustache", packagePath(), "util.py"));
supportingFiles.add(new SupportingFile("typing_utils.mustache", packagePath(), "typing_utils.py"));
supportingFiles.add(new SupportingFile("__init__.mustache", packagePath() + File.separatorChar + packageToPath(controllerPackage), "__init__.py"));
supportingFiles.add(new SupportingFile("security_controller_.mustache", packagePath() + File.separatorChar + packageToPath(controllerPackage), "security_controller_.py"));
supportingFiles.add(new SupportingFile("__init__model.mustache", packagePath() + File.separatorChar + packageToPath(modelPackage), "__init__.py"));
supportingFiles.add(new SupportingFile("base_model_.mustache", packagePath() + File.separatorChar + packageToPath(modelPackage), "base_model_.py"));
supportingFiles.add(new SupportingFile("openapi.mustache", packagePath() + File.separatorChar + "openapi", "openapi.yaml"));
addSupportingFiles();
modelPackage = packageName + "." + modelPackage;
controllerPackage = packageName + "." + controllerPackage;
}
public void setUseNose(String val) {
this.useNose = Boolean.valueOf(val);
}
private static String packageToPath(String pkg) {
return pkg.replace(".", File.separator);
}
private static String dropDots(String str) {
return str.replaceAll("\\.", "_");
}
@Override
public String apiPackage() {
return controllerPackage;
}
/**
* Configures the type of generator.
*
* @return the CodegenType for this generator
* @see org.openapitools.codegen.CodegenType
*/
@Override
public CodegenType getTag() {
return CodegenType.SERVER;
}
/**
* Returns human-friendly help for the generator. Provide the consumer with help
* tips, parameters here
*
* @return A string value for the help message
*/
@Override
public String getHelp() {
return "Generates a Python server library using the Connexion project. By default, " +
"it will also generate service classes -- which you can disable with the `-Dnoservice` environment variable.";
}
@Override
public String toApiName(String name) {
if (name == null || name.length() == 0) {
return "DefaultController";
}
return camelize(name, false) + "Controller";
}
@Override
public String toApiFilename(String name) {
return underscore(toApiName(name));
}
@Override
public String toApiTestFilename(String name) {
return "test_" + toApiFilename(name);
}
/**
* Escapes a reserved word as defined in the `reservedWords` array. Handle escaping
* those terms here. This logic is only called if a variable matches the reserved words
*
* @return the escaped term
*/
@Override
public String escapeReservedWord(String name) {
if (this.reservedWordsMappings().containsKey(name)) {
return this.reservedWordsMappings().get(name);
}
return "_" + name; // add an underscore to the name
}
/**
* Location to write api files. You can use the apiPackage() as defined when the class is
* instantiated
*/
@Override
public String apiFileFolder() {
return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar);
}
@Override
public String getTypeDeclaration(Schema p) {
if (ModelUtils.isArraySchema(p)) {
ArraySchema ap = (ArraySchema) p;
Schema inner = ap.getItems();
return getSchemaType(p) + "[" + getTypeDeclaration(inner) + "]";
} else if (ModelUtils.isMapSchema(p)) {
Schema inner = ModelUtils.getAdditionalProperties(p);
return getSchemaType(p) + "[str, " + getTypeDeclaration(inner) + "]";
}
return super.getTypeDeclaration(p);
}
@Override
public String getSchemaType(Schema p) {
String schemaType = super.getSchemaType(p);
String type = null;
if (typeMapping.containsKey(schemaType)) {
type = typeMapping.get(schemaType);
if (languageSpecificPrimitives.contains(type)) {
return type;
}
} else {
type = toModelName(schemaType);
}
return type;
}
@Override
public void preprocessOpenAPI(OpenAPI openAPI) {
// need vendor extensions for x-openapi-router-controller
Map paths = openAPI.getPaths();
if (paths != null) {
List pathnames = new ArrayList(paths.keySet());
for (String pathname : pathnames) {
PathItem path = paths.get(pathname);
// Fix path parameters to be in snake_case
if (pathname.contains("{")) {
String fixedPath = new String();
for (String token : pathname.substring(1).split("/")) {
if (token.startsWith("{")) {
String snake_case_token = "{" + this.toParamName(token.substring(1, token.length() - 1)) + "}";
if (!token.equals(snake_case_token)) {
token = snake_case_token;
}
}
fixedPath += "/" + token;
}
if (!fixedPath.equals(pathname)) {
LOGGER.warn("Path '" + pathname + "' is not consistant with Python variable names. It will be replaced by '" + fixedPath + "'");
paths.remove(pathname);
path.addExtension("x-python-connexion-openapi-name", pathname);
paths.put(fixedPath, path);
}
}
Map operationMap = path.readOperationsMap();
if (operationMap != null) {
for (HttpMethod method : operationMap.keySet()) {
Operation operation = operationMap.get(method);
String tag = "default";
if (operation.getTags() != null && operation.getTags().size() > 0) {
tag = operation.getTags().get(0);
}
String operationId = operation.getOperationId();
if (operationId == null) {
operationId = getOrGenerateOperationId(operation, pathname, method.toString());
}
operation.setOperationId(toOperationId(operationId));
if (operation.getExtensions() == null || operation.getExtensions().get("x-openapi-router-controller") == null) {
operation.addExtension(
"x-openapi-router-controller",
controllerPackage + "." + toApiFilename(tag)
);
}
if (operation.getParameters() != null) {
for (Parameter parameter : operation.getParameters()) {
String swaggerParameterName = parameter.getName();
String pythonParameterName = this.toParamName(swaggerParameterName);
if (!swaggerParameterName.equals(pythonParameterName)) {
LOGGER.warn("Parameter name '" + swaggerParameterName + "' is not consistant with Python variable names. It will be replaced by '" + pythonParameterName + "'");
parameter.addExtension("x-python-connexion-openapi-name", swaggerParameterName);
parameter.setName(pythonParameterName);
}
if (swaggerParameterName.isEmpty()) {
LOGGER.error("Missing parameter name in " + pathname + "." + parameter.getIn());
}
}
}
RequestBody body = operation.getRequestBody();
if (fixBodyName && body != null) {
if (body.getExtensions() == null || !body.getExtensions().containsKey("x-body-name")) {
String bodyParameterName = "body";
if (operation.getExtensions() != null && operation.getExtensions().containsKey("x-codegen-request-body-name")) {
bodyParameterName = (String) operation.getExtensions().get("x-codegen-request-body-name");
} else {
// Used by code generator
operation.addExtension("x-codegen-request-body-name", bodyParameterName);
}
// Used by connexion
body.addExtension("x-body-name", bodyParameterName);
}
}
}
}
}
// Sort path names after variable name fix
List fixedPathnames = new ArrayList(paths.keySet());
Collections.sort(fixedPathnames);
for (String pathname : fixedPathnames) {
PathItem pathItem = paths.remove(pathname);
paths.put(pathname, pathItem);
}
}
addSecurityExtensions(openAPI);
}
private void addSecurityExtension(SecurityScheme securityScheme, String extensionName, String functionName) {
if (securityScheme.getExtensions() == null || !securityScheme.getExtensions().containsKey(extensionName)) {
securityScheme.addExtension(extensionName, functionName);
}
}
private void addSecurityExtensions(OpenAPI openAPI) {
Components components = openAPI.getComponents();
if (components != null && components.getSecuritySchemes() != null) {
Map securitySchemes = components.getSecuritySchemes();
for (String securityName : securitySchemes.keySet()) {
SecurityScheme securityScheme = securitySchemes.get(securityName);
String baseFunctionName = controllerPackage + ".security_controller_.";
switch (securityScheme.getType()) {
case APIKEY:
addSecurityExtension(securityScheme, "x-apikeyInfoFunc", baseFunctionName + "info_from_" + securityName);
break;
case HTTP:
if ("basic".equals(securityScheme.getScheme())) {
addSecurityExtension(securityScheme, "x-basicInfoFunc", baseFunctionName + "info_from_" + securityName);
} else if ("bearer".equals(securityScheme.getScheme())) {
addSecurityExtension(securityScheme, "x-bearerInfoFunc", baseFunctionName + "info_from_" + securityName);
}
break;
case OPENIDCONNECT:
LOGGER.warn("Security type " + securityScheme.getType().toString() + " is not supported by connextion yet");
case OAUTH2:
addSecurityExtension(securityScheme, "x-tokenInfoFunc", baseFunctionName + "info_from_" + securityName);
addSecurityExtension(securityScheme, "x-scopeValidateFunc", baseFunctionName + "validate_scope_" + securityName);
break;
default:
LOGGER.warn("Unknown security type " + securityScheme.getType().toString());
}
}
}
}
@SuppressWarnings("unchecked")
private static List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy